1 Reply Latest reply on Aug 24, 2009 1:07 PM by archont

    AIR, Fonts, CS4 and the security sandbox

    archont Level 1

      I have no idea why embedding fonts in CS4 using library->new font includes every european character EXCEPT polish. You have german, french, spanish, norwegian, but not polish. Well, since embedding a font from Flash is the only way to use bitmap fonts in Flex, I had to create a library of external font files, one SWF per font size and style. Such an SWF exposes several functions, such as returning a ready to use pre-formatted textfield, returning the font name (Such as Tahoma) and the font name you actually need to use (such as Tahoma_13pt_st).

       

      I thought I'd need an AIR application to parse through all the fonts (and there are quite a few) extract the neccesary data, such as font size, name and so on and generate an XML file, so that I can load fonts at dynamic.

       

      The first problem I encountered was the security sandbox. A possible solution was to use the loaderInfo.childSandboxBridge. That approach didn't work however, as I was generating plain SWF files from flash CS4. childSandboxBridge is an AIR property, so I had to create an AIR file and try to set the bridge property to a simple number. So I did, but it gave me a

       

      SecurityError: Error #3206: Caller app:/TahomaBold13.swf cannot set LoaderInfo property childSandboxBridge.

      Weird. Well, I reverted the file to plain CS4 FPL10 SWF and decided to try another approach. I first loaded the SWF as a FileStream, then put the bytes into Loader.loadBytes. That should take care of security. And it did, however it created another problem.

       

      The font library relies on being able to enumerate the embeded fonts. The SWF's constructor has a function that enumerates all fonts and isolates the font embeded in the SWF, and then extracts it's properties. When launching the SWF by itself, or loading it from another CS4 FPL10 SWF it launches perfectly and enumerates the fonts as it should. However when the SWF is executed from inside AIR, the constructor located in the font file, as well as a function called from the main application upon executing enumerateFonts(false) both give an empty array. Which is quite weird really, as the loaded SWF contains an input TextField with embedded fonts. And I can edit and type stuff in that textfield, even while it's rotated.

       

      I thought this might be an issue of a different flash player version, but I tried to target AIR 1.5 and flash 9, neither worked and both returned no embeded fonts.

       

      Here's the entire source of the mxml air app

      <?xml version="1.0" encoding="utf-8"?>
      <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
           <mx:Panel x="0" y="0" width="100%" height="100%" layout="absolute" title="M2C Studio Font Parser Utility">
                <mx:VBox x="0" y="0" width="100%" height="100%" paddingRight="10" paddingLeft="10" paddingTop="10" paddingBottom="0">     
                     <mx:HBox x="10" y="10" width="100%" height="95%">
                          <mx:VBox width="50%" height="100%">
                               <mx:Label text="Select font directory from filelist below"/>
                               <mx:FileSystemTree width="100%" height="50%" id="fileTree"/>
                               <mx:HRule width="100%"/>
                               <mx:Label text="Fonts list"/>
                               <mx:Text width="100%" height="50%" id="fontlist"/>
                          </mx:VBox>
                          <mx:VRule height="100%"/>
                          <mx:VBox width="50%" height="100%">
                               <mx:Label text="XML Output"/>
                               <mx:TextArea width="100%" height="50%" backgroundColor="#ECE9E9"/>
                               <mx:Canvas width="100%" height="50%" id="canv">
                               </mx:Canvas>
                          </mx:VBox>
                     </mx:HBox>
                     <mx:Button label="Generate XML from directory" width="100%" click="handlePress();"/>
                </mx:VBox>
           </mx:Panel>
           <mx:Script>
          <![CDATA[
               import flash.utils.setInterval;
               import com.m2cstudio.archont.utility.fonts.FontLibraryItem;
               import com.m2cstudio.archont.utility.fonts.IFontLibraryItem;
               import mx.accessibility.AlertAccImpl;
                   import mx.controls.*;
                  import mx.events.*;
                  import mx.controls.Alert;
                  var rx:RegExp = /^.*\.swf$/;
                  
                  
                  function handlePress():void
                  {
                       // This also throws an error
                       //Security.allowDomain("*"); 
                       var file:File = fileTree.selectedItem as File;
                       var aLoad:Array = new Array();                 
                       if(!file)
                       {
                            Alert.show("You must select a folder", "Error");
                            return;
                       } else if(!file.isDirectory) {
                            Alert.show("You must select a folder, not a file", "Error");
                            return;
                       }
                        
                       var aList:Array = file.getDirectoryListing();
                       for each (var fil:File in aList)
                       {
                            if(!fil.isDirectory)
                            {
                                 if(fil.nativePath.match(rx))
                                 {
                                      // Is swf
                                      var fs:FileStream = new FileStream();
                                      fs.addEventListener(Event.COMPLETE, handleFileStreamLoaded);
                                      fs.openAsync(fil, FileMode.READ);                       
                                 }
                            }
                       }
                  }
                  
                  function handleFileStreamLoaded(e:Event):void
                  {
                       var fs:FileStream = e.target as FileStream;
                       
                       var ld:Loader = new Loader();
                       var lc:LoaderContext = new LoaderContext();
                       var ba:ByteArray = new ByteArray();
                       
                       lc.allowLoadBytesCodeExecution = true;
                       
                       fs.readBytes(ba);
                       fs.close();
                       ld.contentLoaderInfo.addEventListener(Event.COMPLETE, handleLoaded);
                       ld.loadBytes(ba, lc);        
                  }
                  
                  function handleLoaded(e:Event):void
                  {
                          var cnt:FontLibraryItem = e.target.content as FontLibraryItem;
                          cnt.rotation=10; // Rotation, just to be sure it's not using system fonts
                          canv.rawChildren.addChild(cnt);
                          
                          // This doesn't output anything - neither the main app nor the loaded SWF 'see' any embedded fonts, even though the later uses them! 
                          for each (var f:Font in Font.enumerateFonts(false))
                          {
                               Alert.show(f.fontName, f.fontType);     
                          }
                          
                          // This should retrieve the appropriate values but throws an error because the SWF can't grab the Font definition
                          //Alert.show(cnt.getFontName(), cnt.getFontStyle());               
                  }
          ]]>
          </mx:Script>
      </mx:WindowedApplication>
      
      

       

      Here's a screen of what it actually looks like when compiled:

      screenshot.jpg

       

      Here's the source of the font library item. Note that the SWF contains only 2 items. A TextField named font with embeded characters and a boolean bt on the first frame.

       

      package com.m2cstudio.archont.utility.fonts
      {
           import flash.display.MovieClip;
           import flash.text.*; 
      
           public dynamic class FontLibraryItem extends MovieClip implements IFontLibraryItem
           {
                private var txtFont:TextField;
                private var fFont:Font;
                
                public function FontLibraryItem()
                {
                     super();
                               // Causes an error - see below why
                     //init();
                }
                
                public function getFontName():String
                {
                     return fFont.fontName;
                }
                public function getFontType():String
                {
                     return fFont.fontType;
                }
                public function getFontStyle():String
                {
                     return fFont.fontStyle;
                }
                
                public function getBitmapText():Boolean
                {
                     return this.bt;
                }
                
                public function getBitmapTextSize():uint
                {
                     if(this.bt) {
                          return Number(txtFont.defaultTextFormat.size);
                     } else {
                          return 0;
                     }
                     
                }
                
                public function hasGlyphs(glyphs:String):Boolean
                {
                     return fFont.hasGlyphs(glyphs);
                }
                
                public function createTextField():TextField
                {
                     var tf:TextField = new TextField();
                     tf.embedFonts = true;
                     tf.defaultTextFormat = (this.font as TextField).defaultTextFormat;
                     return tf;
                } 
                
                public function init():void
                {
                     if(this.font) {
                          txtFont = this.font;
                     } else {
                          throw new Error("Document must contain a textfield named 'font' with the embedded font");
                     }
                     
                     var fArr:Array = Font.enumerateFonts(false);
                     if(fArr.length==0) {
                          throw new Error("Document does not contain any embeded fonts.");
                     } else if (fArr.length>1) {
                          throw new Error("Document must contain not more than one embedded font");
                     }
                     
                     fFont = fArr[0];
                }
           }
      }
      

       

      I'm hoping some AIR specialists will take a look at this. Frankly I'm stumped. Font support in Flash was always black magic, more or less, so I can only hope this is an issue that can be solved.

       

      Just tell me and I'll provide more source or sceenshots.

       

      Cheers,

      -archont

        • 1. Re: AIR, Fonts, CS4 and the security sandbox
          archont Level 1

          I even tried porting the code to Gumbo and running it there - still, no fonts are being enumerated.

           

          If you're too lazy to read the whole above post, here's the problem in one sentence

           

          An SWF that contains a textfield with embedded fonts, when launched by itself succeeds to return the embedded font using Font.enumerateFonts(false), however when loaded using Loader.loadBytes into AIR, it fails to see those fonts even though the textfield in it is displayed and editable.

           

          How do I make the loaded child application and AIR see the embedded font?