<?php
/*******************************************************************************
 * Utility to generate font definition files                                    *
 * Version: 1.13                                                                *
 * Date:    2004-12-31                                                          *
 *******************************************************************************/

function ReadMap($enc)
{
  //Read a map file
  $file=dirname(__FILE__).'/'.strtolower($enc).'.map';
  $a=file($file);
  if(empty($a))
    die('<B>Error:</B> encoding not found: '.$enc);
  $cc2gn=array();
  foreach($a as $l)
    {
      if($l{0}=='!')
        {
          $e=preg_split('/[ \\t]+/',rtrim($l));
          $cc=hexdec(substr($e[0],1));
          $gn=$e[2];
          $cc2gn[$cc]=$gn;
        }
    }
  for($i=0;$i<=255;$i++)
    {
      if(!isset($cc2gn[$i]))
        $cc2gn[$i]='.notdef';
    }
  return $cc2gn;
}

function ReadAFM($file,&$map)
{
  //Read a font metric file
  $a=file($file);
  if(empty($a))
    die('File not found');
  $widths=array();
  $fm=array();
  $fix=array('Edot'=>'Edotaccent','edot'=>'edotaccent','Idot'=>'Idotaccent','Zdot'=>'Zdotaccent','zdot'=>'zdotaccent',
             'Odblacute'=>'Ohungarumlaut','odblacute'=>'ohungarumlaut','Udblacute'=>'Uhungarumlaut','udblacute'=>'uhungarumlaut',
             'Gcedilla'=>'Gcommaaccent','gcedilla'=>'gcommaaccent','Kcedilla'=>'Kcommaaccent','kcedilla'=>'kcommaaccent',
             'Lcedilla'=>'Lcommaaccent','lcedilla'=>'lcommaaccent','Ncedilla'=>'Ncommaaccent','ncedilla'=>'ncommaaccent',
             'Rcedilla'=>'Rcommaaccent','rcedilla'=>'rcommaaccent','Scedilla'=>'Scommaaccent','scedilla'=>'scommaaccent',
             'Tcedilla'=>'Tcommaaccent','tcedilla'=>'tcommaaccent','Dslash'=>'Dcroat','dslash'=>'dcroat','Dmacron'=>'Dcroat','dmacron'=>'dcroat',
             'combininggraveaccent'=>'gravecomb','combininghookabove'=>'hookabovecomb','combiningtildeaccent'=>'tildecomb',
             'combiningacuteaccent'=>'acutecomb','combiningdotbelow'=>'dotbelowcomb','dongsign'=>'dong');
  foreach($a as $l)
    {
      $e=explode(' ',rtrim($l));
      if(count($e)<2)
        continue;
      $code=$e[0];
      $param=$e[1];
      if($code=='C')
        {
          //Character metrics
          $cc=(int)$e[1];
          $w=$e[4];
          $gn=$e[7];
          if(substr($gn,-4)=='20AC')
            $gn='Euro';
          if(isset($fix[$gn]))
            {
              //Fix incorrect glyph name
              foreach($map as $c=>$n)
                {
                  if($n==$fix[$gn])
                    $map[$c]=$gn;
                }
            }
          if(empty($map))
            {
              //Symbolic font: use built-in encoding
              $widths[$cc]=$w;
            }
          else
            {
              $widths[$gn]=$w;
              if($gn=='X')
                $fm['CapXHeight']=$e[13];
            }
          if($gn=='.notdef')
            $fm['MissingWidth']=$w;
        }
      elseif($code=='FontName')
        $fm['FontName']=$param;
      elseif($code=='Weight')
        $fm['Weight']=$param;
      elseif($code=='ItalicAngle')
        $fm['ItalicAngle']=(double)$param;
      elseif($code=='Ascender')
        $fm['Ascender']=(int)$param;
      elseif($code=='Descender')
        $fm['Descender']=(int)$param;
      elseif($code=='UnderlineThickness')
        $fm['UnderlineThickness']=(int)$param;
      elseif($code=='UnderlinePosition')
        $fm['UnderlinePosition']=(int)$param;
      elseif($code=='IsFixedPitch')
        $fm['IsFixedPitch']=($param=='true');
      elseif($code=='FontBBox')
        $fm['FontBBox']=array($e[1],$e[2],$e[3],$e[4]);
      elseif($code=='CapHeight')
        $fm['CapHeight']=(int)$param;
      elseif($code=='StdVW')
        $fm['StdVW']=(int)$param;
    }
  if(!isset($fm['FontName']))
    die('FontName not found');
  if(!empty($map))
    {
      if(!isset($widths['.notdef']))
        $widths['.notdef']=600;
      if(!isset($widths['Delta']) and isset($widths['increment']))
        $widths['Delta']=$widths['increment'];
      //Order widths according to map
      for($i=0;$i<=255;$i++)
        {
          if(!isset($widths[$map[$i]]))
            {
              echo '<B>Warning:</B> character '.$map[$i].' is missing<BR>';
              $widths[$i]=$widths['.notdef'];
            }
          else
            $widths[$i]=$widths[$map[$i]];
        }
    }
  $fm['Widths']=$widths;
  return $fm;
}

function MakeFontDescriptor($fm,$symbolic)
{
  //Ascent
  $asc=(isset($fm['Ascender']) ? $fm['Ascender'] : 1000);
  $fd="array('Ascent'=>".$asc;
  //Descent
  $desc=(isset($fm['Descender']) ? $fm['Descender'] : -200);
  $fd.=",'Descent'=>".$desc;
  //CapHeight
  if(isset($fm['CapHeight']))
    $ch=$fm['CapHeight'];
  elseif(isset($fm['CapXHeight']))
    $ch=$fm['CapXHeight'];
  else
    $ch=$asc;
  $fd.=",'CapHeight'=>".$ch;
  //Flags
  $flags=0;
  if(isset($fm['IsFixedPitch']) and $fm['IsFixedPitch'])
    $flags+=1<<0;
  if($symbolic)
    $flags+=1<<2;
  if(!$symbolic)
    $flags+=1<<5;
  if(isset($fm['ItalicAngle']) and $fm['ItalicAngle']!=0)
    $flags+=1<<6;
  $fd.=",'Flags'=>".$flags;
  //FontBBox
  if(isset($fm['FontBBox']))
    $fbb=$fm['FontBBox'];
  else
    $fbb=array(0,$des-100,1000,$asc+100);
  $fd.=",'FontBBox'=>'[".$fbb[0].' '.$fbb[1].' '.$fbb[2].' '.$fbb[3]."]'";
  //ItalicAngle
  $ia=(isset($fm['ItalicAngle']) ? $fm['ItalicAngle'] : 0);
  $fd.=",'ItalicAngle'=>".$ia;
  //StemV
  if(isset($fm['StdVW']))
    $stemv=$fm['StdVW'];
  elseif(isset($fm['Weight']) and eregi('(bold|black)',$fm['Weight']))
    $stemv=120;
  else
    $stemv=70;
  $fd.=",'StemV'=>".$stemv;
  //MissingWidth
  if(isset($fm['MissingWidth']))
    $fd.=",'MissingWidth'=>".$fm['MissingWidth'];
  $fd.=')';
  return $fd;
}

function MakeWidthArray($fm)
{
  //Make character width array
  $s="array(\n\t";
  $cw=$fm['Widths'];
  for($i=0;$i<=255;$i++)
    {
      if(chr($i)=="'")
        $s.="'\\''";
      elseif(chr($i)=="\\")
        $s.="'\\\\'";
      elseif($i>=32 and $i<=126)
        $s.="'".chr($i)."'";
      else
        $s.="chr($i)";
      $s.='=>'.$fm['Widths'][$i];
      if($i<255)
        $s.=',';
      if(($i+1)%22==0)
        $s.="\n\t";
    }
  $s.=')';
  return $s;
}

function MakeFontEncoding($map)
{
  //Build differences from reference encoding
  $ref=ReadMap('cp1252');
  $s='';
  $last=0;
  for($i=32;$i<=255;$i++)
    {
      if($map[$i]!=$ref[$i])
        {
          if($i!=$last+1)
            $s.=$i.' ';
          $last=$i;
          $s.='/'.$map[$i].' ';
        }
    }
  return rtrim($s);
}

function SaveToFile($file,$s,$mode='t')
{
  $f=fopen($file,'w'.$mode);
  if(!$f)
    die('Can\'t write to file '.$file);
  fwrite($f,$s,strlen($s));
  fclose($f);
}

function ReadShort($f)
{
  $a=unpack('n1n',fread($f,2));
  return $a['n'];
}

function ReadLong($f)
{
  $a=unpack('N1N',fread($f,4));
  return $a['N'];
}

function CheckTTF($file)
{
  //Check if font license allows embedding
  $f=fopen($file,'rb');
  if(!$f)
    die('<B>Error:</B> Can\'t open '.$file);
  //Extract number of tables
  fseek($f,4,SEEK_CUR);
  $nb=ReadShort($f);
  fseek($f,6,SEEK_CUR);
  //Seek OS/2 table
  $found=false;
  for($i=0;$i<$nb;$i++)
    {
      if(fread($f,4)=='OS/2')
        {
          $found=true;
          break;
        }
      fseek($f,12,SEEK_CUR);
    }
  if(!$found)
    {
      fclose($f);
      return;
    }
  fseek($f,4,SEEK_CUR);
  $offset=ReadLong($f);
  fseek($f,$offset,SEEK_SET);
  //Extract fsType flags
  fseek($f,8,SEEK_CUR);
  $fsType=ReadShort($f);
  $rl=($fsType & 0x02)!=0;
  $pp=($fsType & 0x04)!=0;
  $e=($fsType & 0x08)!=0;
  fclose($f);
  if($rl and !$pp and !$e)
    echo '<B>Warning:</B> font license does not allow embedding';
}

/*******************************************************************************
 * $fontfile : chemin du fichier TTF (ou chaîne vide si pas d'incorporation)    *
 * $afmfile :  chemin du fichier AFM                                            *
 * $enc :      encodage (ou chaîne vide si la police est symbolique)            *
 * $patch :    patch optionnel pour l'encodage                                  *
 * $type :     type de la police si $fontfile est vide                          *
 *******************************************************************************/
function MakeFont($fontfile,$afmfile,$destdir,$destfile,$enc='cp1252',$patch=array(),$type='TrueType')
{
  //Generate a font definition file
  set_magic_quotes_runtime(0);
  ini_set('auto_detect_line_endings','1');
  if($enc)
    {
      $map=ReadMap($enc);
      foreach($patch as $cc=>$gn)
        $map[$cc]=$gn;
    }
  else
    $map=array();
  if(!file_exists($afmfile))
    die('<B>Error:</B> AFM file not found: '.$afmfile);
  $fm=ReadAFM($afmfile,$map);
  if($enc)
    $diff=MakeFontEncoding($map);
  else
    $diff='';
  $fd=MakeFontDescriptor($fm,empty($map));
  //Find font type
  if($fontfile)
    {
      $ext=strtolower(substr($fontfile,-3));
      if($ext=='ttf')
        $type='TrueType';
      elseif($ext=='pfb')
        $type='Type1';
      else
        die('<B>Error:</B> unrecognized font file extension: '.$ext);
    }
  else
    {
      if($type!='TrueType' and $type!='Type1')
        die('<B>Error:</B> incorrect font type: '.$type);
    }
  //Start generation
  $s='<?php'."\n";
  $s.='$type=\''.$type."';\n";
  $s.='$name=\''.$fm['FontName']."';\n";
  $s.='$desc='.$fd.";\n";
  if(!isset($fm['UnderlinePosition']))
    $fm['UnderlinePosition']=-100;
  if(!isset($fm['UnderlineThickness']))
    $fm['UnderlineThickness']=50;
  $s.='$up='.$fm['UnderlinePosition'].";\n";
  $s.='$ut='.$fm['UnderlineThickness'].";\n";
  $w=MakeWidthArray($fm);
  $s.='$cw='.$w.";\n";
  $s.='$enc=\''.$enc."';\n";
  $s.='$diff=\''.$diff."';\n";
  $basename=substr(basename($afmfile),0,-4);
  if($fontfile)
    {
      //Embedded font
      if(!file_exists($fontfile))
        die('<B>Error:</B> font file not found: '.$fontfile);
      if($type=='TrueType')
        CheckTTF($fontfile);
      $f=fopen($fontfile,'rb');
      if(!$f)
        die('<B>Error:</B> Can\'t open '.$fontfile);
      $file=fread($f,filesize($fontfile));
      fclose($f);
      if($type=='Type1')
        {
          //Find first two sections and discard third one
          $header=(ord($file{0})==128);
          if($header)
            {
              //Strip first binary header
              $file=substr($file,6);
            }
          $pos=strpos($file,'eexec');
          if(!$pos)
            die('<B>Error:</B> font file does not seem to be valid Type1');
          $size1=$pos+6;
          if($header and ord($file{$size1})==128)
            {
              //Strip second binary header
              $file=substr($file,0,$size1).substr($file,$size1+6);
            }
          $pos=strpos($file,'00000000');
          if(!$pos)
            die('<B>Error:</B> font file does not seem to be valid Type1');
          $size2=$pos-$size1;
          $file=substr($file,0,$size1+$size2);
        }
      if(function_exists('gzcompress'))
        {
          $cmp=$basename.'.z';
          SaveToFile($destdir.$cmp,gzcompress($file),'b');
          $s.='$file=\''.$cmp."';\n";
          echo 'Font file compressed ('.$cmp.')<BR>';
        }
      else
        {
          $s.='$file=\''.basename($fontfile)."';\n";
          echo '<B>Notice:</B> font file could not be compressed (zlib extension not available)<BR>';
        }
      if($type=='Type1')
        {
          $s.='$size1='.$size1.";\n";
          $s.='$size2='.$size2.";\n";
        }
      else
        $s.='$originalsize='.filesize($fontfile).";\n";
    }
  else
    {
      //Not embedded font
      $s.='$file='."'';\n";
    }
  $s.="?>\n";
  SaveToFile($destdir.$destfile,$s);
  echo 'Font definition file generated ('.$basename.'.php'.')<BR>';
}
?>
