Tuesday, December 08, 2009

PHP Howto: Adding TrueType Fonts to FPDF Documents

I'm loving the FPDF PDF library for PHP. It's simple enough that I can hack it when needed, yet powerful enough that I don't usually need to do so.

Today I wanted to render some text in a non-standard font. And while there are instructions around for adding fonts to the system, I did find myself confused at times. Here then, is a general description of what I did to support using any TrueType font in a PDF.

Step 1: find some fonts. I grabbed a bunch of .ttf files from from C:/Windows/Fonts.

Step 2: Generate .afm files.. Cygwin (X-Windows, I believe) comes with a handy utility, ttf2afm.exe. I ran it on all the .ttf files I a grabbed like so:

 for f in *.ttf; do echo $f ; ttf2afm -o `basename $f ttf`afm $f ; done

Step 3: Prepare a PHP script to generate font metrics. FPDF comes with a file: font/makefont/makefont.php that accounts for all the dirty work of generating the required font metric files. However, I didn't see an easy way to run this from the command line. So, I whipped up this quick php script:

<?php
/*
 * mkfont.php - A PHP file for providing a command line access to makefont.php
 */
require_once(dirname(__FILE__) . '/makefont.php');
$afm_files = glob($argv[1]);
function ttf_file($f) {
  $ttf_name = basename($f, '.afm') . '.ttf';
  return file_exists($ttf_name) ? $ttf_name : '';
}
foreach($afm_files as $f) {
  MakeFont(ttf_file($f), $f);
}
?>

I ran this like so:

  php -f mkfont.php *.afm

Step 4: Move the generated files to the right location. For now I didn't get too fancy, I just copied the generated .z and .php files into the font subdirectory of FPDF.

Step 5: Prepare your code to know about the new font. You need to call AddFont on the FPDF object before you can use your new font.

As a lazy solution, I through together the following code. It loads all the fonts in your font directory. I don't suggest using this on production code, as it's no doubt slower than just hand loading the fonts you need. But still, if you just to load up all your fonts in one shot, here you go:


function fpdf_font_info($metric_file) {
  $name = false;
  require($metric_file);
  if($name) {
    return strpos($name, "-") === false ? array($name, 'Regular') : explode("-", $name);
  } else {
    return array(false, false);
  }
}

function register_fonts($fpdf) {
  $path = $fpdf->_getfontpath();
  $metric_files = glob("{$path}*.php");

  foreach($metric_files as $f) {
    list($base_name, $style_name) = fpdf_font_info($f);
    if($base_name) {
      switch($style_name) {
        case 'Bold':
          $style = 'B';
          break;
        case 'Italic':
          $style = 'I';
          break;
        case 'BoldItalic':
          $style = 'BI';
          break;
        default:
          $style = '';
      }
      $fpdf->AddFont($base_name, $style, basename($f));
    }
  }
}

You can use the above code by saying:

  $fpdf = new FPDF(...);
   register_fonts($fpdf);

Step 6: Use your newly installed fonts. Once you've done all the above, you can say:

  $fpdf = new FPDF(...);
  register_fonts($fpdf);
  // Assumes you've installed: bob_b.ttf
  $fpdf->SetFont('BodoniMT', 'B', 12);

Bonus: Listing all your installed font names. Step 6 assumes you know the correct name of the fonts you've installed. To get a full list, you can invoke the following function:

// Use fpdf_font_info from above

function available_fonts($fpdf) {
  $names = array();
  $path = $fpdf->_getfontpath();
  $metric_files = glob("{$path}*.php");

  foreach($metric_files as $f) {
    list($name, $style) = fpdf_font_info($f);
    if($name) {
      $names[] = "$name ($style)";
    }
  }

  return $names;
}

This function will return an array of string names of all fonts you've got installed.

Happy PDF'ing!

6 comments:

  1. Thanks for that I was looking for a simple solution to this issue and your example worked first time! Andy

    ReplyDelete
  2. Andy - I'm glad this was helpful!

    -Ben

    ReplyDelete
  3. It works just perfect. Thanks a lot for the information

    ReplyDelete
  4. Carlos - So glad this was helpful!

    ReplyDelete
  5. How can I make it work in Debian6

    ReplyDelete
  6. Thanks , works perfect

    ReplyDelete