PHP Classes

File: extSrc/makefont/ttfparser.php

Recommend this page to a friend!
  Classes of Vincenzo Di Biaggio   PHP Digital Download Script   extSrc/makefont/ttfparser.php   Download  
File: extSrc/makefont/ttfparser.php
Role: Auxiliary script
Content type: text/plain
Description: Auxiliary script
Class: PHP Digital Download Script
Serve files for download with codes given to users
Author: By
Last change: Update of extSrc/makefont/ttfparser.php
Date: 9 months ago
Size: 7,288 bytes
 

Contents

Class file image Download
<?php
/*******************************************************************************
* Utility to parse TTF font files *
* *
* Version: 1.0 *
* Date: 2011-06-18 *
* Author: Olivier PLATHEY *
*******************************************************************************/

class TTFParser
{
    var
$f;
    var
$tables;
    var
$unitsPerEm;
    var
$xMin, $yMin, $xMax, $yMax;
    var
$numberOfHMetrics;
    var
$numGlyphs;
    var
$widths;
    var
$chars;
    var
$postScriptName;
    var
$Embeddable;
    var
$Bold;
    var
$typoAscender;
    var
$typoDescender;
    var
$capHeight;
    var
$italicAngle;
    var
$underlinePosition;
    var
$underlineThickness;
    var
$isFixedPitch;

    function
Parse($file)
    {
       
$this->f = fopen($file, 'rb');
        if(!
$this->f)
           
$this->Error('Can\'t open file: '.$file);

       
$version = $this->Read(4);
        if(
$version=='OTTO')
           
$this->Error('OpenType fonts based on PostScript outlines are not supported');
        if(
$version!="\x00\x01\x00\x00")
           
$this->Error('Unrecognized file format');
       
$numTables = $this->ReadUShort();
       
$this->Skip(3*2); // searchRange, entrySelector, rangeShift
       
$this->tables = array();
        for(
$i=0;$i<$numTables;$i++)
        {
           
$tag = $this->Read(4);
           
$this->Skip(4); // checkSum
           
$offset = $this->ReadULong();
           
$this->Skip(4); // length
           
$this->tables[$tag] = $offset;
        }

       
$this->ParseHead();
       
$this->ParseHhea();
       
$this->ParseMaxp();
       
$this->ParseHmtx();
       
$this->ParseCmap();
       
$this->ParseName();
       
$this->ParseOS2();
       
$this->ParsePost();

       
fclose($this->f);
    }

    function
ParseHead()
    {
       
$this->Seek('head');
       
$this->Skip(3*4); // version, fontRevision, checkSumAdjustment
       
$magicNumber = $this->ReadULong();
        if(
$magicNumber!=0x5F0F3CF5)
           
$this->Error('Incorrect magic number');
       
$this->Skip(2); // flags
       
$this->unitsPerEm = $this->ReadUShort();
       
$this->Skip(2*8); // created, modified
       
$this->xMin = $this->ReadShort();
       
$this->yMin = $this->ReadShort();
       
$this->xMax = $this->ReadShort();
       
$this->yMax = $this->ReadShort();
    }

    function
ParseHhea()
    {
       
$this->Seek('hhea');
       
$this->Skip(4+15*2);
       
$this->numberOfHMetrics = $this->ReadUShort();
    }

    function
ParseMaxp()
    {
       
$this->Seek('maxp');
       
$this->Skip(4);
       
$this->numGlyphs = $this->ReadUShort();
    }

    function
ParseHmtx()
    {
       
$this->Seek('hmtx');
       
$this->widths = array();
        for(
$i=0;$i<$this->numberOfHMetrics;$i++)
        {
           
$advanceWidth = $this->ReadUShort();
           
$this->Skip(2); // lsb
           
$this->widths[$i] = $advanceWidth;
        }
        if(
$this->numberOfHMetrics<$this->numGlyphs)
        {
           
$lastWidth = $this->widths[$this->numberOfHMetrics-1];
           
$this->widths = array_pad($this->widths, $this->numGlyphs, $lastWidth);
        }
    }

    function
ParseCmap()
    {
       
$this->Seek('cmap');
       
$this->Skip(2); // version
       
$numTables = $this->ReadUShort();
       
$offset31 = 0;
        for(
$i=0;$i<$numTables;$i++)
        {
           
$platformID = $this->ReadUShort();
           
$encodingID = $this->ReadUShort();
           
$offset = $this->ReadULong();
            if(
$platformID==3 && $encodingID==1)
               
$offset31 = $offset;
        }
        if(
$offset31==0)
           
$this->Error('No Unicode encoding found');

       
$startCount = array();
       
$endCount = array();
       
$idDelta = array();
       
$idRangeOffset = array();
       
$this->chars = array();
       
fseek($this->f, $this->tables['cmap']+$offset31, SEEK_SET);
       
$format = $this->ReadUShort();
        if(
$format!=4)
           
$this->Error('Unexpected subtable format: '.$format);
       
$this->Skip(2*2); // length, language
       
$segCount = $this->ReadUShort()/2;
       
$this->Skip(3*2); // searchRange, entrySelector, rangeShift
       
for($i=0;$i<$segCount;$i++)
           
$endCount[$i] = $this->ReadUShort();
       
$this->Skip(2); // reservedPad
       
for($i=0;$i<$segCount;$i++)
           
$startCount[$i] = $this->ReadUShort();
        for(
$i=0;$i<$segCount;$i++)
           
$idDelta[$i] = $this->ReadShort();
       
$offset = ftell($this->f);
        for(
$i=0;$i<$segCount;$i++)
           
$idRangeOffset[$i] = $this->ReadUShort();

        for(
$i=0;$i<$segCount;$i++)
        {
           
$c1 = $startCount[$i];
           
$c2 = $endCount[$i];
           
$d = $idDelta[$i];
           
$ro = $idRangeOffset[$i];
            if(
$ro>0)
               
fseek($this->f, $offset+2*$i+$ro, SEEK_SET);
            for(
$c=$c1;$c<=$c2;$c++)
            {
                if(
$c==0xFFFF)
                    break;
                if(
$ro>0)
                {
                   
$gid = $this->ReadUShort();
                    if(
$gid>0)
                       
$gid += $d;
                }
                else
                   
$gid = $c+$d;
                if(
$gid>=65536)
                   
$gid -= 65536;
                if(
$gid>0)
                   
$this->chars[$c] = $gid;
            }
        }
    }

    function
ParseName()
    {
       
$this->Seek('name');
       
$tableOffset = ftell($this->f);
       
$this->postScriptName = '';
       
$this->Skip(2); // format
       
$count = $this->ReadUShort();
       
$stringOffset = $this->ReadUShort();
        for(
$i=0;$i<$count;$i++)
        {
           
$this->Skip(3*2); // platformID, encodingID, languageID
           
$nameID = $this->ReadUShort();
           
$length = $this->ReadUShort();
           
$offset = $this->ReadUShort();
            if(
$nameID==6)
            {
               
// PostScript name
               
fseek($this->f, $tableOffset+$stringOffset+$offset, SEEK_SET);
               
$s = $this->Read($length);
               
$s = str_replace(chr(0), '', $s);
               
$s = preg_replace('|[ \[\](){}<>/%]|', '', $s);
               
$this->postScriptName = $s;
                break;
            }
        }
        if(
$this->postScriptName=='')
           
$this->Error('PostScript name not found');
    }

    function
ParseOS2()
    {
       
$this->Seek('OS/2');
       
$version = $this->ReadUShort();
       
$this->Skip(3*2); // xAvgCharWidth, usWeightClass, usWidthClass
       
$fsType = $this->ReadUShort();
       
$this->Embeddable = ($fsType!=2) && ($fsType & 0x200)==0;
       
$this->Skip(11*2+10+4*4+4);
       
$fsSelection = $this->ReadUShort();
       
$this->Bold = ($fsSelection & 32)!=0;
       
$this->Skip(2*2); // usFirstCharIndex, usLastCharIndex
       
$this->typoAscender = $this->ReadShort();
       
$this->typoDescender = $this->ReadShort();
        if(
$version>=2)
        {
           
$this->Skip(3*2+2*4+2);
           
$this->capHeight = $this->ReadShort();
        }
        else
           
$this->capHeight = 0;
    }

    function
ParsePost()
    {
       
$this->Seek('post');
       
$this->Skip(4); // version
       
$this->italicAngle = $this->ReadShort();
       
$this->Skip(2); // Skip decimal part
       
$this->underlinePosition = $this->ReadShort();
       
$this->underlineThickness = $this->ReadShort();
       
$this->isFixedPitch = ($this->ReadULong()!=0);
    }

    function
Error($msg)
    {
        if(
PHP_SAPI=='cli')
            die(
"Error: $msg\n");
        else
            die(
"<b>Error</b>: $msg");
    }

    function
Seek($tag)
    {
        if(!isset(
$this->tables[$tag]))
           
$this->Error('Table not found: '.$tag);
       
fseek($this->f, $this->tables[$tag], SEEK_SET);
    }

    function
Skip($n)
    {
       
fseek($this->f, $n, SEEK_CUR);
    }

    function
Read($n)
    {
        return
fread($this->f, $n);
    }

    function
ReadUShort()
    {
       
$a = unpack('nn', fread($this->f,2));
        return
$a['n'];
    }

    function
ReadShort()
    {
       
$a = unpack('nn', fread($this->f,2));
       
$v = $a['n'];
        if(
$v>=0x8000)
           
$v -= 65536;
        return
$v;
    }

    function
ReadULong()
    {
       
$a = unpack('NN', fread($this->f,4));
        return
$a['N'];
    }
}
?>