ttfparser.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. <?php
  2. /*******************************************************************************
  3. * Class to parse and subset TrueType fonts *
  4. * *
  5. * Version: 1.1 *
  6. * Date: 2015-11-29 *
  7. * Author: Olivier PLATHEY *
  8. *******************************************************************************/
  9. class TTFParser
  10. {
  11. protected $f;
  12. protected $tables;
  13. protected $numberOfHMetrics;
  14. protected $numGlyphs;
  15. protected $glyphNames;
  16. protected $indexToLocFormat;
  17. protected $subsettedChars;
  18. protected $subsettedGlyphs;
  19. public $chars;
  20. public $glyphs;
  21. public $unitsPerEm;
  22. public $xMin, $yMin, $xMax, $yMax;
  23. public $postScriptName;
  24. public $embeddable;
  25. public $bold;
  26. public $typoAscender;
  27. public $typoDescender;
  28. public $capHeight;
  29. public $italicAngle;
  30. public $underlinePosition;
  31. public $underlineThickness;
  32. public $isFixedPitch;
  33. function __construct($file)
  34. {
  35. $this->f = fopen($file, 'rb');
  36. if(!$this->f)
  37. $this->Error('Can\'t open file: '.$file);
  38. }
  39. function __destruct()
  40. {
  41. if(is_resource($this->f))
  42. fclose($this->f);
  43. }
  44. function Parse()
  45. {
  46. $this->ParseOffsetTable();
  47. $this->ParseHead();
  48. $this->ParseHhea();
  49. $this->ParseMaxp();
  50. $this->ParseHmtx();
  51. $this->ParseLoca();
  52. $this->ParseGlyf();
  53. $this->ParseCmap();
  54. $this->ParseName();
  55. $this->ParseOS2();
  56. $this->ParsePost();
  57. }
  58. function ParseOffsetTable()
  59. {
  60. $version = $this->Read(4);
  61. if($version=='OTTO')
  62. $this->Error('OpenType fonts based on PostScript outlines are not supported');
  63. if($version!="\x00\x01\x00\x00")
  64. $this->Error('Unrecognized file format');
  65. $numTables = $this->ReadUShort();
  66. $this->Skip(3*2); // searchRange, entrySelector, rangeShift
  67. $this->tables = array();
  68. for($i=0;$i<$numTables;$i++)
  69. {
  70. $tag = $this->Read(4);
  71. $checkSum = $this->Read(4);
  72. $offset = $this->ReadULong();
  73. $length = $this->ReadULong(4);
  74. $this->tables[$tag] = array('offset'=>$offset, 'length'=>$length, 'checkSum'=>$checkSum);
  75. }
  76. }
  77. function ParseHead()
  78. {
  79. $this->Seek('head');
  80. $this->Skip(3*4); // version, fontRevision, checkSumAdjustment
  81. $magicNumber = $this->ReadULong();
  82. if($magicNumber!=0x5F0F3CF5)
  83. $this->Error('Incorrect magic number');
  84. $this->Skip(2); // flags
  85. $this->unitsPerEm = $this->ReadUShort();
  86. $this->Skip(2*8); // created, modified
  87. $this->xMin = $this->ReadShort();
  88. $this->yMin = $this->ReadShort();
  89. $this->xMax = $this->ReadShort();
  90. $this->yMax = $this->ReadShort();
  91. $this->Skip(3*2); // macStyle, lowestRecPPEM, fontDirectionHint
  92. $this->indexToLocFormat = $this->ReadShort();
  93. }
  94. function ParseHhea()
  95. {
  96. $this->Seek('hhea');
  97. $this->Skip(4+15*2);
  98. $this->numberOfHMetrics = $this->ReadUShort();
  99. }
  100. function ParseMaxp()
  101. {
  102. $this->Seek('maxp');
  103. $this->Skip(4);
  104. $this->numGlyphs = $this->ReadUShort();
  105. }
  106. function ParseHmtx()
  107. {
  108. $this->Seek('hmtx');
  109. $this->glyphs = array();
  110. for($i=0;$i<$this->numberOfHMetrics;$i++)
  111. {
  112. $advanceWidth = $this->ReadUShort();
  113. $lsb = $this->ReadShort();
  114. $this->glyphs[$i] = array('w'=>$advanceWidth, 'lsb'=>$lsb);
  115. }
  116. for($i=$this->numberOfHMetrics;$i<$this->numGlyphs;$i++)
  117. {
  118. $lsb = $this->ReadShort();
  119. $this->glyphs[$i] = array('w'=>$advanceWidth, 'lsb'=>$lsb);
  120. }
  121. }
  122. function ParseLoca()
  123. {
  124. $this->Seek('loca');
  125. $offsets = array();
  126. if($this->indexToLocFormat==0)
  127. {
  128. // Short format
  129. for($i=0;$i<=$this->numGlyphs;$i++)
  130. $offsets[] = 2*$this->ReadUShort();
  131. }
  132. else
  133. {
  134. // Long format
  135. for($i=0;$i<=$this->numGlyphs;$i++)
  136. $offsets[] = $this->ReadULong();
  137. }
  138. for($i=0;$i<$this->numGlyphs;$i++)
  139. {
  140. $this->glyphs[$i]['offset'] = $offsets[$i];
  141. $this->glyphs[$i]['length'] = $offsets[$i+1] - $offsets[$i];
  142. }
  143. }
  144. function ParseGlyf()
  145. {
  146. $tableOffset = $this->tables['glyf']['offset'];
  147. foreach($this->glyphs as &$glyph)
  148. {
  149. if($glyph['length']>0)
  150. {
  151. fseek($this->f, $tableOffset+$glyph['offset'], SEEK_SET);
  152. if($this->ReadShort()<0)
  153. {
  154. // Composite glyph
  155. $this->Skip(4*2); // xMin, yMin, xMax, yMax
  156. $offset = 5*2;
  157. $a = array();
  158. do
  159. {
  160. $flags = $this->ReadUShort();
  161. $index = $this->ReadUShort();
  162. $a[$offset+2] = $index;
  163. if($flags & 1) // ARG_1_AND_2_ARE_WORDS
  164. $skip = 2*2;
  165. else
  166. $skip = 2;
  167. if($flags & 8) // WE_HAVE_A_SCALE
  168. $skip += 2;
  169. elseif($flags & 64) // WE_HAVE_AN_X_AND_Y_SCALE
  170. $skip += 2*2;
  171. elseif($flags & 128) // WE_HAVE_A_TWO_BY_TWO
  172. $skip += 4*2;
  173. $this->Skip($skip);
  174. $offset += 2*2 + $skip;
  175. }
  176. while($flags & 32); // MORE_COMPONENTS
  177. $glyph['components'] = $a;
  178. }
  179. }
  180. }
  181. }
  182. function ParseCmap()
  183. {
  184. $this->Seek('cmap');
  185. $this->Skip(2); // version
  186. $numTables = $this->ReadUShort();
  187. $offset31 = 0;
  188. for($i=0;$i<$numTables;$i++)
  189. {
  190. $platformID = $this->ReadUShort();
  191. $encodingID = $this->ReadUShort();
  192. $offset = $this->ReadULong();
  193. if($platformID==3 && $encodingID==1)
  194. $offset31 = $offset;
  195. }
  196. if($offset31==0)
  197. $this->Error('No Unicode encoding found');
  198. $startCount = array();
  199. $endCount = array();
  200. $idDelta = array();
  201. $idRangeOffset = array();
  202. $this->chars = array();
  203. fseek($this->f, $this->tables['cmap']['offset']+$offset31, SEEK_SET);
  204. $format = $this->ReadUShort();
  205. if($format!=4)
  206. $this->Error('Unexpected subtable format: '.$format);
  207. $this->Skip(2*2); // length, language
  208. $segCount = $this->ReadUShort()/2;
  209. $this->Skip(3*2); // searchRange, entrySelector, rangeShift
  210. for($i=0;$i<$segCount;$i++)
  211. $endCount[$i] = $this->ReadUShort();
  212. $this->Skip(2); // reservedPad
  213. for($i=0;$i<$segCount;$i++)
  214. $startCount[$i] = $this->ReadUShort();
  215. for($i=0;$i<$segCount;$i++)
  216. $idDelta[$i] = $this->ReadShort();
  217. $offset = ftell($this->f);
  218. for($i=0;$i<$segCount;$i++)
  219. $idRangeOffset[$i] = $this->ReadUShort();
  220. for($i=0;$i<$segCount;$i++)
  221. {
  222. $c1 = $startCount[$i];
  223. $c2 = $endCount[$i];
  224. $d = $idDelta[$i];
  225. $ro = $idRangeOffset[$i];
  226. if($ro>0)
  227. fseek($this->f, $offset+2*$i+$ro, SEEK_SET);
  228. for($c=$c1;$c<=$c2;$c++)
  229. {
  230. if($c==0xFFFF)
  231. break;
  232. if($ro>0)
  233. {
  234. $gid = $this->ReadUShort();
  235. if($gid>0)
  236. $gid += $d;
  237. }
  238. else
  239. $gid = $c+$d;
  240. if($gid>=65536)
  241. $gid -= 65536;
  242. if($gid>0)
  243. $this->chars[$c] = $gid;
  244. }
  245. }
  246. }
  247. function ParseName()
  248. {
  249. $this->Seek('name');
  250. $tableOffset = $this->tables['name']['offset'];
  251. $this->postScriptName = '';
  252. $this->Skip(2); // format
  253. $count = $this->ReadUShort();
  254. $stringOffset = $this->ReadUShort();
  255. for($i=0;$i<$count;$i++)
  256. {
  257. $this->Skip(3*2); // platformID, encodingID, languageID
  258. $nameID = $this->ReadUShort();
  259. $length = $this->ReadUShort();
  260. $offset = $this->ReadUShort();
  261. if($nameID==6)
  262. {
  263. // PostScript name
  264. fseek($this->f, $tableOffset+$stringOffset+$offset, SEEK_SET);
  265. $s = $this->Read($length);
  266. $s = str_replace(chr(0), '', $s);
  267. $s = preg_replace('|[ \[\](){}<>/%]|', '', $s);
  268. $this->postScriptName = $s;
  269. break;
  270. }
  271. }
  272. if($this->postScriptName=='')
  273. $this->Error('PostScript name not found');
  274. }
  275. function ParseOS2()
  276. {
  277. $this->Seek('OS/2');
  278. $version = $this->ReadUShort();
  279. $this->Skip(3*2); // xAvgCharWidth, usWeightClass, usWidthClass
  280. $fsType = $this->ReadUShort();
  281. $this->embeddable = ($fsType!=2) && ($fsType & 0x200)==0;
  282. $this->Skip(11*2+10+4*4+4);
  283. $fsSelection = $this->ReadUShort();
  284. $this->bold = ($fsSelection & 32)!=0;
  285. $this->Skip(2*2); // usFirstCharIndex, usLastCharIndex
  286. $this->typoAscender = $this->ReadShort();
  287. $this->typoDescender = $this->ReadShort();
  288. if($version>=2)
  289. {
  290. $this->Skip(3*2+2*4+2);
  291. $this->capHeight = $this->ReadShort();
  292. }
  293. else
  294. $this->capHeight = 0;
  295. }
  296. function ParsePost()
  297. {
  298. $this->Seek('post');
  299. $version = $this->ReadULong();
  300. $this->italicAngle = $this->ReadShort();
  301. $this->Skip(2); // Skip decimal part
  302. $this->underlinePosition = $this->ReadShort();
  303. $this->underlineThickness = $this->ReadShort();
  304. $this->isFixedPitch = ($this->ReadULong()!=0);
  305. if($version==0x20000)
  306. {
  307. // Extract glyph names
  308. $this->Skip(4*4); // min/max usage
  309. $this->Skip(2); // numberOfGlyphs
  310. $glyphNameIndex = array();
  311. $names = array();
  312. $numNames = 0;
  313. for($i=0;$i<$this->numGlyphs;$i++)
  314. {
  315. $index = $this->ReadUShort();
  316. $glyphNameIndex[] = $index;
  317. if($index>=258 && $index-257>$numNames)
  318. $numNames = $index-257;
  319. }
  320. for($i=0;$i<$numNames;$i++)
  321. {
  322. $len = ord($this->Read(1));
  323. $names[] = $this->Read($len);
  324. }
  325. foreach($glyphNameIndex as $i=>$index)
  326. {
  327. if($index>=258)
  328. $this->glyphs[$i]['name'] = $names[$index-258];
  329. else
  330. $this->glyphs[$i]['name'] = $index;
  331. }
  332. $this->glyphNames = true;
  333. }
  334. else
  335. $this->glyphNames = false;
  336. }
  337. function Subset($chars)
  338. {
  339. /* $chars = array_keys($this->chars);
  340. $this->subsettedChars = $chars;
  341. $this->subsettedGlyphs = array();
  342. for($i=0;$i<$this->numGlyphs;$i++)
  343. {
  344. $this->subsettedGlyphs[] = $i;
  345. $this->glyphs[$i]['ssid'] = $i;
  346. }*/
  347. $this->AddGlyph(0);
  348. $this->subsettedChars = array();
  349. foreach($chars as $char)
  350. {
  351. if(isset($this->chars[$char]))
  352. {
  353. $this->subsettedChars[] = $char;
  354. $this->AddGlyph($this->chars[$char]);
  355. }
  356. }
  357. }
  358. function AddGlyph($id)
  359. {
  360. if(!isset($this->glyphs[$id]['ssid']))
  361. {
  362. $this->glyphs[$id]['ssid'] = count($this->subsettedGlyphs);
  363. $this->subsettedGlyphs[] = $id;
  364. if(isset($this->glyphs[$id]['components']))
  365. {
  366. foreach($this->glyphs[$id]['components'] as $cid)
  367. $this->AddGlyph($cid);
  368. }
  369. }
  370. }
  371. function Build()
  372. {
  373. $this->BuildCmap();
  374. $this->BuildHhea();
  375. $this->BuildHmtx();
  376. $this->BuildLoca();
  377. $this->BuildGlyf();
  378. $this->BuildMaxp();
  379. $this->BuildPost();
  380. return $this->BuildFont();
  381. }
  382. function BuildCmap()
  383. {
  384. if(!isset($this->subsettedChars))
  385. return;
  386. // Divide charset in contiguous segments
  387. $chars = $this->subsettedChars;
  388. sort($chars);
  389. $segments = array();
  390. $segment = array($chars[0], $chars[0]);
  391. for($i=1;$i<count($chars);$i++)
  392. {
  393. if($chars[$i]>$segment[1]+1)
  394. {
  395. $segments[] = $segment;
  396. $segment = array($chars[$i], $chars[$i]);
  397. }
  398. else
  399. $segment[1]++;
  400. }
  401. $segments[] = $segment;
  402. $segments[] = array(0xFFFF, 0xFFFF);
  403. $segCount = count($segments);
  404. // Build a Format 4 subtable
  405. $startCount = array();
  406. $endCount = array();
  407. $idDelta = array();
  408. $idRangeOffset = array();
  409. $glyphIdArray = '';
  410. for($i=0;$i<$segCount;$i++)
  411. {
  412. list($start, $end) = $segments[$i];
  413. $startCount[] = $start;
  414. $endCount[] = $end;
  415. if($start!=$end)
  416. {
  417. // Segment with multiple chars
  418. $idDelta[] = 0;
  419. $idRangeOffset[] = strlen($glyphIdArray) + ($segCount-$i)*2;
  420. for($c=$start;$c<=$end;$c++)
  421. {
  422. $ssid = $this->glyphs[$this->chars[$c]]['ssid'];
  423. $glyphIdArray .= pack('n', $ssid);
  424. }
  425. }
  426. else
  427. {
  428. // Segment with a single char
  429. if($start<0xFFFF)
  430. $ssid = $this->glyphs[$this->chars[$start]]['ssid'];
  431. else
  432. $ssid = 0;
  433. $idDelta[] = $ssid - $start;
  434. $idRangeOffset[] = 0;
  435. }
  436. }
  437. $entrySelector = 0;
  438. $n = $segCount;
  439. while($n!=1)
  440. {
  441. $n = $n>>1;
  442. $entrySelector++;
  443. }
  444. $searchRange = (1<<$entrySelector)*2;
  445. $rangeShift = 2*$segCount - $searchRange;
  446. $cmap = pack('nnnn', 2*$segCount, $searchRange, $entrySelector, $rangeShift);
  447. foreach($endCount as $val)
  448. $cmap .= pack('n', $val);
  449. $cmap .= pack('n', 0); // reservedPad
  450. foreach($startCount as $val)
  451. $cmap .= pack('n', $val);
  452. foreach($idDelta as $val)
  453. $cmap .= pack('n', $val);
  454. foreach($idRangeOffset as $val)
  455. $cmap .= pack('n', $val);
  456. $cmap .= $glyphIdArray;
  457. $data = pack('nn', 0, 1); // version, numTables
  458. $data .= pack('nnN', 3, 1, 12); // platformID, encodingID, offset
  459. $data .= pack('nnn', 4, 6+strlen($cmap), 0); // format, length, language
  460. $data .= $cmap;
  461. $this->SetTable('cmap', $data);
  462. }
  463. function BuildHhea()
  464. {
  465. $this->LoadTable('hhea');
  466. $numberOfHMetrics = count($this->subsettedGlyphs);
  467. $data = substr_replace($this->tables['hhea']['data'], pack('n',$numberOfHMetrics), 4+15*2, 2);
  468. $this->SetTable('hhea', $data);
  469. }
  470. function BuildHmtx()
  471. {
  472. $data = '';
  473. foreach($this->subsettedGlyphs as $id)
  474. {
  475. $glyph = $this->glyphs[$id];
  476. $data .= pack('nn', $glyph['w'], $glyph['lsb']);
  477. }
  478. $this->SetTable('hmtx', $data);
  479. }
  480. function BuildLoca()
  481. {
  482. $data = '';
  483. $offset = 0;
  484. foreach($this->subsettedGlyphs as $id)
  485. {
  486. if($this->indexToLocFormat==0)
  487. $data .= pack('n', $offset/2);
  488. else
  489. $data .= pack('N', $offset);
  490. $offset += $this->glyphs[$id]['length'];
  491. }
  492. if($this->indexToLocFormat==0)
  493. $data .= pack('n', $offset/2);
  494. else
  495. $data .= pack('N', $offset);
  496. $this->SetTable('loca', $data);
  497. }
  498. function BuildGlyf()
  499. {
  500. $tableOffset = $this->tables['glyf']['offset'];
  501. $data = '';
  502. foreach($this->subsettedGlyphs as $id)
  503. {
  504. $glyph = $this->glyphs[$id];
  505. fseek($this->f, $tableOffset+$glyph['offset'], SEEK_SET);
  506. $glyph_data = $this->Read($glyph['length']);
  507. if(isset($glyph['components']))
  508. {
  509. // Composite glyph
  510. foreach($glyph['components'] as $offset=>$cid)
  511. {
  512. $ssid = $this->glyphs[$cid]['ssid'];
  513. $glyph_data = substr_replace($glyph_data, pack('n',$ssid), $offset, 2);
  514. }
  515. }
  516. $data .= $glyph_data;
  517. }
  518. $this->SetTable('glyf', $data);
  519. }
  520. function BuildMaxp()
  521. {
  522. $this->LoadTable('maxp');
  523. $numGlyphs = count($this->subsettedGlyphs);
  524. $data = substr_replace($this->tables['maxp']['data'], pack('n',$numGlyphs), 4, 2);
  525. $this->SetTable('maxp', $data);
  526. }
  527. function BuildPost()
  528. {
  529. $this->Seek('post');
  530. if($this->glyphNames)
  531. {
  532. // Version 2.0
  533. $numberOfGlyphs = count($this->subsettedGlyphs);
  534. $numNames = 0;
  535. $names = '';
  536. $data = $this->Read(2*4+2*2+5*4);
  537. $data .= pack('n', $numberOfGlyphs);
  538. foreach($this->subsettedGlyphs as $id)
  539. {
  540. $name = $this->glyphs[$id]['name'];
  541. if(is_string($name))
  542. {
  543. $data .= pack('n', 258+$numNames);
  544. $names .= chr(strlen($name)).$name;
  545. $numNames++;
  546. }
  547. else
  548. $data .= pack('n', $name);
  549. }
  550. $data .= $names;
  551. }
  552. else
  553. {
  554. // Version 3.0
  555. $this->Skip(4);
  556. $data = "\x00\x03\x00\x00";
  557. $data .= $this->Read(4+2*2+5*4);
  558. }
  559. $this->SetTable('post', $data);
  560. }
  561. function BuildFont()
  562. {
  563. $tags = array();
  564. foreach(array('cmap', 'cvt ', 'fpgm', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'name', 'post', 'prep') as $tag)
  565. {
  566. if(isset($this->tables[$tag]))
  567. $tags[] = $tag;
  568. }
  569. $numTables = count($tags);
  570. $offset = 12 + 16*$numTables;
  571. foreach($tags as $tag)
  572. {
  573. if(!isset($this->tables[$tag]['data']))
  574. $this->LoadTable($tag);
  575. $this->tables[$tag]['offset'] = $offset;
  576. $offset += strlen($this->tables[$tag]['data']);
  577. }
  578. // $this->tables['head']['data'] = substr_replace($this->tables['head']['data'], "\x00\x00\x00\x00", 8, 4);
  579. // Build offset table
  580. $entrySelector = 0;
  581. $n = $numTables;
  582. while($n!=1)
  583. {
  584. $n = $n>>1;
  585. $entrySelector++;
  586. }
  587. $searchRange = 16*(1<<$entrySelector);
  588. $rangeShift = 16*$numTables - $searchRange;
  589. $offsetTable = pack('nnnnnn', 1, 0, $numTables, $searchRange, $entrySelector, $rangeShift);
  590. foreach($tags as $tag)
  591. {
  592. $table = $this->tables[$tag];
  593. $offsetTable .= $tag.$table['checkSum'].pack('NN', $table['offset'], $table['length']);
  594. }
  595. // Compute checkSumAdjustment (0xB1B0AFBA - font checkSum)
  596. $s = $this->CheckSum($offsetTable);
  597. foreach($tags as $tag)
  598. $s .= $this->tables[$tag]['checkSum'];
  599. $a = unpack('n2', $this->CheckSum($s));
  600. $high = 0xB1B0 + ($a[1]^0xFFFF);
  601. $low = 0xAFBA + ($a[2]^0xFFFF) + 1;
  602. $checkSumAdjustment = pack('nn', $high+($low>>16), $low);
  603. $this->tables['head']['data'] = substr_replace($this->tables['head']['data'], $checkSumAdjustment, 8, 4);
  604. $font = $offsetTable;
  605. foreach($tags as $tag)
  606. $font .= $this->tables[$tag]['data'];
  607. return $font;
  608. }
  609. function LoadTable($tag)
  610. {
  611. $this->Seek($tag);
  612. $length = $this->tables[$tag]['length'];
  613. $n = $length % 4;
  614. if($n>0)
  615. $length += 4 - $n;
  616. $this->tables[$tag]['data'] = $this->Read($length);
  617. }
  618. function SetTable($tag, $data)
  619. {
  620. $length = strlen($data);
  621. $n = $length % 4;
  622. if($n>0)
  623. $data = str_pad($data, $length+4-$n, "\x00");
  624. $this->tables[$tag]['data'] = $data;
  625. $this->tables[$tag]['length'] = $length;
  626. $this->tables[$tag]['checkSum'] = $this->CheckSum($data);
  627. }
  628. function Seek($tag)
  629. {
  630. if(!isset($this->tables[$tag]))
  631. $this->Error('Table not found: '.$tag);
  632. fseek($this->f, $this->tables[$tag]['offset'], SEEK_SET);
  633. }
  634. function Skip($n)
  635. {
  636. fseek($this->f, $n, SEEK_CUR);
  637. }
  638. function Read($n)
  639. {
  640. return $n>0 ? fread($this->f, $n) : '';
  641. }
  642. function ReadUShort()
  643. {
  644. $a = unpack('nn', fread($this->f,2));
  645. return $a['n'];
  646. }
  647. function ReadShort()
  648. {
  649. $a = unpack('nn', fread($this->f,2));
  650. $v = $a['n'];
  651. if($v>=0x8000)
  652. $v -= 65536;
  653. return $v;
  654. }
  655. function ReadULong()
  656. {
  657. $a = unpack('NN', fread($this->f,4));
  658. return $a['N'];
  659. }
  660. function CheckSum($s)
  661. {
  662. $n = strlen($s);
  663. $high = 0;
  664. $low = 0;
  665. for($i=0;$i<$n;$i+=4)
  666. {
  667. $high += (ord($s[$i])<<8) + ord($s[$i+1]);
  668. $low += (ord($s[$i+2])<<8) + ord($s[$i+3]);
  669. }
  670. return pack('nn', $high+($low>>16), $low);
  671. }
  672. function Error($msg)
  673. {
  674. throw new Exception($msg);
  675. }
  676. }
  677. ?>