Runtime font loading with AS3 / Flash CS3 (not Flex!)
June 22, 2007
If I never really got something in Flash it was font embedding and runtime font loading. The bad news is: We have ActionScript 3.0 now and Flash CS3 and I still haven’t completely understood it yet. The good news is: It works – somehow – if you keep in mind some things. After all articles about this topic I’ve read until now I have the feeling that nobody really understands what’s going on with fonts in different use cases. Trial and error…
As the headline suggests this article deals with font embedding in Flash CS3 and not in Flex. With Flex and mxmlc there’s the embed metatag where you can even define font character ranges which should be embedded. You don’t have this option if compile with the Flash IDE…
[UPDATE (16.11.2007)
You can define character sets in the Flash Authoring Tool (however you can't set them via ActionScript!):
There's a UnicodeTable.xml in the Adobe Flash CS3\(language)\First Run\FontEmbedding\ folder that you can copy and modify. For more information see the flash help (Creating custom character sets) and Renzo's Flash unicode table generator page.]
As we needed runtime font loading in our current project I tried out a few solutions. Our concrete requirements were the following:
- Textfields get their content from an XML file
- Text in the XML is wrapped in a CData-Tag and formated with CSS styles
- The CSS styles are defined in an external file
- Fonts are all in a a separate fla / swf file and loaded at runtime
- Some textfields have embedded fonts (e.g. headlines), some use device fonts (continuous text)
- Sometimes there are different font styles / variants in a single textfield with embedded fonts
When you’re working with embedded fonts it’s important to always test your project on a different machine where they aren’t installed or disable them in some font managing tool. Otherwise you don’t know if everything works and is really embedded.
In Flash CS3 there’s still the font symbol, i.e. you can create a font symbol in the library, give it a name, set the font, font size and variant and export it for ActionScript. The difference to Flash 8 is that you have to specify a class (same as with MovieClips, Bitmaps etc. in ActionScript 3.0) instead of a linkage id. With fonts the class must extend the Font class and this is automatically set for you when you export the font: flash.text.Font. The Font class in AS3 is an abstract class which means that it can’t be instantiated – so you can’t create a new one by calling new Font()…
The font class has a few properties (fontName, fontStyle and fontType) and two static methods which seem to be interesting:
Font.enumerateFonts() and Font.registerFont(). Unfortunately the help for registerFont (at least the German help) only says that it registers a font in the global font list and that it expects a parameter of type Class. What that means is that you can call Font.registerFont(MyFontClass) (MyFontClass is the class that you can set in the export dialog – name doesn’t matter) and then trace(Font.enumerateFonts()) and your font is listed…
With that you successfully registered a font in the global list and can use it everywhere in your code now. One option for setting a font in a textfield is to create a new TextFormat() and set the font property. The font property must be a String (the font name). You can’t use myTextFormat.font = “MyFontClass” und you can’t use myTextFormat.font = “MyFontPropertyName” (“MyFontPropertyName” is the name that you set in the font symbols properties. Don’t confuse it with the class name in the export dialog). myTextFormat.font expects the real font name:
If you set the font to “standard 07_57″ in the properties dialog you have to write myTextFormat.font = “standard 07_57″. That’s bad if you want to switch your font in the library but don’t want to modify your code. Another solution is to instantiate the symbol directly and use its fontName property: myTextFormat.font = new MyFontClass().fontName. Works. So that seems to be the golden font rule #1:
Always use the real font name (if you use an external css file use the real font name as the font-family)!
The example above would also work if you didn’t call Font.registerFont(MyFontClass) before (when your font symbol is in the same fla / swf there’s no need to register it). What about loading your font symbols from another swf?
Create them in an extra fla file: Create the symbol in the library, set the font in the properties (name doesn’t matter, size dosen’t matter, variant DOES matter – if you want to use the bold version you have to create another font symbol), export for ActionScript, set a class name and compile. Now you can load those fonts once and reuse them in all your swfs. There are at least two ways how you can do that:
1. Check on “Export for runtime sharing” for all fonts in your library file, enter the correct swf path and drag them into the target fla where you want to use them (“import for runtime sharing” is automatically checked on). That worked in previous flash versions and the size of the target file isn’t increased. Additionally it’s a good idea to preload your library to make sure it’s completely loaded when your target swf uses the fonts. Apart from having to place all fonts in your target fla, there are some more disadvantages and workarounds (relative paths, force shared library to be really used etc.) using this approach.
2. Do not check on “Export for runtime sharing” but load the library swf with the Loader class, use the Loader’s applicationDomain to get all the class definitions and register them in the global font list (further explained below). This is the preferred way as you don’t need any font symbols in your target fla. Furthermore you can create different library files for different languages with different fonts (different character sets etc.).
For this purpose you could use some kind of FontManager class:
package de.betriebsraum.utils { import flash.display.*; import flash.events.*; import flash.text.*; import flash.errors.*; import flash.system.*; public class FontManager extends EventDispatcher { private static var INSTANCE:FontManager; private var _fontsDomain:ApplicationDomain; private var _styleSheet:StyleSheet; public function FontManager(enforcer:SingletonEnforcer) { super(); } public static function getInstance():FontManager { if (INSTANCE == null) { INSTANCE = new FontManager(new SingletonEnforcer()); } return INSTANCE; } public function initialize(fontsDomain:ApplicationDomain, styles:StyleSheet):void { if (_fontsDomain == null) { _fontsDomain = fontsDomain; _styleSheet = styles; } else { throw new IllegalOperationError("FontManager already initialized!"); } } public function registerFonts(fontList:Array):void { for (var i:int = 0; i < fontList.length; i++) { Font.registerFont(getFontClass(fontList[i])); } } public function getFontClass(id:String):Class { return _fontsDomain.getDefinition(id) as Class; } public function getFont(id:String):Font { var fontClass:Class = getFontClass(id); return new fontClass as Font; } public function getStyleSheet():StyleSheet { return _styleSheet; } } } class SingletonEnforcer { }
As you can see the FontManager class does not load the library file itself.
The reason is that in our case the files are loaded at application startup in a loader queue (which also loads other stuff) and then just passed to the FontManager but you can add in the loading functionality for the fonts file and a stylesheet easily: Just create a Loader and load the swf…and when the Loader is finished create a URLLoader and load the stylesheet. The initialize method accepts an ApplicationDomain and a StyleSheet which are both stored in instance variables.
The ApplicationDomain parameter would be myLoader.contentLoaderInfo.applicationDomain and the StyleSheet is an instance of the StyleSheet class (with parseCSS() called before to parse the external css file). After calling the initialize method you would call registerFonts and pass an array with font class names that you want to register in the global list, for example: fontManagerInstance.registerFonts(["Standard57"]). This registers the font with a class name of Standard57 (Standard57 is the class set in the symbol linkage, not the symbol name and not the real font name!). In the CSS file, the font is defined with font-family: standard 07_57 (use the real name here!). Also make sure to set just one font in the font-family style! The following didn’t work:
font-family: standard 07_57, _sans
The registerFonts method uses the applicationDomain’s getDefinition method to get the font class definition by a string. This is a great feature in AS3: You can load in a swf with some symbols, get their class definitions (the applicationDomain is some kind of »storage« for the class definitions in a swf) and use them to create new instances.
If you now place a textfield onto the stage and write:
myTextField.styleSheet = fontManagerInstance.getStyleSheet(); myTextField.embedFonts = true; myTextField.htmlText = "<h1>This is the headline</h1>";
everything should work (provided there’s a h1 style in the css with the correct font-family set and a font symbol in the library swf). If the textfield is manually placed onto the stage it’s important to set a different font for the textfield itself (in the property inspector) than the one that is defined in the CSS file (simply select _sans, for example). Don’t embed it in the property inspector as the file size would be increased then (set embedFonts in your code instead). What’s really strange:
If you additionally place a static textfield onto the stage with the same font, the text in the dynamic textfield isn’t shown anymore. Don’t really understand why…but it’s probably the same problem as if you set the same font in the dynamic textfield (maybe a »font collision«?). A workaround is to create the font symbol in the fla and select it (the one with *) in the property inspector.
If you know more about Font.registerFonts() and the above problems please leave a comment.
Filed under: Flash Platform
Hi Felix, your article is really helpful but I think there is at least one use case which it doesn’t address. And that case is: using the run-time loaded font on a manually placed TextField inside a run-time loaded swf which is I’m suffering at the moment.
More info:
Fonts.swf: It has the font symbols embedded and exported for ActionScript.
Child.swf: It has text fields placed on stage. Fonts are not embedded.
Main.swf: Main application. It loads Fonts.swf, registers the fonts inside it and uses the font throughout the application. Child.swf is a typical external swf loaded by Main.swf
Requirement:
Set the texts of text fields inside Child.swf once both Child.swf and Fonts.swf are loaded by the Main.swf AND the fonts in the Fonts.swf are registered by the Main.swf
Problem:
None of the texts in the Child.swf are visible when the text is set by the Main.swf even after registering the fonts of Fonts.swf. But when a dynamic text field is created by using new TextField() it works perfectly fine
If you or anyone else has a solution for this problem please do share.
@santoshs: you need to have a mechanism to set the text format for the static text when the loading is over. And you have to take care that the fonts are already loaded in the main , before you try to access it.
What I used to do is , Load the fonts intially and when they are loaded save them in an class as static variable.Then I used to wait for the intial assets to be laoded(like font and child .swf), and when they are laoded, I call a method(s) in the child swf(s) to set the text format according to the loaded font in the staic variable. All other assets loaded afterwards will be using the staic variable text format to set their text after they are laoded. In this way you can get both your staic and dynamic text hold the loaded font.