<?php
/*******************************************************************************
* Utility to generate font definition files                                    *
*                                                                              *
* Version: 1.14                                                                *
* Date:    2008-08-03                                                          *
* Author:  Olivier PLATHEY                                                     *
*******************************************************************************/

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 = [
        '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']) && 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']) && $fm['IsFixedPitch']) {
        $flags += 1 << 0;
    }
    if ($symbolic) {
        $flags += 1 << 2;
    }
    if (!$symbolic) {
        $flags += 1<< 5;
    }
    if (isset($fm['ItalicAngle']) && $fm['ItalicAngle']!=0) {
        $flags += 1 << 6;
    }
    $fd.=",'Flags'=>".$flags;
    //FontBBox
    if (isset($fm['FontBBox'])) {
        $fbb = $fm['FontBBox'];
    } else {
        $fbb = array(0,$desc-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']) && preg_match('/bold|black/i', $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 && $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)
{
    $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 && !$pp && !$e) {
        echo '<b>Warning:</b> font license does not allow embedding';
    }
}

/*******************************************************************************
* fontfile: path to TTF file (or empty string if not to be embedded)           *
* afmfile:  path to AFM file                                                   *
* enc:      font encoding (or empty string for symbolic fonts)                 *
* patch:    optional patch for encoding                                        *
* type:     font type if fontfile is empty                                     *
*******************************************************************************/
function makeFont($fontfile, $afmfile, $enc = 'cp1252', $patch = array(), $type = 'TrueType')
{
    
    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' && $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 && 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($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($basename.'.php', $s, 't');
    echo 'Font definition file generated ('.$basename.'.php'.')<br>';
}
