123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- <?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'];
- }
- }
- ?>
|