Macromedia Flex Macromedia Flex
Loading Fonts At Runtime Using CSS Part 1
  Home

Mar 28, 2007 - Loading Fonts At Runtime Using CSS Part 1
A complete demo on loading fonts at runtime using CSS and the StyleManager class.

This demo is based on Kyle's runtime fonts article. I just added a few tweaks and created a tester app to demonstrate the concept. This is the first part of my article and in this part you will be shown how to apply different fonts and font sizes to a number of different text field objects that you also create at runtime.

First we create our own custom Label class which is based on UITextField. We do this so that we can store additional info on our label like the font it is using, the size, color and id.

package
{
 import mx.core.UITextField;
 import flash.text.TextFieldAutoSize;
 
 public class Label  extends UITextField
 {
  public var font:String = 'Trebuchet';
  public var size:uint = 11;
  public var color:uint = 0x000000;
  public var id:String = '';

  public function Label() {
   super();
   
   text = 'Sample Text';
   name = 'label';
   autoSize = TextFieldAutoSize.LEFT;
   multiline = true;
   border = true;
   visible = false;
  }
 }
}

We use 2 truetype fonts you can usually find in your windows/fonts folder, Trebuchet and Book Antiqua. We create a CSS file for each font, this will be converted to a SWF file when you compile your applicaton. You can use mxmlc.exe to compile the CSS file to SWF or if you're using Adobe Flex Builder 2, right click on the CSS file in your project tree in the Project Navigator pane and enable Compile CSS to SWF from the context menu that appears, this will add your CSS files to the compilation process when you build your project.

Here's the CSS file for our Book Antiqua font. Make sure you have a copy of the fonts in the appropriate folder, in our case inside the assets folder we have antquab.ttf, antquabi.ttf, antquai.ttf, and bkant.ttf. Note that you can set an optional unicodeRange to limit the characters that will be embedded, this will reduce the output SWF file's size. You can use this CSS file as a reference for additional fonts you want to use on your application.

/* CSS file */
@font-face {
    src:url("assets/bkant.ttf");
    fontFamily: BookAntiqua;
/*  unicodeRange: U+0041-U+007F*/
}

@font-face {
    src:url("assets/antquab.ttf");
    fontFamily: BookAntiqua;
    fontWeight: bold;
/*  unicodeRange: U+0041-U+007F*/
}

@font-face {
    src:url("assets/antquai.ttf");
    fontFamily: BookAntiqua;
    fontStyle: italic;
/*  unicodeRange: U+0041-U+007F*/
}

@font-face
{
    src:url("assets/antquabi.ttf");
    fontFamily: BookAntiqua;
    fontWeight: bold;
    fontStyle: italic;
/*  unicodeRange: U+0041-U+007F*/
}

.BookAntiquaPlainStyle {
    fontFamily: BookAntiqua;
}
 
.BookAntiquaBoldStyle {
    fontFamily: BookAntiqua;
    fontWeight: bold;
}

.BookAntiquaItalicStyle {
    fontFamily: BookAntiqua;
    fontStyle: italic;
}

.BookAntiquaBoldItalicStyle {
    fontFamily: BookAntiqua;
    fontWeight: bold;   
    fontStyle: italic;
}

And here's the CSS file for our Trebuchet font, make sure you have trebuc.ttf, trebucbd.ttf, trebucbi.ttf, and trebucit.ttf in your assets folder.

/* CSS file */
@font-face {
    src:url("assets/trebuc.ttf");
    fontFamily: Trebuchet;
/*  unicodeRange: U+0041-U+007F*/
}

@font-face {
    src:url("assets/trebucbd.ttf");
    fontFamily: Trebuchet;
    fontWeight: bold;
/*  unicodeRange: U+0041-U+007F*/
}

@font-face {
    src:url("assets/trebucit.ttf");
    fontFamily: Trebuchet;
    fontStyle: italic;
/*  unicodeRange: U+0041-U+007F*/
}
 
@font-face {
    src:url("assets/trebucbi.ttf");
    fontFamily: Trebuchet;
    fontWeight: bold;
    fontStyle: italic;
/*  unicodeRange: U+0041-U+007F*/
}

.TrebuchetPlainStyle {
    fontFamily: Trebuchet;
}
 
.TrebuchetBoldStyle {
    fontFamily: Trebuchet;
    fontWeight: bold;
}

.TrebuchetItalicStyle {
    fontFamily: Trebuchet;
    fontStyle: italic;
}

.TrebuchetBoldItalicStyle {
    fontFamily: Trebuchet;
    fontWeight: bold;   
    fontStyle: italic;
}

Now here's our main application. It contains a Panel with a ControlBar at the bottom which contains the controls to add the labels and change its properties like color, font, size and style. We use the StyleManager class to load at runtime the SWF files which contain the fonts. It would be best if we put all this functionality in a separate class instead of coding it inline in our main application, I recommend you do this to make your application more organized.

And here's the code.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" backgroundGradientColors="[#c0c0c0, #ffffff]">
 <mx:Script>
  <![CDATA[
   import mx.events.StyleEvent;
   import mx.containers.Canvas;
   import mx.core.UITextField;
   import flash.text.TextFieldType;
   import flash.text.TextFieldAutoSize;
   import mx.styles.StyleManager;

   private var activeLabel:UITextField = null;
   private var labels:Array = new Array();
   private var loadedFonts:Object = {};

   private function panelClickHandler(event:MouseEvent):void {
    // we deselect the active label when user clicks on the panel
    if (activeLabel != null) {
     if ((fontPanel.hitTestPoint(event.stageX, event.stageY)) && (!cbar.hitTestPoint(event.stageX, event.stageY))) {
      if (!activeLabel.hitTestPoint(event.stageX, event.stageY)) {
       resetActiveLabel();
       activeLabel = null;
      }
     }
    }
   }

   private function loadHandler():void {
    // create the label and store it in an array
    labels.push(new Label);
    var newLabel:Label = labels[labels.length-1];
    newLabel.id = "label" + labels.length;
    resetActiveLabel();
    activeLabel = newLabel;
   
    // put the label inside the canvas so that we can drag it
    var labelHolder:Canvas = new Canvas();
    labelHolder.doubleClickEnabled = true;
    
    // add the mouse drag event handlers
    labelHolder.addEventListener(MouseEvent.MOUSE_DOWN,
     function(event:MouseEvent):void {
      var tempLabel:UITextField = event.currentTarget.getChildAt(0);

      if (tempLabel.type != TextFieldType.INPUT) {
       event.currentTarget.startDrag();
      }
      
       if (activeLabel != tempLabel) {
       resetActiveLabel();
      }
      
      tempLabel.border = true;
      activeLabel = tempLabel;
     }
    );
    
    labelHolder.addEventListener(MouseEvent.MOUSE_UP,
     function(event:MouseEvent):void {
      event.currentTarget.stopDrag();
     }
    );
    
    labelHolder.addEventListener(MouseEvent.DOUBLE_CLICK,
     function(event:MouseEvent):void {
      // convert the label into an editable control
      // IN PART 2 OF THIS TUTORIAL

     }
    );
    
    // add the canvas first to its parent, the panel, before adding the label to the canvas
    fontPanel.addChild(labelHolder);
    labelHolder.addChild(newLabel);
    
    // define the label's properties based on the current state of the controls
    loadFont();
    applySize();
    applyColor();
   }
   
   private function colorPickerHandler():void {
    applyColor();
   }
   
   private function sliderHandler():void {
    applySize();
   }
   
   private function boldHandler():void {
    // IN PART 2 OF THIS TUTORIAL
   }
   
   private function italicHandler():void {
    // IN PART 2 OF THIS TUTORIAL
   }
   
   private function boldItalicHandler():void {
    // IN PART 2 OF THIS TUTORIAL
   }
   
   private function normalHandler():void {
    // IN PART 2 OF THIS TUTORIAL
   }
   
   private function changeFontHandler():void {
    loadFont();
   }
   
   private function loadFont():void {
    // get font from combobox
    var fontName:String = (cmbFonts.selectedItem['data']);    
    if (activeLabel != null) {
     var styleSheet:String = fontName + ".swf";
     
     if (loadedFonts[fontName]) {
      // external font already loaded so we only need to apply the font to the label
      applyFont();
     } else {
      // we use the StyleManager class to load our external stylesheet file that was converted from a CSS to a SWF file
      var myEvent:IEventDispatcher = StyleManager.loadStyleDeclarations(styleSheet, true);
      myEvent.addEventListener(StyleEvent.COMPLETE, loadFontComplete);
      myEvent.addEventListener(StyleEvent.PROGRESS, progressEventHandler);
      myEvent.addEventListener(StyleEvent.ERROR, onError);
      loadedFonts[fontName] = true;
     }
     
    }
   }
   
   private function applyFont():void {
    // apply the loaded font to all labels that use this font
    Label(activeLabel).font = cmbFonts.selectedItem['data'];
    activeLabel.styleName = Label(activeLabel).font + 'PlainStyle' + Label(activeLabel).id;
    
    // we use the CSSStyleDeclaration to modify the style and reapply it to the label object
    var style:CSSStyleDeclaration = new CSSStyleDeclaration();
    style.setStyle('fontSize', Label(activeLabel).size);
    style.setStyle('fontFamily', Label(activeLabel).font);
    StyleManager.setStyleDeclaration("." + activeLabel.styleName, style, true);
    activeLabel.visible = true;
   }
   
   private function applySize():void {
    if (activeLabel != null) {
     Label(activeLabel).size = fontSizeSlider.value;
     applyFont();
    }
   }
   
   private function applyColor():void {
    if (activeLabel != null) {
     activeLabel.setColor(colorPicker.selectedColor);
    }
   }

   private function resetActiveLabel():void {
     if (activeLabel != null) {
     activeLabel.border = false;
     activeLabel.selectable = false;
     activeLabel.type = TextFieldType.DYNAMIC;
     activeLabel.background = false;
     activeLabel.setSelection(activeLabel.text.length, activeLabel.text.length); // put the cursor at the end of the text
    }
   }

   private function loadFontComplete(event:StyleEvent):void {
    applyFont();
   }
   
   private function progressEventHandler(e:ProgressEvent):void {
    // track font downloading progress here
   }
 
   private function onError(event:StyleEvent):void {
    trace('font not loaded!');
   }

  ]]>
 </mx:Script>
 <mx:Panel id="fontPanel" width="350" height="300" layout="absolute" horizontalCenter="0" verticalCenter="0" title="Runtime Fonts" mouseDown="panelClickHandler(event)">
  <mx:ControlBar id="cbar" height="75">
   <mx:Canvas width="100%" height="100%">
    <mx:VBox>
     <mx:HBox>
      <mx:Button
label="Load UITextField" id="btnLoad" click="loadHandler()"/>
      <mx:ColorPicker id="colorPicker" selectedColor="#008040" change="colorPickerHandler()"/>
      <mx:HSlider minimum="11" maximum="48" id="fontSizeSlider" change="sliderHandler()"/>
     </mx:HBox>
     <mx:HBox>
      <mx:Button
label="B" id="btnBold" click="boldHandler()"/>
      <mx:Button label="I" id="btnItalic" click="italicHandler()"/>
      <mx:Button label="BI" id="btnBoldItalic" click="boldItalicHandler()"/>
      <mx:Button label="N" id="btnNormal" click="normalHandler()"/>
      <mx:ComboBox width="130" id="cmbFonts" change="changeFontHandler()">
       <mx:dataProvider>
        <mx:Array>
         <mx:Object
label="Book Antiqua" data="BookAntiqua"/>
         <mx:Object label="Trebuchet" data="Trebuchet"/>
        </mx:Array>
       </mx:dataProvider>
      </mx:ComboBox>
     </mx:HBox>
    </mx:VBox>
   </mx:Canvas>
  </mx:ControlBar>
 </mx:Panel>
</mx:Application>

In the next part I will add functionality to edit the labels by double clicking on them, applying styles to the fonts (bold, italic, bold-italic), and deleting the labels, I also might add PDF generation in a future article.

File Details
Created On Mar, 28, 2007 by Rico Zuniga
Last Modified On Mar, 28, 2007 by Rico Zuniga
Group: Tips and Articles
Flex Versions: 2.0
Category: Customizing (Skinning/Themes/CSS)
Type: Complete Lesson
Difficulty: Intermediate
Keywords: runtime fonts, css, StyleManager