forked from bruno/pabx-app
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
723 lines
18 KiB
723 lines
18 KiB
<?php |
|
/******************************************************************************* |
|
* Class to parse and subset TrueType fonts * |
|
* * |
|
* Version: 1.1 * |
|
* Date: 2015-11-29 * |
|
* Author: Olivier PLATHEY * |
|
*******************************************************************************/ |
|
|
|
class TTFParser |
|
{ |
|
protected $f; |
|
protected $tables; |
|
protected $numberOfHMetrics; |
|
protected $numGlyphs; |
|
protected $glyphNames; |
|
protected $indexToLocFormat; |
|
protected $subsettedChars; |
|
protected $subsettedGlyphs; |
|
public $chars; |
|
public $glyphs; |
|
public $unitsPerEm; |
|
public $xMin, $yMin, $xMax, $yMax; |
|
public $postScriptName; |
|
public $embeddable; |
|
public $bold; |
|
public $typoAscender; |
|
public $typoDescender; |
|
public $capHeight; |
|
public $italicAngle; |
|
public $underlinePosition; |
|
public $underlineThickness; |
|
public $isFixedPitch; |
|
|
|
function __construct($file) |
|
{ |
|
$this->f = fopen($file, 'rb'); |
|
if(!$this->f) |
|
$this->Error('Can\'t open file: '.$file); |
|
} |
|
|
|
function __destruct() |
|
{ |
|
if(is_resource($this->f)) |
|
fclose($this->f); |
|
} |
|
|
|
function Parse() |
|
{ |
|
$this->ParseOffsetTable(); |
|
$this->ParseHead(); |
|
$this->ParseHhea(); |
|
$this->ParseMaxp(); |
|
$this->ParseHmtx(); |
|
$this->ParseLoca(); |
|
$this->ParseGlyf(); |
|
$this->ParseCmap(); |
|
$this->ParseName(); |
|
$this->ParseOS2(); |
|
$this->ParsePost(); |
|
} |
|
|
|
function ParseOffsetTable() |
|
{ |
|
$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); |
|
$checkSum = $this->Read(4); |
|
$offset = $this->ReadULong(); |
|
$length = $this->ReadULong(4); |
|
$this->tables[$tag] = array('offset'=>$offset, 'length'=>$length, 'checkSum'=>$checkSum); |
|
} |
|
} |
|
|
|
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(); |
|
$this->Skip(3*2); // macStyle, lowestRecPPEM, fontDirectionHint |
|
$this->indexToLocFormat = $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->glyphs = array(); |
|
for($i=0;$i<$this->numberOfHMetrics;$i++) |
|
{ |
|
$advanceWidth = $this->ReadUShort(); |
|
$lsb = $this->ReadShort(); |
|
$this->glyphs[$i] = array('w'=>$advanceWidth, 'lsb'=>$lsb); |
|
} |
|
for($i=$this->numberOfHMetrics;$i<$this->numGlyphs;$i++) |
|
{ |
|
$lsb = $this->ReadShort(); |
|
$this->glyphs[$i] = array('w'=>$advanceWidth, 'lsb'=>$lsb); |
|
} |
|
} |
|
|
|
function ParseLoca() |
|
{ |
|
$this->Seek('loca'); |
|
$offsets = array(); |
|
if($this->indexToLocFormat==0) |
|
{ |
|
// Short format |
|
for($i=0;$i<=$this->numGlyphs;$i++) |
|
$offsets[] = 2*$this->ReadUShort(); |
|
} |
|
else |
|
{ |
|
// Long format |
|
for($i=0;$i<=$this->numGlyphs;$i++) |
|
$offsets[] = $this->ReadULong(); |
|
} |
|
for($i=0;$i<$this->numGlyphs;$i++) |
|
{ |
|
$this->glyphs[$i]['offset'] = $offsets[$i]; |
|
$this->glyphs[$i]['length'] = $offsets[$i+1] - $offsets[$i]; |
|
} |
|
} |
|
|
|
function ParseGlyf() |
|
{ |
|
$tableOffset = $this->tables['glyf']['offset']; |
|
foreach($this->glyphs as &$glyph) |
|
{ |
|
if($glyph['length']>0) |
|
{ |
|
fseek($this->f, $tableOffset+$glyph['offset'], SEEK_SET); |
|
if($this->ReadShort()<0) |
|
{ |
|
// Composite glyph |
|
$this->Skip(4*2); // xMin, yMin, xMax, yMax |
|
$offset = 5*2; |
|
$a = array(); |
|
do |
|
{ |
|
$flags = $this->ReadUShort(); |
|
$index = $this->ReadUShort(); |
|
$a[$offset+2] = $index; |
|
if($flags & 1) // ARG_1_AND_2_ARE_WORDS |
|
$skip = 2*2; |
|
else |
|
$skip = 2; |
|
if($flags & 8) // WE_HAVE_A_SCALE |
|
$skip += 2; |
|
elseif($flags & 64) // WE_HAVE_AN_X_AND_Y_SCALE |
|
$skip += 2*2; |
|
elseif($flags & 128) // WE_HAVE_A_TWO_BY_TWO |
|
$skip += 4*2; |
|
$this->Skip($skip); |
|
$offset += 2*2 + $skip; |
|
} |
|
while($flags & 32); // MORE_COMPONENTS |
|
$glyph['components'] = $a; |
|
} |
|
} |
|
} |
|
} |
|
|
|
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']['offset']+$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 = $this->tables['name']['offset']; |
|
$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'); |
|
$version = $this->ReadULong(); |
|
$this->italicAngle = $this->ReadShort(); |
|
$this->Skip(2); // Skip decimal part |
|
$this->underlinePosition = $this->ReadShort(); |
|
$this->underlineThickness = $this->ReadShort(); |
|
$this->isFixedPitch = ($this->ReadULong()!=0); |
|
if($version==0x20000) |
|
{ |
|
// Extract glyph names |
|
$this->Skip(4*4); // min/max usage |
|
$this->Skip(2); // numberOfGlyphs |
|
$glyphNameIndex = array(); |
|
$names = array(); |
|
$numNames = 0; |
|
for($i=0;$i<$this->numGlyphs;$i++) |
|
{ |
|
$index = $this->ReadUShort(); |
|
$glyphNameIndex[] = $index; |
|
if($index>=258 && $index-257>$numNames) |
|
$numNames = $index-257; |
|
} |
|
for($i=0;$i<$numNames;$i++) |
|
{ |
|
$len = ord($this->Read(1)); |
|
$names[] = $this->Read($len); |
|
} |
|
foreach($glyphNameIndex as $i=>$index) |
|
{ |
|
if($index>=258) |
|
$this->glyphs[$i]['name'] = $names[$index-258]; |
|
else |
|
$this->glyphs[$i]['name'] = $index; |
|
} |
|
$this->glyphNames = true; |
|
} |
|
else |
|
$this->glyphNames = false; |
|
} |
|
|
|
function Subset($chars) |
|
{ |
|
/* $chars = array_keys($this->chars); |
|
$this->subsettedChars = $chars; |
|
$this->subsettedGlyphs = array(); |
|
for($i=0;$i<$this->numGlyphs;$i++) |
|
{ |
|
$this->subsettedGlyphs[] = $i; |
|
$this->glyphs[$i]['ssid'] = $i; |
|
}*/ |
|
|
|
$this->AddGlyph(0); |
|
$this->subsettedChars = array(); |
|
foreach($chars as $char) |
|
{ |
|
if(isset($this->chars[$char])) |
|
{ |
|
$this->subsettedChars[] = $char; |
|
$this->AddGlyph($this->chars[$char]); |
|
} |
|
} |
|
} |
|
|
|
function AddGlyph($id) |
|
{ |
|
if(!isset($this->glyphs[$id]['ssid'])) |
|
{ |
|
$this->glyphs[$id]['ssid'] = count($this->subsettedGlyphs); |
|
$this->subsettedGlyphs[] = $id; |
|
if(isset($this->glyphs[$id]['components'])) |
|
{ |
|
foreach($this->glyphs[$id]['components'] as $cid) |
|
$this->AddGlyph($cid); |
|
} |
|
} |
|
} |
|
|
|
function Build() |
|
{ |
|
$this->BuildCmap(); |
|
$this->BuildHhea(); |
|
$this->BuildHmtx(); |
|
$this->BuildLoca(); |
|
$this->BuildGlyf(); |
|
$this->BuildMaxp(); |
|
$this->BuildPost(); |
|
return $this->BuildFont(); |
|
} |
|
|
|
function BuildCmap() |
|
{ |
|
if(!isset($this->subsettedChars)) |
|
return; |
|
|
|
// Divide charset in contiguous segments |
|
$chars = $this->subsettedChars; |
|
sort($chars); |
|
$segments = array(); |
|
$segment = array($chars[0], $chars[0]); |
|
for($i=1;$i<count($chars);$i++) |
|
{ |
|
if($chars[$i]>$segment[1]+1) |
|
{ |
|
$segments[] = $segment; |
|
$segment = array($chars[$i], $chars[$i]); |
|
} |
|
else |
|
$segment[1]++; |
|
} |
|
$segments[] = $segment; |
|
$segments[] = array(0xFFFF, 0xFFFF); |
|
$segCount = count($segments); |
|
|
|
// Build a Format 4 subtable |
|
$startCount = array(); |
|
$endCount = array(); |
|
$idDelta = array(); |
|
$idRangeOffset = array(); |
|
$glyphIdArray = ''; |
|
for($i=0;$i<$segCount;$i++) |
|
{ |
|
list($start, $end) = $segments[$i]; |
|
$startCount[] = $start; |
|
$endCount[] = $end; |
|
if($start!=$end) |
|
{ |
|
// Segment with multiple chars |
|
$idDelta[] = 0; |
|
$idRangeOffset[] = strlen($glyphIdArray) + ($segCount-$i)*2; |
|
for($c=$start;$c<=$end;$c++) |
|
{ |
|
$ssid = $this->glyphs[$this->chars[$c]]['ssid']; |
|
$glyphIdArray .= pack('n', $ssid); |
|
} |
|
} |
|
else |
|
{ |
|
// Segment with a single char |
|
if($start<0xFFFF) |
|
$ssid = $this->glyphs[$this->chars[$start]]['ssid']; |
|
else |
|
$ssid = 0; |
|
$idDelta[] = $ssid - $start; |
|
$idRangeOffset[] = 0; |
|
} |
|
} |
|
$entrySelector = 0; |
|
$n = $segCount; |
|
while($n!=1) |
|
{ |
|
$n = $n>>1; |
|
$entrySelector++; |
|
} |
|
$searchRange = (1<<$entrySelector)*2; |
|
$rangeShift = 2*$segCount - $searchRange; |
|
$cmap = pack('nnnn', 2*$segCount, $searchRange, $entrySelector, $rangeShift); |
|
foreach($endCount as $val) |
|
$cmap .= pack('n', $val); |
|
$cmap .= pack('n', 0); // reservedPad |
|
foreach($startCount as $val) |
|
$cmap .= pack('n', $val); |
|
foreach($idDelta as $val) |
|
$cmap .= pack('n', $val); |
|
foreach($idRangeOffset as $val) |
|
$cmap .= pack('n', $val); |
|
$cmap .= $glyphIdArray; |
|
|
|
$data = pack('nn', 0, 1); // version, numTables |
|
$data .= pack('nnN', 3, 1, 12); // platformID, encodingID, offset |
|
$data .= pack('nnn', 4, 6+strlen($cmap), 0); // format, length, language |
|
$data .= $cmap; |
|
$this->SetTable('cmap', $data); |
|
} |
|
|
|
function BuildHhea() |
|
{ |
|
$this->LoadTable('hhea'); |
|
$numberOfHMetrics = count($this->subsettedGlyphs); |
|
$data = substr_replace($this->tables['hhea']['data'], pack('n',$numberOfHMetrics), 4+15*2, 2); |
|
$this->SetTable('hhea', $data); |
|
} |
|
|
|
function BuildHmtx() |
|
{ |
|
$data = ''; |
|
foreach($this->subsettedGlyphs as $id) |
|
{ |
|
$glyph = $this->glyphs[$id]; |
|
$data .= pack('nn', $glyph['w'], $glyph['lsb']); |
|
} |
|
$this->SetTable('hmtx', $data); |
|
} |
|
|
|
function BuildLoca() |
|
{ |
|
$data = ''; |
|
$offset = 0; |
|
foreach($this->subsettedGlyphs as $id) |
|
{ |
|
if($this->indexToLocFormat==0) |
|
$data .= pack('n', $offset/2); |
|
else |
|
$data .= pack('N', $offset); |
|
$offset += $this->glyphs[$id]['length']; |
|
} |
|
if($this->indexToLocFormat==0) |
|
$data .= pack('n', $offset/2); |
|
else |
|
$data .= pack('N', $offset); |
|
$this->SetTable('loca', $data); |
|
} |
|
|
|
function BuildGlyf() |
|
{ |
|
$tableOffset = $this->tables['glyf']['offset']; |
|
$data = ''; |
|
foreach($this->subsettedGlyphs as $id) |
|
{ |
|
$glyph = $this->glyphs[$id]; |
|
fseek($this->f, $tableOffset+$glyph['offset'], SEEK_SET); |
|
$glyph_data = $this->Read($glyph['length']); |
|
if(isset($glyph['components'])) |
|
{ |
|
// Composite glyph |
|
foreach($glyph['components'] as $offset=>$cid) |
|
{ |
|
$ssid = $this->glyphs[$cid]['ssid']; |
|
$glyph_data = substr_replace($glyph_data, pack('n',$ssid), $offset, 2); |
|
} |
|
} |
|
$data .= $glyph_data; |
|
} |
|
$this->SetTable('glyf', $data); |
|
} |
|
|
|
function BuildMaxp() |
|
{ |
|
$this->LoadTable('maxp'); |
|
$numGlyphs = count($this->subsettedGlyphs); |
|
$data = substr_replace($this->tables['maxp']['data'], pack('n',$numGlyphs), 4, 2); |
|
$this->SetTable('maxp', $data); |
|
} |
|
|
|
function BuildPost() |
|
{ |
|
$this->Seek('post'); |
|
if($this->glyphNames) |
|
{ |
|
// Version 2.0 |
|
$numberOfGlyphs = count($this->subsettedGlyphs); |
|
$numNames = 0; |
|
$names = ''; |
|
$data = $this->Read(2*4+2*2+5*4); |
|
$data .= pack('n', $numberOfGlyphs); |
|
foreach($this->subsettedGlyphs as $id) |
|
{ |
|
$name = $this->glyphs[$id]['name']; |
|
if(is_string($name)) |
|
{ |
|
$data .= pack('n', 258+$numNames); |
|
$names .= chr(strlen($name)).$name; |
|
$numNames++; |
|
} |
|
else |
|
$data .= pack('n', $name); |
|
} |
|
$data .= $names; |
|
} |
|
else |
|
{ |
|
// Version 3.0 |
|
$this->Skip(4); |
|
$data = "\x00\x03\x00\x00"; |
|
$data .= $this->Read(4+2*2+5*4); |
|
} |
|
$this->SetTable('post', $data); |
|
} |
|
|
|
function BuildFont() |
|
{ |
|
$tags = array(); |
|
foreach(array('cmap', 'cvt ', 'fpgm', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'name', 'post', 'prep') as $tag) |
|
{ |
|
if(isset($this->tables[$tag])) |
|
$tags[] = $tag; |
|
} |
|
$numTables = count($tags); |
|
$offset = 12 + 16*$numTables; |
|
foreach($tags as $tag) |
|
{ |
|
if(!isset($this->tables[$tag]['data'])) |
|
$this->LoadTable($tag); |
|
$this->tables[$tag]['offset'] = $offset; |
|
$offset += strlen($this->tables[$tag]['data']); |
|
} |
|
// $this->tables['head']['data'] = substr_replace($this->tables['head']['data'], "\x00\x00\x00\x00", 8, 4); |
|
|
|
// Build offset table |
|
$entrySelector = 0; |
|
$n = $numTables; |
|
while($n!=1) |
|
{ |
|
$n = $n>>1; |
|
$entrySelector++; |
|
} |
|
$searchRange = 16*(1<<$entrySelector); |
|
$rangeShift = 16*$numTables - $searchRange; |
|
$offsetTable = pack('nnnnnn', 1, 0, $numTables, $searchRange, $entrySelector, $rangeShift); |
|
foreach($tags as $tag) |
|
{ |
|
$table = $this->tables[$tag]; |
|
$offsetTable .= $tag.$table['checkSum'].pack('NN', $table['offset'], $table['length']); |
|
} |
|
|
|
// Compute checkSumAdjustment (0xB1B0AFBA - font checkSum) |
|
$s = $this->CheckSum($offsetTable); |
|
foreach($tags as $tag) |
|
$s .= $this->tables[$tag]['checkSum']; |
|
$a = unpack('n2', $this->CheckSum($s)); |
|
$high = 0xB1B0 + ($a[1]^0xFFFF); |
|
$low = 0xAFBA + ($a[2]^0xFFFF) + 1; |
|
$checkSumAdjustment = pack('nn', $high+($low>>16), $low); |
|
$this->tables['head']['data'] = substr_replace($this->tables['head']['data'], $checkSumAdjustment, 8, 4); |
|
|
|
$font = $offsetTable; |
|
foreach($tags as $tag) |
|
$font .= $this->tables[$tag]['data']; |
|
|
|
return $font; |
|
} |
|
|
|
function LoadTable($tag) |
|
{ |
|
$this->Seek($tag); |
|
$length = $this->tables[$tag]['length']; |
|
$n = $length % 4; |
|
if($n>0) |
|
$length += 4 - $n; |
|
$this->tables[$tag]['data'] = $this->Read($length); |
|
} |
|
|
|
function SetTable($tag, $data) |
|
{ |
|
$length = strlen($data); |
|
$n = $length % 4; |
|
if($n>0) |
|
$data = str_pad($data, $length+4-$n, "\x00"); |
|
$this->tables[$tag]['data'] = $data; |
|
$this->tables[$tag]['length'] = $length; |
|
$this->tables[$tag]['checkSum'] = $this->CheckSum($data); |
|
} |
|
|
|
function Seek($tag) |
|
{ |
|
if(!isset($this->tables[$tag])) |
|
$this->Error('Table not found: '.$tag); |
|
fseek($this->f, $this->tables[$tag]['offset'], SEEK_SET); |
|
} |
|
|
|
function Skip($n) |
|
{ |
|
fseek($this->f, $n, SEEK_CUR); |
|
} |
|
|
|
function Read($n) |
|
{ |
|
return $n>0 ? 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']; |
|
} |
|
|
|
function CheckSum($s) |
|
{ |
|
$n = strlen($s); |
|
$high = 0; |
|
$low = 0; |
|
for($i=0;$i<$n;$i+=4) |
|
{ |
|
$high += (ord($s[$i])<<8) + ord($s[$i+1]); |
|
$low += (ord($s[$i+2])<<8) + ord($s[$i+3]); |
|
} |
|
return pack('nn', $high+($low>>16), $low); |
|
} |
|
|
|
function Error($msg) |
|
{ |
|
throw new Exception($msg); |
|
} |
|
} |
|
?>
|
|
|