SWF Trivia

Sep 3, 2010

While working on my own SWF decompiler (Flashbug 1.7) I've learned a lot of interesting things about SWFs that I wanted to jot down.

  1. PNGs can be compressed : When embedding PNG images into a SWF you have the option of compressing them. This actually converts them to a JPEG with an alpha channel. It's non-standard but definitely a neat trick. This almost negates any reason why you shouldn't always embed a PNG into your FLA.
  2. Text character codes aren't embedded : When you have any text fields in your SWF, the character codes aren't embedded into the SWF. Instead it embeds the glyph codes for the specific font used. It then checks the font to match the glyph code to a character code. I suppose this helps ensure your text is displayed correctly.
  3. Streamed sounds are combined into one sound : In timeline animation, you may embed 1 or more sounds into the timeline to be streamed. What actually happens is that all the sounds in a given timeline are combined into a single blob of sound data. Then that data is split over each frame, this insures that it's synced to the frame animation. Which explains why when you decompile a SWF you can't really extract streamed sounds.
  4. SWF Encrypters are pretty useless : I've only tested a few, but they really don't do much. Each SWF is composed of a series of tags. Each tag contains a specific kind of data. So there are for instance shape tags, and image tags, etc. Well there is also an End tag, which means you've gotten to the End of the SWF. Well one of the encrypters just puts a fake End tag at the beginning for decompilers to get tripped up on. It's pretty easy to avoid and most decompilers skip over it. Others try to add custom tags to hide data. But again, most of these work to hide the ActionScript. All of the media is still easily extracted as shown by Flashbug.

General AS3 Tips

Jan 27, 2010

This is the final bit migrated from the Labs Wiki. This is just a short list of quick and easy tips to keep in mind when programming AS3.

Reuse instantiated classes

Create constants for commonly used objects, such as new Point(0,0)
// 100k loops: 75ms
new Point(0,0);

// 100k loops: 8ms
private static const POINT:Point = new Point(0,0);
POINT;

Getters

Store getter properties as local variables when using them more than once in a method (such as .graphics, .transform)
// 100k loops: 202ms
somesprite.graphics.clear();
somesprite.graphics.beginFill(0x000000);
somesprite.graphics.drawRect(0,0,10,10);
somesprite.graphics.endFill();

// 100k loops: 22ms
var n:Graphics = sprite.graphics;
n.clear();
n.beginFill(0x000000);
n.drawRect(0,0,10,10);
n.endFill();

Class Reflection

Create custom reflection methods instead of using: getDefinitionByName(getQualifiedClassName(object))
package {
	public class SomeClass {
		public function get reflect():Class {
			return SomeClass;
		}
	}
}
var someObject:SomeClass = new SomeClass();
var someClass:Class;
var i:int;

// 66ms
for(i=0; i<100000; i++) {
	someclass = getDefinitionByName(getQualifiedClassName(someObject)) as Class;
}

// 28ms
for(i=0; i<100000; i++) {
	someclass = Object(someObject).constructor;
}

// 16ms
for(i=0; i<100000; i++) {
	someclass = someObject.reflect;
}

Constants from other classes

// This takes 34.08ms to execute
var tmpVar:int;
for(var i:Number=0; i<100000; i++) {
	tmpVar = SomeClass.SOME_CONSTANT;
}

// This takes 15.8ms to execute
var tmpVar:int;
var myConstant:int = SomeClass.SOME_CONSTANT;
for(var i:Number=0; i<100000; i++) {
	tmpVar = myConstant;
}

Variable instantiation

// This takes 46.52ms to execute
for(var i:int=0; i<100000; i++) {
	var v1:Number=10;
	var v2:Number=10;
	var v3:Number=10;
	var v4:Number=10;
	var v5:Number=10;
}

// This takes 19.74ms to execute
for(var i:int=0; i<100000; i++) {
	var v1:Number=10, v2:Number=10, v3:Number=10, v4:Number=10, v5:Number=10;
}

AS3 Math Optimizations

Jan 27, 2010 This is some more stuff migrated from the Labs Wiki. Below is just some aggregated tips and tricks I've found on other blogs or websites.

Extract Colors

Not really an optimization per se, but just how to extract color values.
//24bit
var color:uint = 0x336699;
var r:uint = color >> 16;
var g:uint = color >> 8 & 0xFF;
var b:uint = color & 0xFF;

//32bit
var color:uint = 0xff336699;
var a:uint = color >>> 24;
var r:uint = color >>> 16 & 0xFF;
var g:uint = color >>>  8 & 0xFF;
var b:uint = color & 0xFF;

Combine Colors

Not really an optimization per se, but just how to combine color values.
//24bit
var r:uint = 0x33;
var g:uint = 0x66;
var b:uint = 0x99;
var color:uint = r << 16 | g << 8 | b;

//32bit
var a:uint = 0xff;
var r:uint = 0x33;
var g:uint = 0x66;
var b:uint = 0x99;
var color:uint = a << 24 | r << 16 | g << 8 | b;

Modulo

If the divisor is a power of 2, the modulo (%) operation can be done with: modulus = numerator & (divisor - 1); This is about 600% faster. Ref
	//Slower
	x = 131 % 4;

	//Faster
	x = 131 & (4 - 1);

Check if Even

	//Slower
	isEven = (i % 2) == 0;

	//Faster
	isEven = (i & 1) == 0;

Division

For bit shifting, use any power of two. Also keep in mind that bit shifting only works with integers, not floats/numbers.
	//Slowest 152ms
	var n:Number = value / 2; // Divide by 2
	var n:Number = value / 4; // Divide by 4

	//Faster 112ms
	var n:Number = value * .5; // Divide by 2
	var n:Number = value * .25; // Divide by 4

	//Fastest 63ms
	var n:Number = value >> 1; // Divide by 2
	var n:Number = value >> 2; // Divide by 4

Multiplication

For bit shifting, use any power of two. Also keep in mind that bit shifting only works with integers, not floats/numbers.
	//Slower
	var n:Number = value * 2; // Multiply by 2
	var n:Number = value * 4; // Multiply by 4

	//Fastest
	var n:Number = value << 1; // Multiply by 2
	var n:Number = value << 2; // Multiply by 4

Math.floor

	//Slower 1733ms
	var test:Number = Math.floor(1.5);

	//Faster 157ms
	var test:int = int(1.5);

	//Fastest 145ms
	var test:int = 1.5 >> 0;

Math.ceil

	// Slower 1624ms
	var test:Number = Math.ceil(1.5);

	// Faster 440ms
	var n:Number = 1.5;
	var test:int = int(n+1) + (n >> 31);

	// Even faster but inaccurate
	var test:int = int(n) + 1;

	// Also faster but inaccurate
	var test:int = n == int(n) ? n : int(n) + 1;

Math.abs

Keep in mind that bit shifting only works with integers, not floats/numbers. Ref
	// Slower 1572ms
	var nn:Number = -23;
	nn = Math.abs(nn);

	// Faster 76ms
	var nn:Number = -23;
	if (nn < 0) nn = -nn;

	// Faster by about 20%
	var x:Number = -23;
	var nn:Number = (x ^ (x >> 31)) - (x >> 31);

Math.sqrt

This uses the Babylonian method of approximating a square root. On Flash 10 there is still about a 20% performance gain if you really need it. But be sure to use this code INLINE and not as a separate function or all the performance gain will be lost. Ref
//--Using the Babylonian Method for computing square roots efficiently requires making the initial fast
// approximation as accurate as possible, insuring that the number of iterations can be minimized while
// perserving a desired level of accuracy.
//
var apprxInit:int;
var apprxRoot:Number;
var initVal:Number;
var invVal:Boolean;

if (sqNum < 1) {
	initVal = 1 / sqNum;
	invVal = true;
} else {
	initVal = sqNum;
	invVal = false;
}
apprxInit = initVal;

//--Make first approximation of APPRXROOT:  Set the starting approximation APPRXINIT to determine
// its binary degree; in this case, the location of its most significant bit to determine its length
// in bits.
//  In AS3, the most efficient method for accomplishing this is by comparing it to integers equal to odd
// powers of 2, then shifting it right half the number of bits of its length.  This unfortunately results
// in rather unseemly code.  The final treatment is one iteration of the Babylonian Method.  "Hey, Morf
// said it's fast and it works"
//   Well, yes... but this MUST BE USED AS INLINE CODE FOR ANY SPEED OPTIMIZATION TO BE REALIZED.
//
if (apprxInit > 7) {
	if (apprxInit < 32768) {
		if (apprxInit < 128) {
			if (apprxInit < 32) {
				apprxInit >>= 2;
				if (apprxInit < 4) apprxInit++;
			} else {
				apprxInit >>= 3;
			}
		} else {
			if (apprxInit < 2048) {
				if (apprxInit < 512) {
					apprxInit >>= 4;
				} else {
					apprxInit >>= 5;
				}
			} else {
				if (apprxInit < 8096) {
					apprxInit >>= 6;
				} else {
					apprxInit >>= 7;
				}
			}
		}
	} else {
		if (apprxInit < 8388608) {
			if (apprxInit < 524288) {
				if (apprxInit < 131072) {
					apprxInit >>= 8;
				} else {
					apprxInit >>= 9;
				}
			} else {
				if (apprxInit < 2097152) {
					apprxInit >>= 10;
				} else {
					apprxInit >>= 11;
				}
			}
		} else {
			if (apprxInit < 134217728) {
				if (apprxInit < 33554432) {
					apprxInit >>= 12;
				} else {
					apprxInit >>= 13;
				}
			} else {
				apprxInit >>= 14; //What are you doing with a number this big anyway?  Not bothering with yet another test.
			}
		}
	}
	apprxRoot = (apprxInit + initVal / apprxInit) * 0.5;
} else if (apprxInit < 2) {
	apprxRoot = initVal * 0.5 + 0.5;
} else {
	apprxRoot = initVal * 0.25 + 1;
}

//
//-- First approximation will be within about 1% at twice the speed and may be sufficient for collision detection computations.

//-- Now perform as many additional approximations as desired; fourth iteration = same precision as Math.sqrt().
//
apprxRoot = (apprxRoot + initVal / apprxRoot) * 0.5;	// babylonian iteration 2 -- about 20% faster.
// apprxRoot = (apprxRoot + initVal / apprxRoot) * 0.5;	// babylonian iteration 3 -- seven-digit precision.
// apprxRoot = (apprxRoot + initVal / apprxRoot) * 0.5;	// babylonian iteration 4 -- full precision.

if (invVal)  apprxRoot = 1 / apprxRoot;

// apprxRoot is now your final value

Flash 9 Gotchas/Bugs

Jan 26, 2010

This is also migrated over from labs. This was a list of bugs and gotchas I've run into over during my time as a Flash developer. I'm sure the best play to look would be Adobe's Bug tracker but it doesn't hurt to have a concise list of things to watch out for.

Bugs/ Gotches

Cross-Domain Policy

Jan 14, 2010

Below is the structure of the cross-domain policy and all the allowed values. The majority of the content was aggregated from Adobe's page on cross-domain policies.

A cross-domain policy file is an XML document that allows a web client to handle data across multiple domains. You will usually need one if the SWF is playing on Domain A and the files are located on Domain B. Policy files grant read access to data as well as permit a client to include custom headers in cross-domain requests. A policy file is also used when using Sockets during socket-based connections.

The most common location for a policy file on a server is in the root directory of a domain with the filename crossdomain.xml (e.g. http://example.com/crossdomain.xml) the default location that clients check when a policy file is required. Policy files saved this way are known as master policy files. The following is an example of a typical, permissive URL (i.e. non-socket) master policy file:

Example

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
	<site-control permitted-cross-domain-policies="master-only"/>
	<allow-access-from domain="*"/>
	<allow-http-request-headers-from domain="*" headers="SOAPAction"/>
</cross-domain-policy>

Serving Policy Files

Ideally, all URL policy files should be served with Content-Type: text/x-cross-domain-policy. This is the same content type that the by-content-type value of the permitted-cross-domain-policies attribute uses to determine validity of a policy file. The following content types are also permissible: Instead of relying entirely on master policy files for meta-policies, clients may also decide to check for a X-Permitted-Cross-Domain-Policies header in documents to specify a meta-policy. In addition to the values acceptable in permitted-cross-domain-policies, this header may also use a value of none-this-response to indicate that the current document should not be used as a policy file, despite other headers or its content.

Master Policy Files

Master policy files are policy files located in root of a domain with the filename crossdomain.xml (e.g. http://example.com/crossdomain.xml). When clients require a policy file, this is the default location for the policy file they should check. A domain should always host a master policy file to enforce its intended meta-policy. If a client is instructed to load a policy file other than the master policy file, the master policy file will still need to be checked by the client to ensure that the meta-policy defined by the master policy file (via the <site-control> element) permits the use of the originally requested policy file. Without a master policy file, it is left to the client to enforce the default behavior. Non-master policy files can only grant access to data within their own directory or within that directory's subdirectories.

Domain Matching

The following rules are used in determining if a value in the domain attribute of the allow-access-from or allow-http-request-headers-from elements matches an actual domain name: Domain matching examples

Specification

Schemas

There are a number of different schemas you can use with your cross-domain policy file. Anything but the default is usually used on a case by case basis.

cross-domain-policy tag

The <cross-domain-policy> element is the root element for cross-domain policy files. It acts as a container for policy file definitions and has no attributes of its own to define cross-domain policies. The following is the least permissive master policy file definition. It enforces a meta-policy that restricts any policy file, including this one, from granting permissions of any type to any domain:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
	<site-control permitted-cross-domain-policies="none"/>
</cross-domain-policy>
The following is the most permissive master policy file definition (strongly not recommended). It allows any policy file defined on the server of this domain to grant permissions, allows access to any file on the server, and permits any header to be sent to the server-all of this possible even through HTTPS despite the source being HTTP:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
	<site-control permitted-cross-domain-policies="all"/>
	<allow-access-from domain="*" secure="false"/>
	<allow-http-request-headers-from domain="*" headers="*" secure="false"/>
</cross-domain-policy>

The following policy file is an example of a socket-based policy that would be served to a client through a socket connection. It allows socket connections from the domain example.com, including subdomains, to ports 1100, 1200, and 1212:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
	<allow-access-from domain="*.example.com" to-ports="1100,1200,1212"/>
</cross-domain-policy>

site-control tag - [Master policy file only]

The <site-control> element defines the meta-policy for the current domain. A meta-policy decides what policy files on a domain are acceptable in the case that a client needs to use a policy file other than the domain's master policy file-the policy file located in the domain's root under the name crossdomain.xml. The <site-control> element is specific to master policy files; if used within a policy file that is not the master policy file, the <site-control> element will be ignored.

The default value is master-only for all policy files except socket policy files, where the default is all.

Though the following policy file does not allow data access for this domain, it defined a meta-policy that allows other policy files within this domain to determine how access will be handled. A client will need to load a policy file other than the master for permissions related to this domain:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
	<site-control permitted-cross-domain-policies="all"/>
</cross-domain-policy>

The following defines a meta-policy that allows only this master policy file to function for this domain. It allows access to data on example.com and all of its subdomains:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
	<site-control permitted-cross-domain-policies="master-only"/>
	<allow-access-from domain="*.example.com"/>
</cross-domain-policy>

allow-access-from tag

The <allow-access-from> element grants another domain access to read data from the current domain. For each individual domain that is given permission, a new <allow-access-from> element is required, although multiple domains can be given access with one <allow-access-from> element by using a wildcard (*).

Note: An allow-access-from element in a non-master policy file can only grant another domain access to dat within its own directory and subdirectories, not a directory in a higher level of that domain.

The following policy file demonstrates the most permissive use of allow-access-from granting any other domain access to the files on this domain, even if an HTTP source is accessing data on this domain through HTTPS:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
	<allow-access-from domain="*" secure="false"/>
</cross-domain-policy>

Here the policy file allows access to example.com in both uses of the root domain, with and without the www

subdomain:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
	<allow-access-from domain="example.com"/>
	<allow-access-from domain="www.example.com"/>
</cross-domain-policy>

The following is a socket policy file example. This XML should be served to a client through a socket connection when requested with policy-file-request. It permits access to content from example.com or any of its subdomains through ports 507 and any port between 516 and 523, inclusive:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
	<allow-access-from domain="*.example.com" to-ports="507,516-523"/>
</cross-domain-policy>

allow-http-request-headers-from tag

The <allow-http-request-headers-from> element grants a client hosting content from another domain to send user-defined headers to the current domain (see Figure 4). Whereas the <allow-access-from> element grants permission to pull data from the current domain, this tag grants permission to push data-data in the form of headers.

Note: An allow-http-request-headers-from element in a non-master policy file can only allow headers to be sent to pages within the directory in which the policy file is defined and that directory's subdirectories.

Here, any domain is allowed to send the SOAPAction header to this domain:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
	<allow-http-request-headers-from domain="*" headers="SOAPAction"/>
</cross-domain-policy>

The following policy file allows the Authorization header and any header beginning with the characters X-Foo from www.example.com to be sent to this domain. If a request is coming from foo.example.com, only headers beginning with the characters X-Foo are allowed, not Authorization:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
	<allow-http-request-headers-from domain="www.example.com" headers="Authorization,X-Foo*"/>
	<allow-http-request-headers-from domain="foo.example.com" headers="X-Foo*"/>
</cross-domain-policy>

policy-file-request tag

The policy-file-request element is not specific to policy file documents. Rather, policy-file-request is the root element of a single-node XML document used by a client to request policy file information from a socket server. Upon reception of this document, a socket server should provide the client with the necessary policy file so that the client can continue with the connection or close it if the policy file does not permit it:

<policy-file-request/>

Policy files for sockets should always be socket-based. URL policy files from an HTTP source should not be used to provide permissions to socket connections.

Note: Adobe Flash Player allowed HTTP policy files to work for socket connections prior to version 9,0,124,0.

Flash Version Support

Adobe Flash Player has supported the use of cross-domain policy files since Flash Player 6. Certain policy file features weren't implemented until later versions of the player.

Loop/Statement Labels

Jan 13, 2010

Below are some notes from Christian Cantrell's blog that I just wanted to aggregate onto my site for my own personal reference. I am not claiming this as my own content, but I've been meaning to add this to my notes and Christian did a great job explaining it.

Labels are a relatively unknown feature in AS3. Basically what it does is give an ID to a specific loop or statement. So when you call break or continue, you can specify WHICH loop to break or continue.

Below is copied verbatim from Christian's site.

"Consider the following piece of code which compares two arrays to find which elements exist in outerArray that do not exist in innerArray:

var outerArray:Array = ["coffee", "juice", "water", "beer"];
var innerArray:Array = ["liquor", "beer", "wine", "juice"];

for each (var foo:String in outerArray) {
    var found:Boolean = false;
    for each (var bar:String in innerArray) {
        if (foo == bar) found = true;
    }
    if (!found) trace(foo);
}

(Note: I know there are better ways of doing this particular comparison; this technique is for demonstration purposes only.)

The same code could be written using labels like this:

var outerArray:Array = ["coffee", "juice", "water", "beer"];
var innerArray:Array = ["liquor", "beer", "wine", "juice"];

outerLoop: for (var i:uint = 0; i < outerArray.length; ++i) {
    var foo:String = outerArray[i];
    innerLoop: for (var j:uint = 0; j < innerArray.length; ++j) {
        var bar:String = innerArray[j];
        if (foo == bar) continue outerLoop;
    }
    trace(foo);
}

Notice how the labels outerLoop and innerLoop precede my for loops, and how I can reference the outerLoop label in my continue statement. Without specifying a label, continue would have continued from the top of innerLoop rather than outerLoop.

Labels will also work with break statements, and can even be used to create their own blocks as in the following example:

dateCheck: {
    trace("Good morning.");
    var today:Date = new Date();
    if (today.month == 11 && today.date == 25)  break dateCheck; // Christmas!
    trace("Time for work!");
}

Some things to keep in mind about labels:

Loop Optimizations 2

Sep 8, 2009

This is kind of an extension of my previous loop tests I did. I ran into this while making improvements to Orion. I was really surprised there was this much of a difference in speed. The discovery I made after looking into BetweenAS3 and the efforts of Joa Ebert on his AS3 particles I noticed they were using LinkedLists to manage large numbers of objects. LinkedLists are only good if the you don't have to remove items from the list as that can be a painful experience. But adding to and iterating through the list is blazing fast as denoted below.

Note: The only difference between v1 and v2 of each test is that I was testing the speed of a while loop versus a do...while loop.

Each run is over 1 million items, and the times are an average of the 5 runs.
linkedListTest Create: 886.4ms Iterate: 20ms
linkedListTest2 Create: 893.8ms Iterate: 20.2ms
vectorTest Create: 973.2ms Iterate: 468.4ms
vectorTest2 Create: 944ms Iterate: 379.6ms
arrayTest Create: 985.4ms Iterate: 407.4ms
arrayTest2 Create: 997.2ms Iterate: 433ms
dictionaryTest Create: 991.4ms Iterate: 410.2ms
dictionaryTest2 Create: 974.4ms Iterate: 457.2ms
package {
	
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.utils.getTimer;
	import flash.utils.Dictionary;
	
	public class LoopTest2 extends MovieClip {
		
		private var _maxItems:uint = 1000000;
		private var createTimes:Array = [0, 0, 0, 0, 0];
		private var iterateTimes:Array = [0, 0, 0, 0, 0];
		
		public function LoopTest2() {
			// linkedListTest Create: 886.4ms / Iterate: 20ms - 5 Runs
			//runTest(linkedListTest, "linkedListTest", 5);
			
			// linkedListTest2 Create: 893.8ms / Iterate: 20.2ms - 5 Runs
			//runTest(linkedListTest2, "linkedListTest2", 5);
			
			// vectorTest Create: 973.2ms / Iterate: 468.4ms - 5 Runs
			//runTest(vectorTest, "vectorTest", 5);
			
			// vectorTest2 Create: 944ms / Iterate: 379.6ms - 5 Runs
			//runTest(vectorTest2, "vectorTest2", 5);
			
			// arrayTest Create: 985.4ms / Iterate: 407.4ms - 5 Runs
			//runTest(arrayTest, "arrayTest", 5);
			
			// arrayTest2 Create: 997.2ms / Iterate: 433ms - 5 Runs
			//runTest(arrayTest2, "arrayTest2", 5);
			
			// dictionaryTest Create: 991.4ms / Iterate: 410.2ms - 5 Runs
			//runTest(dictionaryTest, "dictionaryTest", 5);
			
			// dictionaryTest2 Create: 974.4ms / Iterate: 457.2ms - 5 Runs
			//runTest(dictionaryTest2, "dictionaryTest2", 5);
		}
		
		public function runTest(f:Function, name:String, runs:int = 5):void {
			var i:int = runs;
			while (--i > -1) {
				f(i);
			}
			
			trace(name + " Create: " + average(createTimes) + "ms / Iterate: " + average(iterateTimes) + "ms - " + runs + " Runs");
		}
		
		public function average(arr:Array):Number {
			return (arr[0] + arr[1] + arr[2] + arr[3] + arr[4]) / 5;
		}
		
		public function linkedListTest(index:int):void {
			// Create
			var ts:Number = getTimer();
			var _items:MyItem = new MyItem();
			var currentItem:MyItem = _items;
			var i:int = _maxItems;
			while (--i != 0) {
				currentItem = currentItem.next = new MyItem();
			}
			createTimes[index] = getTimer() - ts;
			
			// Iterate
			ts = getTimer();
			currentItem = _items;
			do {
				currentItem.data += 1;
				currentItem = currentItem.next;
			} while (currentItem);
			iterateTimes[index] = getTimer() - ts;
		}
		
		public function linkedListTest2(index:int):void {
			// Create
			var ts:Number = getTimer();
			var _items:MyItem = new MyItem();
			var currentItem:MyItem = _items;
			var i:int = _maxItems;
			while (--i != 0) {
				currentItem = currentItem.next = new MyItem();
			}
			createTimes[index] = getTimer() - ts;
			
			// Iterate
			ts = getTimer();
			currentItem = _items;
			while (currentItem) {
				currentItem.data += 1;
				currentItem = currentItem.next;
			}
			iterateTimes[index] = getTimer() - ts;
		}
		
		public function vectorTest(index:int):void {
			// Create
			var ts:Number = getTimer();
			var _items:Vector. = new Vector.(_maxItems, true);
			var i:int = _maxItems;
			while (--i) {
				_items[i] = new MyItem();
			}
			createTimes[index] = getTimer() - ts;
			
			// Iterate
			ts = getTimer();
			i = _maxItems - 1;
			do {
				_items[i].data += 1;
			} while (--i);
			iterateTimes[index] = getTimer() - ts;
		}
		
		public function vectorTest2(index:int):void {
			// Create
			var ts:Number = getTimer();
			var _items:Vector. = new Vector.(_maxItems, true);
			var i:int = _maxItems;
			while (--i) {
				_items[i] = new MyItem();
			}
			createTimes[index] = getTimer() - ts;
			
			// Iterate
			ts = getTimer();
			i = _maxItems - 1;
			while (--i) {
				_items[i].data += 1;
			}
			iterateTimes[index] = getTimer() - ts;
		}
		
		public function arrayTest(index:int):void {
			// Create
			var ts:Number = getTimer();
			var _items:Array = new Array(_maxItems);
			var i:int = _maxItems;
			while (--i) {
				_items[i] = new MyItem();
			}
			createTimes[index] = getTimer() - ts;
			
			// Iterate
			ts = getTimer();
			i = _maxItems - 1;
			do {
				_items[i].data += 1;
			} while (--i);
			iterateTimes[index] = getTimer() - ts;
		}
		
		public function arrayTest2(index:int):void {
			// Create
			var ts:Number = getTimer();
			var _items:Array = new Array(_maxItems);
			var i:int = _maxItems;
			while (--i) {
				_items[i] = new MyItem();
			}
			createTimes[index] = getTimer() - ts;
			
			// Iterate
			ts = getTimer();
			i = _maxItems - 1;
			while (--i) {
				_items[i].data += 1;
			}
			iterateTimes[index] = getTimer() - ts;
		}
		
		public function dictionaryTest(index:int):void {
			// Create
			var ts:Number = getTimer();
			var _items:Dictionary = new Dictionary();
			var i:int = _maxItems;
			while (--i) {
				_items[i] = new MyItem();
			}
			createTimes[index] = getTimer() - ts;
			
			// Iterate
			ts = getTimer();
			i = _maxItems - 1;
			do {
				_items[i].data += 1;
			} while (--i);
			iterateTimes[index] = getTimer() - ts;
		}
		
		public function dictionaryTest2(index:int):void {
			// Create
			var ts:Number = getTimer();
			var _items:Dictionary = new Dictionary();
			var i:int = _maxItems;
			while (--i) {
				_items[i] = new MyItem();
			}
			createTimes[index] = getTimer() - ts;
			
			// Iterate
			ts = getTimer();
			i = _maxItems - 1;
			while(--i) {
				_items[i].data += 1;
			}
			iterateTimes[index] = getTimer() - ts;
		}
	}
}


class MyItem {
	
	public var data:Number;
	
	public var next:MyItem;
	
	public function MyItem() {
		data = Math.random();
	}
}

Positioning Optimizations

Apr 7, 2009

This page sort of expands upon the previous post I made on loop optimizations. I'm going to use this post in the future as a catchall for any other optimizations I run across. So for now I just have one to list, but hopefully in the future I'll add some more.

Edit: (Sept 8th 2009) Ok I was kidding, I think I'm going to make separate posts each time. That way it shows up as a new post and people actually notice it. The "optimization" tag can be used to filter out just these posts.

Modifying a DisplayObject

Using a Matrix to modify a DisplayObject is faster than editing the individual properties.

// 1 Million runs 2578ms, 2583ms, 2572ms
spr.transform.matrix = new Matrix(spr.scaleX * 0.98, spr.scaleY * 0.98, 0, 0, spr.x + pt.x, spr.y + pt.y);

is faster than

// 1 Million runs 3597ms, 3609ms, 3569ms
spr.scaleX *= 0.98;
spr.scaleY *= 0.98;
spr.x += pt.x;
spr.y += pt.y;

AIR

Apr 7, 2009

Some notes I've gathered over time in regards to AIR. As per usual, just posted up here for future reference.

Multiple Monitors

When building an app for multiple monitors, be sure to make use of the flash.display.Screen class. This class lets you iterate through all of the monitors available with access to the size and resolution of each. It also can tell you which is the primary monitor. Below is a useful function to grab the current screen.

private function getCurrentScreen():Screen {
	return Screen.getScreensForRectangle(stage.nativeWindow.bounds)[0];
}

Closing App

To close an app gracefully try to use the Event.EXITING event from NativeApplication.nativeApplication. Or for a window, the Event.CLOSING event from stage.nativeWindow. Both of these dispatch events before closing giving you the opportunity to close any open connections or even animate a closing animation. All code that you execute from these events are processed synchronously.

Below is some code that will stop the application from closing, then close any open windows. If you have NativeApplication.autoExit set to true, this will eventually close your application as well. If you leave any windows open when closing your app, it will not actually exit.

this.nativeWindow.addEventListener(Event.CLOSING,
function(e:Event):void { 
	e.preventDefault();
	for (var i:int = NativeApplication.nativeApplication.openedWindows.length - 1; i >= 0; --i) { 
		NativeWindow(NativeApplication.nativeApplication.openedWindows[i]).close();
	} 
});

Starting App

To start your app at computer startup (when a user logs in), set this property to true.

NativeApplication.nativeApplication.startAtLogin = true;

PDF

Detect Capability
import flash.html.HTMLPDFCapability;
if(HTMLLoader.pdfCapability == HTMLPDFCapability.STATUS_OK) {
	// Yes
}
Limitations

HTML

Flash
AIR
AIR WebKit versions

Clipboard

Formats
Get Data
import flash.desktop.Clipboard;
import flash.desktop.ClipboardFormats;
Clipboard.generalClipboard.getData(ClipboardFormats.BITMAP_FORMAT);
Set Data

Setting data to the clipboard AT paste time (in a copy paste operation) :

import flash.desktop.Clipboard;
import flash.desktop.ClipboardFormats;
function copyLater(event:MouseEvent):void {
	// getBitmapdata() just returns the data you want set in this example
	Clipboard.generalClipboard.setDataHandler(ClipboardFormats.BITMAP_FORMAT, getBitmapdata());
}

Dragging Items Out

To drag items out, pass an instance of the clipboard class and add whatever data you want to be transferred to it. Then pass that instance to the doDrag method. Optionally, create a BitmapData object to serve as a proxy image during the drag.

import flash.desktop.Clipboard;
import flash.desktop.NativeDragManager;
var cb:Clipboard = new Clipboard();
cb.setData(ClipboardFormats.BITMAP_FORMAT, bitmapdata);
NativeDragManager.doDrag(dragInitiator:InteractiveObject, cb, bitmapdata); // bitmap data for use when dragging

Files

File Type Associations

To set file type associations you can set it in the application descriptor. If a different program is set as the primary program for that extensions you can check to see if your program is set for it.

var isSet:Boolean = NativeApplication.nativeApplication.isSetAsDefaultApplication("mp4");

If it isn't you can reset the associations with the code below. But you can only be used with file types declared in the fileTypes statement in the application descriptor.

NativeApplication.nativeApplication.setAsDefaultApplication("mp4");
Copy a File
var f:File = new File();
f.addEventListener(Event.SELECT, onCopySelect);
f.browseForSave("Copy File To");

funciton onCopySelect(event:Event):void {
	var f:File = event.target as File;
	file.copyToAsync(f);
}
Delete a File
file.addEventListener(Event.COMPLETE, onFileActionComplete);
file.deleteFileAsync();

function onFileActionComplete(event:Event):void {
	// do something
}
Move File to Trash
file.addEventListener(Event.COMPLETE, onFileActionComplete);
file.moveToTrashAsync();

function onFileActionComplete(event:Event):void {
	// do something
}
Write a File

This code will save a prefs.xml to the application's directory.

var fs:FileStream = new FileStream();
fs.addEventListener(Event.COMPLETE, onFileStreamComplete);
fs.openAsync(new File("app:/prefs.xml"), FileMode.WRITE);
var filedata:String = '';	
filedata += "" + curDir.nativePath;
filedata += "";
fs.writeUTFBytes(filedata);

function onFileStreamComplete(event:Event):void {
	var fs:FileStream = event.target as FileStream;
	fs.close();
}
Write a Temp File

Makes a uniquely named temp file.

fs.openAsync(new File().createTempFile(), FileMode.WRITE);

Encrypted Local Store

The encrypted local store is a unique feature that is not saved as a file. It also is limited to 10mb of data per application. You can save any secure data here safely.

import flash.data.EncryptedLocalStore;
import flash.utils.ByteArray;

//Setting data
var ba:ByteArray = new ByteArray();
ba.writeUTFBytes("password");
EncryptedLocalStore.setItem(username.text, ba);

//Getting data
var ba:ByteArray = EncryptedLocalStore.getItem(username.text);
if(ba) {
	var dataPW:String = ba.readUTFBytes(ba.bytesAvailable);
	if(password.text == dataPW) {
		// login
	} else {
		// wrong password
	}
} else {
	// No data found
}

Service Monitor

When using the Service Monitor Framework, it requires that the ServiceMonitorShim component be in your FLA's library.

Loop Optimizations

Jan 27, 2009

Here is some testing I did to determine the best kind of loop to use in AS3. In the end the fastest I determined was a simple reverse while loop. Below is the results with the code for my testing beneath that. Where I could, I tried to run 1 million items in the collection. If I was unable to do that (the program would crash) I would run 500k items. From what I have learned, using the Array.forEach() is kinda expensive for what it does. Standard for() loops aren't too bad, but the for...in() loops are even worse than the Array.forEach(). If you know of anything faster methods or maybe I tested incorrectly please let me know!


Standard Loops (1 Million runs)
test00(); for() [uint inline & list.length] 198ms, 204ms, 204ms
test0(); for() [uint & list.length] 150ms, 157ms, 157ms
test1(); for() [uint & l] 149ms, 140ms, 145ms
test2(); for() [uint & l reverse] 149ms, 140ms, 145ms
test3(); for each in() 160ms, 163ms, 164ms
test4(); for in() 583ms, 585ms, 586ms
test5(); while() [i < l] 139ms, 145ms, 142ms
test6(); while() [i--] 139ms, 144ms, 143ms
test7(); while() [i-- > 0] 152ms, 154ms, 154ms
test8(); do while() 234ms, 236ms, 237ms
test18(); Array.forEach() 324ms, 326ms, 321ms

Dictionary Loops (500k runs)
test9(); Dictionary for each in() [local vars] 90ms, 89ms, 90ms
test10(); Dictionary for each in() 87ms, 88ms, 88ms
test11(); Dictionary for each in() [inline] 88ms, 88ms, 87ms

Object Loops (500k runs)
test12(); Object for each in() [local vars] 93ms, 90ms, 92ms
test13(); Object for each in() 96ms, 93ms, 90ms
test14(); Object for each in() [inline] 98ms, 94ms, 91ms

Dictionary Loops with 'length' variable (1 Million and 500k runs)
test15(); Dictionary while() [i < l] 1M 144ms, 142ms, 136ms / 500k 72ms, 70ms, 71ms
test16(); Dictionary while() [i--] 1M 138ms, 138ms, 141ms / 500k 74ms, 70ms, 70ms
test17(); Dictionary while() [i-- > 0] 1M 153ms, 147ms, 149ms / 500k 74ms, 74ms, 75ms

package {
	
	import flash.display.MovieClip;
	import flash.utils.getTimer;
	import flash.utils.Dictionary;
	
	public class LoopTest extends MovieClip {
		private var l:int;
		private var list:Array = new Array();
		private var dict:Dictionary = new Dictionary();
		private var obj:Object = new Object();
		
		public function LoopTest() {
			var i:uint;
			
			//Create the source array
			for(i = 0; i < 1000000; i++) {
				list.push(Math.random());
			}
			l = list.length;
			trace("List of " + l + " elements.");
			
			// Create the source dictionary
			/*for (i = 0; i < 500000; i++) {
				var n:Number = Math.random();
				dict[n] = n;
			}*/
			
			// Create the source object
			/*for (i = 0; i < 500000; i++) {
				var n:Number = Math.random();
				obj[n] = n;
			}*/
			
			// Create the source dictionary 2
			/*for (i = 0; i <= 500000; i++) {
				dict[i] = Math.random();
				dict["length"] = i;
			}
			trace("Dictionary of " + dict["length"] + " elements.");*/
		}
		
		public function test000():void {
			var ts:Number = getTimer();
			for(var i:Number = 0; i < list.length; i++) {
				var z:Number = Math.random();
			}
			trace("for() [Number & list.length]: " + (getTimer() - ts) + "ms");
		}
		
		public function test00():void {
			var ts:Number = getTimer();
			for(var i:uint = 0; i < list.length; i++) {
				var z:Number = Math.random();
			}
			trace("for() [uint inline & list.length]: " + (getTimer() - ts) + "ms");
		}
		
		public function test0():void {
			var ts:Number = getTimer();
			var i:uint;
			for(i = 0; i < list.length; i++) {
				var z:Number = Math.random();
			}
			trace("for() [uint & list.length]: " + (getTimer() - ts) + "ms");
		}
		
		public function test1():void {
			var ts:Number = getTimer();
			var i:uint;
			for(i = 0; i < l; i++) {
				var z:Number = Math.random();
			}
			trace("for() [uint & l]: " + (getTimer() - ts) + "ms");
		}
		
		public function test2():void {
			var ts:Number = getTimer();
			var i:int;
			var l2:int = l - 1;
			for(i = l2; i >= 0; i--) {
				var z:Number = Math.random();
			}
			trace("for() [uint & l reverse]: " + (getTimer() - ts) + "ms");
		}
		
		public function test3():void {
			var ts:Number = getTimer();
			var j:Number;
			for each(j in list) {
				var z:Number = Math.random();
			}
			trace("for each in(): " + (getTimer() - ts) + "ms");
		}
		
		public function test4():void {
			var ts:Number = getTimer();
			var m:String;
			for (m in list) {
				var z:Number = Math.random();
			}
			trace("for in(): " + (getTimer() - ts) + "ms");
		}
		
		public function test5():void {
			var ts:Number = getTimer();
			var i:int = 0;
			while(i < l) {
				var z:Number = Math.random();
				i++;
			}
			trace("while() [i < l]: "+(getTimer()-ts)+"ms");
		}
		
		public function test6():void {
			var ts:Number = getTimer();
			var i:int = l - 1; 
			while(i--) {
				var z:Number = Math.random();
			}
			trace("while() [i--]: "+(getTimer()-ts)+"ms");
		}
		
		public function test7():void {
			var ts:Number = getTimer();
			var i:uint = l;
			while( i-- > 0 ){
				var z:Number = Math.random();
			}
			trace("while() [i-- > 0]: "+(getTimer()-ts)+"ms");
		}
		
		public function test8():void {
			var ts:Number = getTimer();
			var i:int = l - 1; 
			do {
				var z:Number = Math.random();
			} while (i--);
			trace("do while(): "+(getTimer()-ts)+"ms");
		}
		
		public function test18():void {
			var ts:Number = getTimer();
			list.forEach(doRandom);
			trace("Array.forEach(): "+(getTimer()-ts)+"ms");
		}
		
		private function doRandom(item:*, index:int, arr:Array):void {
            var z:Number = Math.random();
        }
		
		// Dictionary Tests
		
		public function test9():void {
			var ts:Number = getTimer();
			var d:Dictionary = dict, e:Object;
			for each (e in d) {
				var z:Number = Math.random();
			}
			trace("Dictionary for each in() [local vars]: "+(getTimer()-ts)+"ms");
		}
		
		public function test10():void {
			var ts:Number = getTimer();
			var e:Object;
			for each (e in dict) {
				var z:Number = Math.random();
			}
			trace("Dictionary for each in(): "+(getTimer()-ts)+"ms");
		}
		
		public function test11():void {
			var ts:Number = getTimer();
			for each (var e:Object in dict) {
				var z:Number = Math.random();
			}
			trace("Dictionary for each in() [inline]: "+(getTimer()-ts)+"ms");
		}
		
		// Object Tests
		
		public function test12():void {
			var ts:Number = getTimer();
			var o:Object = obj, e:Object;
			for each (e in o) {
				var z:Number = Math.random();
			}
			trace("Object for each in() [local vars]: "+(getTimer()-ts)+"ms");
		}
		
		public function test13():void {
			var ts:Number = getTimer();
			var e:Object;
			for each (e in obj) {
				var z:Number = Math.random();
			}
			trace("Object for each in(): "+(getTimer()-ts)+"ms");
		}
		
		public function test14():void {
			var ts:Number = getTimer();
			for each (var e:Object in obj) {
				var z:Number = Math.random();
			}
			trace("Object for each in() [inline]: "+(getTimer()-ts)+"ms");
		}
		
		// Dictionary Tests 2
		
		public function test15():void {
			var ts:Number = getTimer();
			var i:int = 0;
			var l:int = dict["length"];
			while(i < l) {
				var z:Number = Math.random();
				i++;
			}
			trace("dictionary while() [i < l]: "+(getTimer()-ts)+"ms");
		}
		
		public function test16():void {
			var ts:Number = getTimer();
			var i:int = dict["length"] - 1; 
			while(i--) {
				var z:Number = Math.random();
			}
			trace("dictionary while() [i--]: "+(getTimer()-ts)+"ms");
		}
		
		public function test17():void {
			var ts:Number = getTimer();
			var i:uint = dict["length"];
			while( i-- > 0 ){
				var z:Number = Math.random();
			}
			trace("dictionary while() [i-- > 0]: "+(getTimer()-ts)+"ms");
		}
	}
}

CSS StyleSheets

Jan 16, 2009

Note: A text field with a style sheet is not editable. In other words, a text field with the type property set to TextFieldType.INPUT applies the StyleSheet to the default text for the text field, but the content will no longer be editable by the user. Consider using the TextFormat class to assign styles to input text fields.

Flash Player supports a subset of properties in the original CSS1 specification (www.w3.org/TR/REC-CSS1). The following table shows the supported Cascading Style Sheet (CSS) properties and values, as well as their corresponding ActionScript property names. (Each ActionScript property name is derived from the corresponding CSS property name; if the name contains a hyphen, the hyphen is omitted and the subsequent character is capitalized.)

Example 1

var style:StyleSheet = new StyleSheet();

var link:Object = new Object();
link.color = "#FF0000";

var hover:Object = new Object();
hover.fontWeight = "bold";

var active:Object = new Object();
active.color = "#00FF00";

style.setStyle("a:link", link);
style.setStyle("a:hover", hover);
style.setStyle("a:active", active);

var txt:TextField = new TextField();
txt.width = 400;
txt.styleSheet = style;
txt.htmlText = "Visit: <a href='http://www.google.com' target='_blank'><u>Google.com</u></a>";
addChild(txt);

Example 2

var style:StyleSheet = new StyleSheet();
style.parseCSS("a:link { color: #FF0000; } a:hover { font-weight: bold; } a:active { color: #00FF00; }");

var txt:TextField = new TextField();
txt.width = 400;
txt.styleSheet = style;
txt.htmlText = "Visit: <a href='http://www.google.com' target='_blank'><u>Google.com</u></a>";
addChild(txt);

color (css: color)

Only hexadecimal color values are supported. Named colors (such as blue) are not supported. Colors are written in the following format: #FF0000.

display (css: display)

Supported values are inline, block, and none.

fontFamily (css: font-family)

A comma-separated list of fonts to use, in descending order of desirability. Any font family name can be used. If you specify a generic font name, it is converted to an appropriate device font. The following font conversions are available: mono is converted to _typewriter, sans-serif is converted to _sans, and serif is converted to _serif.

fontSize (css: font-size)

Only the numeric part of the value is used. Units (px, pt) are not parsed; pixels and points are equivalent.

fontStyle (css: font-style)

Recognized values are normal and italic.

fontWeight (css: font-weight)

Recognized values are normal and bold.

kerning (css: kerning)

Recognized values are true and false. Kerning is supported for embedded fonts only. Certain fonts, such as Courier New, do not support kerning. The kerning property is only supported in SWF files created in Windows, not in SWF files created on the Macintosh. However, these SWF files can be played in non-Windows versions of Flash Player and the kerning still applies.

leading (css: leading)

The amount of space that is uniformly distributed between lines. The value specifies the number of pixels that are added after each line. A negative value condenses the space between lines. Only the numeric part of the value is used. Units (px, pt) are not parsed; pixels and points are equivalent.

letterSpacing (css: letter-spacing)

The amount of space that is uniformly distributed between characters. The value specifies the number of pixels that are added after each character. A negative value condenses the space between characters. Only the numeric part of the value is used. Units (px, pt) are not parsed; pixels and points are equivalent.

marginLeft (css: margin-left)

Only the numeric part of the value is used. Units (px, pt) are not parsed; pixels and points are equivalent.

marginRight (css: margin-right)

Only the numeric part of the value is used. Units (px, pt) are not parsed; pixels and points are equivalent.

textAlign (css: text-align)

Recognized values are left, center, right, and justify.

textDecoration (css: text-decoration)

Recognized values are none and underline.

textIndent (css: text-indent)

Only the numeric part of the value is used. Units (px, pt) are not parsed; pixels and points are equivalent.

HTMLText

Jan 16, 2009

This is the first of hopefully a series of posts of useful things I want accessible on my blog. Mostly for my own benefit but anyone else is free to use this as an easy reference for AS3.

Flash Player supports the following HTML tags:

Anchor tag

The <a> tag creates a hypertext link and supports the following attributes:

var style:StyleSheet = new StyleSheet();

var link:Object = new Object();
link.fontWeight = "bold";
link.color = "#FF0000";

var hover:Object = new Object();
hover.fontStyle = "italic";

var active:Object = new Object();
active.fontStyle = "italic";
active.color = "#FFFF00";

style.setStyle("a:link", link);
style.setStyle("a:hover", hover);
style.setStyle("a:active", active);

var txt:TextField = new TextField();
txt.width = 400;
txt.styleSheet = style;
txt.htmlText = "Visit: <a href='www.google.com' target='_blank'>Google.com</a> or call a <a href='event:link1'>function</a>";
txt.addEventListener(TextEvent.LINK, linkHandler);
addChild(txt);

function linkHandler(event:TextEvent):void {
	if(event.text == "link1") {
		trace("hello!");
	}
}

Bold tag

The <b> tag renders text as bold. A bold typeface must be available for the font used.

var txt:TextField = new TextField();
txt.htmlText = "<b>Hello World</b>";
addChild(txt);

Break tag

The <br> tag creates a line break in the text field. You must set the text field to be a multiline text field to use this tag.

var txt:TextField = new TextField();
txt.multiline = true;
txt.htmlText = "Hello<br/>World";
addChild(txt);

Font tag

The <font> tag specifies a font or list of fonts to display the text.The font tag supports the following attributes:

var txt:TextField = new TextField();
txt.htmlText = "<font color='#FF0000' face='Times New Roman, Times, _sans' size='+5'>Hello World</font>";
addChild(txt);

Image tag

The <img> tag lets you embed external image files (JPEG, GIF, PNG), SWF files, and movie clips inside text fields. Text automatically flows around images you embed in text fields. To use this tag, you must set the text field to be multiline and to wrap text.

Note: (April 2, 2009) To get the height of a textfield that has images loaded in correctly, you must specify the height and width of the image inside the <img> tag. Otherwise you won't have access to the sizes til after the images have loaded. [Ref]

Note: (April 2, 2009) You cannot nest an <img> tag within an <a> tag. [Ref]

Note: (April 2, 2009) When you use an <img> tag in a text field to load an external file (as opposed to using a Bitmap class embedded within your SWF), a Loader object is automatically created as a child of the TextField object, and the external file is loaded into that Loader just as if you had used a Loader object in ActionScript to load the file. In this case, the getImageReference() method returns the Loader that was automatically created. No security check is needed to access this Loader object because it is in the same security sandbox as the calling code.

However, when you refer to the content property of the Loader object to access the loaded media, security rules apply. If the content is an image, you need to implement a cross-domain policy file, and if the content is a SWF file, you need to have the code in the SWF file call the allowDomain() method. [Ref]

Note: (April 2, 2009) To use bitmap smoothing on a loaded image you must add a listener for the image. Once loaded, apply the smoothing. [Ref]

var loader:Loader = textfield.getImageReference("image");
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onHtmlImageLoaded);

private function onHtmlImageLoaded(event:Event):void {
	event.target.removeEventListener(Event.COMPLETE, onHtmlImageLoaded);
	Bitmap(event.target.content).smoothing = true;	
}

The tag supports the following attributes:

Flash displays media embedded in a text field at full size. To specify the dimensions of the media you are embedding, use the tag's height and width attributes.

In general, an image embedded in a text field appears on the line following the tag. However, when the tag is the first character in the text field, the image appears on the first line of the text field.

var txt:TextField = new TextField();
txt.autoSize = "left";
txt.htmlText = "Here is a sample of embedded swf <img src='http://example.com/Sample.swf' width='20' height='20' align='right' hspace='4' vspace='4' id='tempolite' checkPolicyFile='true' /> and here is a sample of an embedded image <img src='http://www.google.com/intl/en_ALL/images/logo.gif' />";
addChild(txt);

Italic tag

The <i> tag displays the tagged text in italics. An italic typeface must be available for the font used.

var txt:TextField = new TextField();
txt.htmlText = "<i>Hello World</i>";
addChild(txt);

List item tag

The <li> tag places a bullet in front of the text that it encloses.

Note: Because Flash Player does not recognize ordered and unordered list tags (<ol> and <ul>, they do not modify how your list is rendered. All lists are unordered and all list items use bullets.

var txt:TextField = new TextField();
txt.multiline = true;
txt.htmlText = "Here is a list of items:<br/><li>Item 1</li><li>Item 2</li><li>Item 3</li>";
addChild(txt);

Paragraph tag

The <p> tag creates a new paragraph. You must set the text field to be a multiline text field to use this tag.

The <p> tag supports the following attributes:

var style:StyleSheet = new StyleSheet();

var heading:Object = new Object();
heading.fontWeight = "bold";
heading.color = "#FF0000";

var p:Object = new Object();
p.fontStyle = "italic";

style.setStyle(".heading", heading);
style.setStyle("p", p);

var txt:TextField = new TextField();
txt.styleSheet = style;
txt.width = 400;
txt.htmlText = "<p align='center' class='heading'>Hello World</p>";
addChild(txt);

Span tag

The <span> tag is available only for use with CSS text styles. It supports the following attribute:

var style:StyleSheet = new StyleSheet();

var heading:Object = new Object();
heading.fontWeight = "bold";
heading.color = "#FF0000";

var body:Object = new Object();
body.fontStyle = "italic";

style.setStyle(".heading", heading);
style.setStyle("body", body);

var txt:TextField = new TextField();
txt.styleSheet = style;
txt.htmlText = "<body><span class='heading'>Hello </span>World</body>";
addChild(txt);

Text format tag

The <textformat> tag lets you use a subset of paragraph formatting properties of the TextFormat class within text fields, including line leading, indentation, margins, and tab stops. You can combine <textformat> tags with the built-in HTML tags.

The <textformat> tag has the following attributes:

var txt:TextField = new TextField();
txt.htmlText = "<textformat blockindent='10' indent='5' leading='3' leftmargin='4' rightmargin='2' tabstops='6' >Hello World</textformat>";
addChild(txt);

Underline tag

The <u> tag underlines the tagged text.

var txt:TextField = new TextField();
txt.width = 400;
txt.htmlText = "Visit: <a href='www.google.com' target='_blank'><u>Google.com</u></a>";
addChild(txt);

HTML entities

See Flash HTMLEntities Suck for ways to handle more entities.

Flash also supports explicit character codes, such as & (ASCII ampersand) and € (Unicode € symbol).

Flash HTMLEntities Suck

Jan 8, 2009

So I ran into this issue at work where the client was trying to pass in HTML entities and expecting Flash to just take it in stride. Apparently Flash only handles about 5 different kinds of entities of the 300 or so. So to get around this I made a function to convert all the HTML entities to number entities. So if anyone else runs into this issue they can take advantage of this useful tool. Enjoy!

public function convertHTMLEntities(str:String):String {
	var htmlEntities:Array = ["&nbsp;", "&iexcl;", "&cent;", "&pound;", "&curren;", "&yen;", "&brvbar;", "&sect;", "&uml;", "&copy;", "&ordf;", "&laquo;", "&not;", "&shy;", "&reg;", "&macr;", "&deg;", "&plusmn;", "&sup2;", "&sup3;", "&acute;", "&micro;", "&para;", "&middot;", "&cedil;", "&sup1;", "&ordm;", "&raquo;", "&frac14;", "&frac12;", "&frac34;", "&iquest;", "&Agrave;", "&Aacute;", "&Acirc;", "&Atilde;", "&Auml;", "&Aring;", "&AElig;", "&Ccedil;", "&Egrave;", "&Eacute;", "&Ecirc;", "&Euml;", "&Igrave;", "&Iacute;", "&Icirc;", "&Iuml;", "&ETH;", "&Ntilde;", "&Ograve;", "&Oacute;", "&Ocirc;", "&Otilde;", "&Ouml;", "&times;", "&Oslash;", "&Ugrave;", "&Uacute;", "&Ucirc;", "&Uuml;", "&Yacute;", "&THORN;", "&szlig;", "&agrave;", "&aacute;", "&acirc;", "&atilde;", "&auml;", "&aring;", "&aelig;", "&ccedil;", "&egrave;", "&eacute;", "&ecirc;", "&euml;", "&igrave;", "&iacute;", "&icirc;", "&iuml;", "&eth;", "&ntilde;", "&ograve;", "&oacute;", "&ocirc;", "&otilde;", "&ouml;", "&divide;", "&oslash;", "&ugrave;", "&uacute;", "&ucirc;", "&uuml;", "&yacute;", "&thorn;", "&yuml;", "&fnof;", "&Alpha;", "&Beta;", "&Gamma;", "&Delta;", "&Epsilon;", "&Zeta;", "&Eta;", "&Theta;", "&Iota;", "&Kappa;", "&Lambda;", "&Mu;", "&Nu;", "&Xi;", "&Omicron;", "&Pi;", "&Rho;", "&Sigma;", "&Tau;", "&Upsilon;", "&Phi;", "&Chi;", "&Psi;", "&Omega;", "&alpha;", "&beta;", "&gamma;", "&delta;", "&epsilon;", "&zeta;", "&eta;", "&theta;", "&iota;", "&kappa;", "&lambda;", "&mu;", "&nu;", "&xi;", "&omicron;", "&pi;", "&rho;", "&sigmaf;", "&sigma;", "&tau;", "&upsilon;", "&phi;", "&chi;", "&psi;", "&omega;", "&thetasym;", "&upsih;", "&piv;", "&bull;", "&hellip;", "&prime;", "&Prime;", "&oline;", "&frasl;", "&weierp;", "&image;", "&real;", "&trade;", "&alefsym;", "&larr;", "&uarr;", "&rarr;", "&darr;", "&harr;", "&crarr;", "&lArr;", "&uArr;", "&rArr;", "&dArr;", "&hArr;", "&forall;", "&part;", "&exist;", "&empty;", "&nabla;", "&isin;", "&notin;", "&ni;", "&prod;", "&sum;", "&minus;", "&lowast;", "&radic;", "&prop;", "&infin;", "&ang;", "&and;", "&or;", "&cap;", "&cup;", "&int;", "&there4;", "&sim;", "&cong;", "&asymp;", "&ne;", "&equiv;", "&le;", "&ge;", "&sub;", "&sup;", "&nsub;", "&sube;", "&supe;", "&oplus;", "&otimes;", "&perp;", "&sdot;", "&lceil;", "&rceil;", "&lfloor;", "&rfloor;", "&lang;", "&rang;", "&loz;", "&spades;", "&clubs;", "&hearts;", "&diams;", "\"", "&", "<", ">", "&OElig;", "&oelig;", "&Scaron;", "&scaron;", "&Yuml;", "&circ;", "&tilde;", "&ensp;", "&emsp;", "&thinsp;", "&zwnj;", "&zwj;", "&lrm;", "&rlm;", "&ndash;", "&mdash;", "&lsquo;", "&rsquo;", "&sbquo;", "&ldquo;", "&rdquo;", "&bdquo;", "&dagger;", "&Dagger;", "&permil;", "&lsaquo;", "&rsaquo;", "&euro;"];
	var numberEntities:Array = ["&#160;", "&#161;", "&#162;", "&#163;", "&#164;", "&#165;", "&#166;", "&#167;", "&#168;", "&#169;", "&#170;", "&#171;", "&#172;", "&#173;", "&#174;", "&#175;", "&#176;", "&#177;", "&#178;", "&#179;", "&#180;", "&#181;", "&#182;", "&#183;", "&#184;", "&#185;", "&#186;", "&#187;", "&#188;", "&#189;", "&#190;", "&#191;", "&#192;", "&#193;", "&#194;", "&#195;", "&#196;", "&#197;", "&#198;", "&#199;", "&#200;", "&#201;", "&#202;", "&#203;", "&#204;", "&#205;", "&#206;", "&#207;", "&#208;", "&#209;", "&#210;", "&#211;", "&#212;", "&#213;", "&#214;", "&#215;", "&#216;", "&#217;", "&#218;", "&#219;", "&#220;", "&#221;", "&#222;", "&#223;", "&#224;", "&#225;", "&#226;", "&#227;", "&#228;", "&#229;", "&#230;", "&#231;", "&#232;", "&#233;", "&#234;", "&#235;", "&#236;", "&#237;", "&#238;", "&#239;", "&#240;", "&#241;", "&#242;", "&#243;", "&#244;", "&#245;", "&#246;", "&#247;", "&#248;", "&#249;", "&#250;", "&#251;", "&#252;", "&#253;", "&#254;", "&#255;", "&#402;", "&#913;", "&#914;", "&#915;", "&#916;", "&#917;", "&#918;", "&#919;", "&#920;", "&#921;", "&#922;", "&#923;", "&#924;", "&#925;", "&#926;", "&#927;", "&#928;", "&#929;", "&#931;", "&#932;", "&#933;", "&#934;", "&#935;", "&#936;", "&#937;", "&#945;", "&#946;", "&#947;", "&#948;", "&#949;", "&#950;", "&#951;", "&#952;", "&#953;", "&#954;", "&#955;", "&#956;", "&#957;", "&#958;", "&#959;", "&#960;", "&#961;", "&#962;", "&#963;", "&#964;", "&#965;", "&#966;", "&#967;", "&#968;", "&#969;", "&#977;", "&#978;", "&#982;", "&#8226;", "&#8230;", "&#8242;", "&#8243;", "&#8254;", "&#8260;", "&#8472;", "&#8465;", "&#8476;", "&#8482;", "&#8501;", "&#8592;", "&#8593;", "&#8594;", "&#8595;", "&#8596;", "&#8629;", "&#8656;", "&#8657;", "&#8658;", "&#8659;", "&#8660;", "&#8704;", "&#8706;", "&#8707;", "&#8709;", "&#8711;", "&#8712;", "&#8713;", "&#8715;", "&#8719;", "&#8721;", "&#8722;", "&#8727;", "&#8730;", "&#8733;", "&#8734;", "&#8736;", "&#8743;", "&#8744;", "&#8745;", "&#8746;", "&#8747;", "&#8756;", "&#8764;", "&#8773;", "&#8776;", "&#8800;", "&#8801;", "&#8804;", "&#8805;", "&#8834;", "&#8835;", "&#8836;", "&#8838;", "&#8839;", "&#8853;", "&#8855;", "&#8869;", "&#8901;", "&#8968;", "&#8969;", "&#8970;", "&#8971;", "&#9001;", "&#9002;", "&#9674;", "&#9824;", "&#9827;", "&#9829;", "&#9830;", "&#34;", "&#38;", "&#60;", "&#62;", "&#338;", "&#339;", "&#352;", "&#353;", "&#376;", "&#710;", "&#732;", "&#8194;", "&#8195;", "&#8201;", "&#8204;", "&#8205;", "&#8206;", "&#8207;", "&#8211;", "&#8212;", "&#8216;", "&#8217;", "&#8218;", "&#8220;", "&#8221;", "&#8222;", "&#8224;", "&#8225;", "&#8240;", "&#8249;", "&#8250;", "&#8364;"];
	str = str.split("&").join("&#038;");
	var i:uint = htmlEntities.length;
	while (i--) {
		str = str.split(htmlEntities[i]).join(numberEntities[i]);
	}
	return new XMLDocument(str).firstChild.nodeValue;
}