Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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