<?php

require_once(HTML2PS_DIR.'ot.class.php');

/**
 * @return Array font metrics hash or null of TTF file could not be parsed
 */
function ReadTTF($fontfile, $map) {
  if (!is_readable($fontfile)) { return null; };

  /**
   * Open font file and read metrics information
   */
  $font = new OpenTypeFile();
  $font->open($fontfile);
  
  $head =& $font->getTable('head');
  $name =& $font->getTable('name');
  $cmap =& $font->getTable('cmap');
  $hmtx =& $font->getTable('hmtx');
  $hhea =& $font->getTable('hhea');
  $post =& $font->getTable('post');
  $subtable =& $cmap->findSubtable(OT_CMAP_PLATFORM_WINDOWS,
                                   OT_CMAP_PLATFORM_WINDOWS_UNICODE);  
  
  /**
   * Prepare initial data
   */
  $widths = array();

  for ($i=0; $i<256; $i++) {
    $code = chr($i);
    if (!isset($map[$code])) {
      $widths[] = 1000;
      continue;
    };
    $ucs2 = $map[$code];

    /**
     * If the font is monospaced, only one entry need be in the array,
     * but  that entry  is required.  The  last entry  applies to  all
     * subsequent glyphs.
     */
    $glyphIndex = $subtable->lookup($ucs2);

    if (!is_null($glyphIndex)) {
      $realIndex = min($glyphIndex, $hhea->_numberOfHMetrics-1);
      $widths[]  = floor($hmtx->_hMetrics[$realIndex]['advanceWidth']*1000/$head->_unitsPerEm);
    } else {
      $widths[] = 1000;
    };
  };

  $font_info = array();

  /**
   * Here we use a hack; as, acording to OT specifications,
   *
   * When  translated  to  ASCII,  these  [...]  strings  must  be
   * identical; no  longer than 63  characters; and restricted  to the
   * printable ASCII subset,  codes 33 through 126, except  for the 10
   * characters: '[', ']', '(', ')', '{', '}', '<', '>', '/', '%'.
   *
   * we can assume that UCS-2 encoded string we receive can be easily 
   * translated to ASCII by removing the high-byte of all two-byte characters 
   */
  $ps_name_ucs2 = $name->lookup(OT_CMAP_PLATFORM_WINDOWS, 
                                OT_CMAP_PLATFORM_WINDOWS_UNICODE, 
                                OT_CMAP_LANGUAGE_WINDOWS_ENGLISH_AMERICAN, 
                                OT_NAME_ID_POSTSCRIPT_NAME);
  $ps_name_ascii = "";
  for ($i=0; $i<strlen($ps_name_ucs2); $i+=2) {
    $ps_name_ascii .= $ps_name_ucs2{$i+1};
  };

  $font_info['FontName']           = $ps_name_ascii;

  $font_info['Weight']             = $name->lookup(null, null, null, OT_NAME_ID_SUBFAMILY_NAME);
  $font_info['ItalicAngle']        = $post->_italicAngle;
  $font_info['IsFixedPitch']       = (bool)$post->_isFixedPitch;
  // $font_info['CapHeight']         
  // $font_info['StdVW']            
  $font_info['Ascender']           = floor($hhea->_ascender*1000/$head->_unitsPerEm);
  $font_info['Descender']          = floor($hhea->_descender*1000/$head->_unitsPerEm);
  $font_info['UnderlineThickness'] = floor($post->_underlineThickness*1000/$head->_unitsPerEm);
  $font_info['UnderlinePosition']  = floor($post->_underlinePosition*1000/$head->_unitsPerEm);
  $font_info['FontBBox']           = array($head->_xMin*1000/$head->_unitsPerEm,
                                           $head->_yMin*1000/$head->_unitsPerEm,
                                           $head->_xMax*1000/$head->_unitsPerEm,
                                           $head->_yMax*1000/$head->_unitsPerEm);
  $font_info['Widths']             = $widths;

  $font->_delete();
  unset($font);
  
  return $font_info;
}

/**
 * @return Array font metrics hash or null of AFM file is missing
 */
function ReadAFM($file, $map) {
  if (!is_readable($file)) { return null; };

  $afm_lines = file($file);
  $widths=array();
  $fm=array();

  foreach ($afm_lines 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';
      };
      
      $widths[$gn]=$w;

      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 (!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[chr($i)]])) {
      error_log('<B>Warning:</B> character '.$map[chr($i)].' is missing<BR>');
      $widths[$i]=$widths['.notdef'];
    } else {
      $widths[$i]=$widths[$map[chr($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
  $manager = ManagerEncoding::get();
  $ref = $manager->get_encoding_glyphs('windows-1252');

  $s='';
  $last=0;
  for($i=32;$i<=255;$i++) {
    if ($map[chr($i)]!=$ref[chr($i)]) {
      if ($i!=$last+1) {
        $s.=$i.' ';
      };
      $last=$i;
      $s.='/'.$map[chr($i)].' ';
    };
  }

  return rtrim($s);
}

function MakeFontCMap($encoding) {
  //Build differences from reference encoding
  $manager = ManagerEncoding::get();
  $ref = $manager->get_encoding_vector($encoding);

  $s  = "array(\n";
  foreach ($ref as $char => $ucs) {
    $s .= sprintf("0x%02X => 0x%04X,\n", ord($char), $ucs);
  };
  $s .= ")";

  return trim($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) {
  // Generate a font definition file
  set_magic_quotes_runtime(0);
  ini_set('auto_detect_line_endings','1');

  $manager = ManagerEncoding::get();
  $map     = $manager->get_encoding_glyphs($enc);

  $fm = ReadAFM($afmfile, $map);

  if (is_null($fm)) {
    error_log(sprintf("Notice: Missing AFM file '%s'; attempting to parse font file '%s' directly",
                      $afmfile,
                      $fontfile));
    
    $fm = ReadTTF($fontfile, $manager->get_encoding_vector($enc));

    if (is_null($fm)) {
      die(sprintf("Cannot get font metrics for '%s'", $fontfile));
    };
  }

  $diff = MakeFontEncoding($map);
  $cmap = MakeFontCMap($enc);
  $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";
  $s.='$cmap='.$cmap.";\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);
    }

    $gzcompress_exists = function_exists('gzcompress');
    if ($gzcompress_exists) {
      $cmp = $basename.'.z';
      SaveToFile($destdir.$cmp, gzcompress($file), 'b');

      $s.='$file=\''.$cmp."';\n";
    } else {
      $cmp = $basename.'.ttf';
      SaveToFile($destdir.$cmp, $file, 'b');

      $s.='$file=\''.basename($fontfile)."';\n";
      error_log('Notice: font file could not be compressed (zlib extension not available)');
    }
    
    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);
}
?>
