Blame | Letzte Änderung | Log anzeigen | RSS feed
<?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, rangeShiftfor($i=0;$i<$segCount;$i++)$endCount[$i] = $this->ReadUShort();$this->Skip(2); // reservedPadfor($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 namefseek($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");elsedie("<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'];}}?>