290 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			290 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
<?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'];
 | 
						|
	}
 | 
						|
}
 | 
						|
?>
 |