Embedding fonts in a swf… an automated approach.

March 9th, 2010 by JD No comments »

A while back I needed a way to embed true type fonts in swf files… but I needed to do it automatically via PHP code, or something similar.  Google was little help as there are plenty of tutorials on the web about how to embed a font in a swf by hand, but I couldn’t find anything on how to do it in code.  I did finally solve the problem, so I figured I’d share what I did with all of you!  I set this up on CentOS 4 using PHP 4 and Java, but any OS where you have PHP and Java installed should work okay.

First, you’ll need the Flex compiler, which is now free since Adobe released it into the public domain.  Here is a link where you can get it (it’s a fairly hefty download at 120 Mb, so be patient):

Adobe Flex 3.5 SDK

The compiler is a jar file, so you’ll need Java installed to use it.  There are about a million tutorials online about how to do this, so I won’t be discussing that here.  Unzip your Flex SDK into a directory of your choice.  Mine will be located in:

/usr/share/flex

Okay, you need one more thing before we jump into the code… a True-Type Font.  Be a little careful here and make sure you read the license agreement for any fonts you have.  Most of the time they are not allowed to be parked on a web server.  One place to get public domain fonts is dafont.com.  For the sake of this post, we’ll pretend I have a font called Foo.ttf.

In order for us to compile a swf with an embedded font, we’re going to need some Actionscript 3 code.  I’ve created a template file for this purpose, though you could just as easily generate it in your PHP script:

font_template.txt:

package {

  import flash.display.*;
  import flash.text.*;

  public class Fonts extends Sprite {
    [Embed(source="PATH", fontFamily="FONTFAMILY"FONTWEIGHT)]
    private var CLASSNAME:Class;

    public function Fonts() {
      Font.registerFont(CLASSNAME);
    }
  }

}

PATH, FONTFAMILY, FONTWEIGHT and CLASSNAME will be handled on the PHP side, but in order to do that we need some code that can parse our TTF and pull some values out of its tables.  I’m sure there are plenty of better libraries that could be used to do this, but I wrote this at the time of this project so I’ll include it here for completeness.

fonts.php:

define('FONTS_ITALIC', 1);
define('FONTS_UNDERSCORE', 2);
define('FONTS_NEGATIVE', 4);
define('FONTS_OUTLINED', 8);
define('FONTS_STRIKEOUT', 16);
define('FONTS_BOLD', 32);
define('FONTS_REGULAR', 64);
define('FONTS_USE_TYPO_METRICS', 128);
define('FONTS_WWS', 256);
define('FONTS_OBLIQUE', 512);

function getFontData($font)
{

  $data = readFont($font);

  $info = Array();
  $info['font_family'] = isset($data[16]) ? $data[16] : $data[1];
  $info['sub_family'] = isset($data[17]) ? $data[17] : $data[2];
  $info['font_name'] = preg_replace("/\-/", '', $data[6]);
  $info['full_name'] = $data[4];
  $info['italic'] = $data['italic'];
  $info['bold'] = $data['bold'];

  return($info);

}

function readFont($font)
{

  $info = Array();

  $fp = fopen($font, 'rb');

/* Read the Offset Table */
  $data = fread($fp, 12);
  $offset_table = unpack("n6", $data);

/* Read the Table Directory */
  for($i = 0; $i < $offset_table[3]; $i++) {

    $data = fread($fp, 16);
    $table_directory = unpack("c4char/N3", $data);

    $table_name = 	chr($table_directory['char1']) .
  			chr($table_directory['char2']) .
			chr($table_directory['char3']) .
			chr($table_directory['char4']);

    $table_directory_mark = ftell($fp);

  /* If this is the name table */
    if($table_name == 'name') {
      fseek($fp, $table_directory[2]);
      $data = fread($fp, 6);
      $table_header = unpack("n3", $data);

    /* Loop through all name table properties */
      for($j = 0; $j < $table_header[2]; $j++) {
         $data = fread($fp, 12);
         $name_record = unpack("n6", $data);
       /* This is the font name */
         $position = ftell($fp);
         fseek($fp, $table_directory[2] +
                       $name_record[6] +
                       $table_header[3]);
         $length = $name_record[5];
         $data = fread($fp, $length);
         $chars = unpack("c$length", $data);
         $name = '';
         foreach($chars as $char) {
           if(($char > 32) && ($char < 137)) {
            $name .= chr($char);
          }
        }

        $info[$name_record[4]] = $name;
        fseek($fp, $position);

      }

    } else if($table_name == 'OS/2') {
      fseek($fp, $table_directory[2] + 62);
      $data = fread($fp, 2);
      $header = unpack("n1", $data);
      $info['italic'] = ($header[1] & FONTS_ITALIC)
                          ? true : false;
      $info['underscore'] = ($header[1] & FONTS_UNDERSCORE)
                                   ? true : false;
      $info['negative'] = ($header[1] & FONTS_NEGATIVE)
                               ? true : false;
      $info['outlined'] = ($header[1] & FONTS_OUTLINED)
                              ? true : false;
      $info['strikeout'] = ($header[1] & FONTS_STRIKEOUT)
                                ? true : false;
      $info['bold'] = ($header[1] & FONTS_BOLD)
                         ? true : false;
      $info['regular'] = ($header[1] & FONTS_REGULAR)
                             ? true : false;
      $info['use_typo_metrics'] = ($header[1] &
                                             FONTS_USE_TYPO_METRICS)
                                             ? true : false;
      $info['wws'] = ($header[1] & FONTS_WWS)
                          ? true : false;
      $info['oblique'] = ($header[1] & FONTS_OBLIQUE)
                             ? true : false;
    }

    fseek($fp, $table_directory_mark);

  } 

  return($info);

}

Basically, this code scans through a TTF to find its NAME and OS/2 tables because they contain some information about the font that we need in order to embed it.  From the NAME table we get its Postscript name and from the OS/2 table we find out if it’s bold and/or italic.  With all this out of the way we can actually compile the swf:

compile_font.php:

      require("/path/to/fonts.php");

      $path = "/path/to/fonts/Foo.ttf";
      $font_data = getFontData($path);

      $name = $font_data['font_name'];
      $full_name = $font_data['full_name'];
      $family = $font_data['font_family'];

    /* Rename the file to its proper font name in case
        it wasn't named properly to begin with */
      $new_path = "/path/to/fonts/$name.ttf";
      rename($path, $new_path);

      $weight = "";
      if($font_data['italic'] && $font_data['bold']) {
        $weight = ', fontStyle="italic", fontWeight="bold"';
      } else if($font_data['bold']) {
        $weight = ', fontWeight="bold"';
      } else if($font_data['italic']) {
        $weight = ', fontStyle="italic"';
      }

      $data = file_get_contents("/path/to/font_template.txt");
      $find = Array("/PATH/",
                        "/FONTFAMILY/",
                        "/CLASSNAME/",
                        "/FONTWEIGHT/");
      $replace = Array($new_path, $full_name, $name, $weight);
      $data = preg_replace($find, $replace, $data);
      $as_target = "/path/to/fonts/Fonts.as";
      $swf_target = "/path/to/fonts/$name.swf";
      $fp = fopen($as_target, "wb");
      fwrite($fp, $data);
      fclose($fp);

      `/usr/share/flex/bin/mxmlc $as_target`;
      rename("/path/to/fonts/Fonts.swf", $swf_target);
      unlink($as_target);

And there you have it.  You’ll be left with a swf with the same name as your font in the /path/to/fonts directory.  Of course, you should probably do some error checking after the fact, but I found that this approach worked on about 99% of the fonts I compiled with this code.

Well… this has been my first ever blog post.  I’m going to keep at it and hopefully my writing will improve.  I hope someone finds this useful.