composer package updates
[openemr.git] / vendor / rospdf / pdf-php / src / include / TTF.php
blob093e1e8c62016f234c4f6207d9f6fd6b32ed7119
1 <?php
3 /*
4 TTF.php: TrueType font file reader and writer
5 Copyright (C) 2012 Thanos Efraimidis (4real.gr)
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 class TTF
23 // Bit flags used for composite glyphs
24 const ARG_1_AND_2_ARE_WORDS = 1;
25 const ARGS_ARE_XY_VALUES = 2;
26 const ROUND_XY_TO_GRID = 4;
27 const WE_HAVE_A_SCALE = 8;
28 const MORE_COMPONENTS = 32;
29 const WE_HAVE_AN_X_AND_Y_SCALE = 64;
30 const WE_HAVE_A_TWO_BY_TWO = 128;
31 const WE_HAVE_INSTRUCTIONS = 256;
32 const USE_MY_METRICS = 512;
34 // For debugging
35 const VERBOSE = false;
37 private $b; // Array of bytes
38 private $tables; // Tables
40 // Constructor: parses the table directory
41 public function __construct($b)
43 $this->b = $b;
45 $off = 0;
46 $version = self::getFixed($b, $off); // sfnt version
47 $numTables = self::getUshort($b, $off); // number of tables
48 $searchRange = self::getUshort($b, $off);
49 $entrySelector = self::getUshort($b, $off);
50 $rangeShift = self::getUshort($b, $off);
51 $this->tables = array();
52 for ($i = 0; $i < $numTables; $i++) {
53 $name = self::getRaw($b, $off, 4);
54 $checksum = self::getUlong($b, $off);
55 $offset = self::getUlong($b, $off);
56 $length = self::getUlong($b, $off);
57 $this->tables[$name] = array('offset' => $offset, 'length' => $length);
59 if (self::VERBOSE) {
60 echo sprintf("==== Table directory\n");
61 echo sprintf("Version: %s, number of tables: %d\n", $version, $numTables);
62 foreach ($this->tables as $name => $value) {
63 echo sprintf("%s %10d %10d\n", $name, $value['offset'], $value['length']);
65 echo "\n";
69 // Get raw bytes of table, or null if table does not exist
70 public function getTableRaw($name)
72 if (isset($this->tables[$name])) {
73 $entry = $this->tables[$name];
74 return substr($this->b, $entry['offset'], $entry['length']);
76 return null;
79 ////////////////////////////////////////////////////////////////////////////////
80 // Unmarshal - marshal functions follow
81 ////////////////////////////////////////////////////////////////////////////////
82 public function unmarshalName()
84 $name = array();
85 $b = $this->getTableRaw('name');
86 $off = 0;
87 $name['format'] = self::getUshort($b, $off);
88 $name['count'] = self::getUshort($b, $off);
89 $name['offset'] = self::getUshort($b, $off);
90 $name['nameRecords'] = array();
92 $tmp = $name['offset'];
93 for ($i = 0; $i < $name['count']; ++$i) {
94 $name['nameRecords'][$i] = array();
95 $name['nameRecords'][$i]['platformID'] = self::getUshort($b, $off);
96 $name['nameRecords'][$i]['platformSpecificID'] = self::getUshort($b, $off);
97 $name['nameRecords'][$i]['languageID'] = self::getUshort($b, $off);
98 $name['nameRecords'][$i]['nameID'] = self::getUshort($b, $off);
99 $name['nameRecords'][$i]['length'] = self::getUshort($b, $off);
100 $name['nameRecords'][$i]['offset'] = self::getUshort($b, $off);
101 $name['nameRecords'][$i]['value'] = self::getRaw($b, $tmp, $name['nameRecords'][$i]['length'] * 2);
102 $tmp += $name['nameRecords'][$i]['length'] + 3;
105 return $name;
108 public function unmarshalHead()
110 $head = array(); // To return
111 $b = $this->getTableRaw('head'); // Get raw bytes for 'head' table
112 $off = 0;
113 $head['version'] = self::getRaw($b, $off, 4); // This is actually fixed
114 $head['revision'] = self::getRaw($b, $off, 4); // This is actually fixed
115 $off += 4; // Skip checksum adjustment
116 $off += 4; // Skip magic number
117 $head['flags'] = self::getUshort($b, $off);
118 $head['unitsPerEm'] = self::getUshort($b, $off);
119 $head['created'] = self::getRaw($b, $off, 8); // This is actually longdatetime
120 $head['modified'] = self::getRaw($b, $off, 8); // This is actually longdatetime
121 $head['xMin'] = self::getFword($b, $off);
122 $head['yMin'] = self::getFword($b, $off);
123 $head['xMax'] = self::getFword($b, $off);
124 $head['yMax'] = self::getFword($b, $off);
125 $head['macStyle'] = self::getUshort($b, $off);
126 $head['lowestRecPPEM'] = self::getUshort($b, $off);
127 $head['fontDirectionHint'] = self::getShort($b, $off);
128 $head['indexToLocFormat'] = self::getShort($b, $off);
129 $head['glyphDataFormat'] = self::getShort($b, $off);
130 return $head;
133 public static function marshalHead($head)
135 $b = str_repeat(chr(0), 54); // Size of 'head' is 54 bytes
136 $off = 0;
137 self::setRaw($b, $off, $head['version'], 4); // This is actually fixed
138 self::setRaw($b, $off, $head['revision'], 4); // This is actually fixed
139 self::setUlong($b, $off, 0); // Checksum Adjustment - will be calculated later
140 self::setUlong($b, $off, 0x5F0F3CF5); // Magic Number
141 self::setUshort($b, $off, $head['flags']);
142 self::setUshort($b, $off, $head['unitsPerEm']);
143 self::setRaw($b, $off, $head['created'], 8); // This is actually longdatetime
144 self::setRaw($b, $off, $head['modified'], 8); // This is actually longdatetime
145 self::setFword($b, $off, $head['xMin']);
146 self::setFword($b, $off, $head['yMin']);
147 self::setFword($b, $off, $head['xMax']);
148 self::setFword($b, $off, $head['yMax']);
149 self::setUshort($b, $off, $head['macStyle']);
150 self::setUshort($b, $off, $head['lowestRecPPEM']);
151 self::setShort($b, $off, $head['fontDirectionHint']);
152 self::setShort($b, $off, $head['indexToLocFormat']);
153 self::setShort($b, $off, $head['glyphDataFormat']);
154 return $b;
157 public function unmarshalHhea()
159 $hhea = array(); // To return
160 $b = $this->getTableRaw('hhea'); // Get raw bytes for 'hhea' table
161 $off = 0;
162 $hhea['version'] = self::getRaw($b, $off, 4); // This is actually fixed
163 $hhea['ascender'] = self::getFword($b, $off);
164 $hhea['descender'] = self::getFword($b, $off);
165 $hhea['lineGap'] = self::getFword($b, $off);
166 $hhea['advanceWidthMax'] = self::getUFword($b, $off);
167 $hhea['minLeftSideBearing'] = self::getFword($b, $off);
168 $hhea['minRightSideBearing'] = self::getFword($b, $off);
169 $hhea['xMaxExtent'] = self::getFword($b, $off);
170 $hhea['caretSlopeRise'] = self::getShort($b, $off);
171 $hhea['caretSlopeRun'] = self::getShort($b, $off);
172 $off += 10; // Skip reserved
173 $hhea['metricDataFormat'] = self::getShort($b, $off);
174 $hhea['numberOfHMetrics'] = self::getUShort($b, $off);
175 return $hhea;
178 public static function marshalHhea($hhea)
180 $b = str_repeat(chr(0), 36); // Size of 'hhea' is 36 bytes
181 $off = 0;
182 self::setRaw($b, $off, $hhea['version'], 4); // This is actually fixed
183 self::setFword($b, $off, $hhea['ascender']);
184 self::setFword($b, $off, $hhea['descender']);
185 self::setFword($b, $off, $hhea['lineGap']);
186 self::setUFword($b, $off, $hhea['advanceWidthMax']);
187 self::setFword($b, $off, $hhea['minLeftSideBearing']);
188 self::setFword($b, $off, $hhea['minRightSideBearing']);
189 self::setFword($b, $off, $hhea['xMaxExtent']);
190 self::setShort($b, $off, $hhea['caretSlopeRise']);
191 self::setShort($b, $off, $hhea['caretSlopeRun']);
192 $off += 10; // Skip reserved
193 self::setShort($b, $off, $hhea['metricDataFormat']);
194 self::setUshort($b, $off, $hhea['numberOfHMetrics']);
195 return $b;
198 public function unmarshalMaxp()
200 $maxp = array(); // To return
201 $b = $this->getTableRaw('maxp'); // Get raw bytes for 'maxp' table
202 $off = 0;
203 $maxp['version'] = self::getRaw($b, $off, 4); // This is actually fixed
204 $maxp['numGlyphs'] = self::getUshort($b, $off);
205 $maxp['maxPoints'] = self::getUshort($b, $off);
206 $maxp['maxContours'] = self::getUshort($b, $off);
207 $maxp['maxCompositePoints'] = self::getUshort($b, $off);
208 $maxp['maxCompositeContours'] = self::getUshort($b, $off);
209 $maxp['maxZones'] = self::getUshort($b, $off);
210 $maxp['maxTwilightPoints'] = self::getUshort($b, $off);
211 $maxp['maxStorage'] = self::getUshort($b, $off);
212 $maxp['maxFunctionDefs'] = self::getUshort($b, $off);
213 $maxp['maxInstructionDefs'] = self::getUshort($b, $off);
214 $maxp['maxStackElements'] = self::getUshort($b, $off);
215 $maxp['maxSizeOfInstructions'] = self::getUshort($b, $off);
216 $maxp['maxComponentElements'] = self::getUshort($b, $off);
217 $maxp['maxComponentDepth'] = self::getUshort($b, $off);
218 return $maxp;
221 public static function marshalMaxp($maxp)
223 $b = str_repeat(chr(0), 32); // Size of 'maxp' is 32 bytes
224 $off = 0;
225 self::setRaw($b, $off, $maxp['version'], 4); // This is actually fixed
226 self::setUshort($b, $off, $maxp['numGlyphs']);
227 self::setUshort($b, $off, $maxp['maxPoints']);
228 self::setUshort($b, $off, $maxp['maxContours']);
229 self::setUshort($b, $off, $maxp['maxCompositePoints']);
230 self::setUshort($b, $off, $maxp['maxCompositeContours']);
231 self::setUshort($b, $off, $maxp['maxZones']);
232 self::setUshort($b, $off, $maxp['maxTwilightPoints']);
233 self::setUshort($b, $off, $maxp['maxStorage']);
234 self::setUshort($b, $off, $maxp['maxFunctionDefs']);
235 self::setUshort($b, $off, $maxp['maxInstructionDefs']);
236 self::setUshort($b, $off, $maxp['maxStackElements']);
237 self::setUshort($b, $off, $maxp['maxSizeOfInstructions']);
238 self::setUshort($b, $off, $maxp['maxComponentElements']);
239 self::setUshort($b, $off, $maxp['maxComponentDepth']);
240 return $b;
243 public function unmarshalLoca($indexToLocFormat, $numGlyphs)
245 $loca = array(); // To return
246 $b = $this->getTableRaw('loca'); // Get raw bytes for 'loca' table
247 $off = 0;
248 if ($indexToLocFormat == 0) {
249 for ($i = 0; $i < $numGlyphs + 1; $i++) {
250 $loca[] = 2 * self::getUshort($b, $off);
252 } else {
253 for ($i = 0; $i < $numGlyphs + 1; $i++) {
254 $loca[] = self::getUlong($b, $off);
257 return $loca;
260 public static function marshalLoca($loca)
262 $cnt = count($loca);
263 if ($loca[$cnt - 1] <= 0x20000) {
264 // Short offsets
265 $b = str_repeat(chr(0), 2 * $cnt);
266 $off = 0;
267 for ($i = 0; $i < $cnt; $i++) {
268 self::setUshort($b, $off, $loca[$i] / 2);
270 } else {
271 // Long offsets
272 $b = str_repeat(chr(0), 4 * $cnt);
273 $off = 0;
274 for ($i = 0; $i < $cnt; $i++) {
275 self::setUlong($b, $off, $loca[$i]);
278 return $b;
281 public function unmarshalHmtx($numberOfHMetrics, $numGlyphs)
283 $metrics = array(); // To return
284 $lsbs = array(); // To return
285 $b = $this->getTableRaw('hmtx'); // Get raw bytes for 'hmtx' table
286 $off = 0;
287 for ($i = 0; $i < $numberOfHMetrics; $i++) {
288 $advanceWidth = self::getUFword($b, $off);
289 $lsb = self::getFword($b, $off);
290 $metrics[] = array($advanceWidth, $lsb);
292 for ($i = $numberOfHMetrics; $i < $numGlyphs; $i++) {
293 $lsb = self::getFword($b, $off);
294 $lsbs[] = $lsb;
296 return array('metrics' => $metrics, 'lsbs' => $lsbs);
299 public static function marshalHmtx($metrics, $lsbs)
301 $cntMetrics = count($metrics);
302 $cntLsbs = count($lsbs);
303 $b = str_repeat(chr(0), 4 * $cntMetrics + 2 * $cntLsbs);
304 $off = 0;
305 for ($i = 0; $i < $cntMetrics; $i++) {
306 $advanceWidth = $metrics[$i][0];
307 $lsb = $metrics[$i][1];
308 self::setUFword($b, $off, $advanceWidth);
309 self::setFword($b, $off, $lsb);
311 for ($i = 0; $i < $cntLsbs; $i++) {
312 $lsb = $lsbs[$i];
313 self::setFword($b, $off, $lsb);
315 return $b;
318 public function unmarshalGlyf($loca)
320 $glyf = array(); // To return
321 $b = $this->getTableRaw('glyf'); // Get raw bytes for 'glyf' table
323 $num = count($loca) - 1;
324 for ($i = 0; $i < $num; $i++) {
325 $glyf[] = substr($b, $loca[$i], $loca[$i + 1] - $loca[$i]);
327 return $glyf;
330 public static function marshalGlyf($glyf)
332 $b = '';
333 $num = count($glyf);
334 for ($i = 0; $i < $num; $i++) {
335 $b .= $glyf[$i];
337 return $b;
340 public function unmarshalCmap()
342 $cmap = array(); // To return
343 $b = $this->getTableRaw('cmap'); // Get raw bytes for 'cmap' table
344 $off = 0;
345 $cmap['version'] = self::getUshort($b, $off);
346 $cmap['numTables'] = self::getUshort($b, $off);
347 $cmap['tables'] = array();
348 $numTables = $cmap['numTables'];
349 $platformIDs = array();
350 $platformSpecificIDs = array();
351 $offsets = array();
352 for ($i = 0; $i < $numTables; $i++) {
353 $platformIDs[] = self::getUshort($b, $off);
354 $platformSpecificIDs[] = self::getUshort($b, $off);
355 $offsets[] = self::getUlong($b, $off);
357 for ($i = 0; $i < $numTables; $i++) {
358 $off0 = $off = $offsets[$i];
359 $format = self::getUshort($b, $off);
360 $length = self::getUshort($b, $off);
361 $version = self::getUshort($b, $off);
362 if ($format == 0) {
363 $glyphIdArray = array();
364 for ($cid = 0; $cid < 256; $cid++) {
365 $glyphIdArray[] = self::getByte($b, $off);
367 $cmap['tables'][] = array('platformID' => $platformIDs[$i],
368 'platformSpecificID' => $platformSpecificIDs[$i],
369 'format' => $format,
370 'length' => $length,
371 'version' => $version,
372 'glyphIdArray' => $glyphIdArray);
373 } elseif ($format == 2) {
374 throw new Exception('cmap format is 2');
375 } elseif ($format == 4) {
376 $segCountX2 = self::getUshort($b, $off);
377 $searchRange = self::getUshort($b, $off);
378 $entrySelector = self::getUshort($b, $off);
379 $rangeShift = self::getUshort($b, $off);
381 $segCount = $segCountX2 / 2;
382 $endCountArray = array();
383 $startCountArray = array();
384 $idDeltaArray = array();
385 $idRangeOffsetArray = array();
386 $glyphIdArray = array();
387 for ($seg = 0; $seg < $segCount; $seg++) {
388 $endCountArray[] = self::getUshort($b, $off);
390 $off += 2; // Skip reserved
391 for ($seg = 0; $seg < $segCount; $seg++) {
392 $startCountArray[] = self::getUshort($b, $off);
394 for ($seg = 0; $seg < $segCount; $seg++) {
395 $idDeltaArray[] = self::getUshort($b, $off);
397 for ($seg = 0; $seg < $segCount; $seg++) {
398 $idRangeOffsetArray[] = self::getUshort($b, $off);
400 while ($off < $off0 + $length) {
401 $glyphIdArray[] = self::getUshort($b, $off);
403 $cmap['tables'][] = array('platformID' => $platformIDs[$i],
404 'platformSpecificID' => $platformSpecificIDs[$i],
405 'format' => $format,
406 'length' => $length,
407 'version' => $version,
408 'segCount' => $segCount,
409 'endCountArray' => $endCountArray,
410 'startCountArray' => $startCountArray,
411 'idDeltaArray' => $idDeltaArray,
412 'idRangeOffsetArray' => $idRangeOffsetArray,
413 'glyphIdArray' => $glyphIdArray);
414 } elseif ($format == 6) {
415 $firstCode = self::getUshort($b, $off);
416 $entryCount = self::getUshort($b, $off);
417 $glyphIdArray = array();
418 for ($cid = $firstCode; $cid < $firstCode + $entryCount; $cid++) {
419 $glyphIdArray[] = self::getUshort($b, $off);
421 $cmap['tables'][] = array('platformID' => $platformIDs[$i],
422 'platformSpecificID' => $platformSpecificIDs[$i],
423 'format' => $format,
424 'length' => $length,
425 'version' => $version,
426 'firstCode' => $firstCode,
427 'entryCount' => $entryCount,
428 'glyphIdArray' => $glyphIdArray);
429 } else {
430 $off -= 6; // go back and check for 8.0, 10.0 and 12.0 formats
431 $format = self::getFixed($b, $off);
432 $length = self::getUlong($b, $off);
433 $language = self::getUlong($b, $off);
434 if ($format == '8.0') {
435 throw new Exception('cmap format is 8.0');
436 } elseif ($format == '10.0') {
437 throw new Exception('cmap format is 10.0');
438 } elseif ($format == '12.0') {
439 $nGroups = self::getUlong($b, $off);
440 $startCharCodes = array();
441 $endCharCodes = array();
442 $startGlyphCodes = array();
443 for ($grp = 0; $grp < $nGroups; $grp++) {
444 $startCharCodes[] = self::getUlong($b, $off);
445 $endCharCodes[] = self::getUlong($b, $off);
446 $startGlyphCodes[] = self::getUlong($b, $off);
448 $cmap['tables'][] = array('platformID' => $platformIDs[$i],
449 'platformSpecificID' => $platformSpecificIDs[$i],
450 'format' => $format,
451 'length' => $length,
452 'version' => $version,
453 'startCharCodes' => $startCharCodes,
454 'endCharCodes' => $endCharCodes,
455 'startGlyphCodes' => $startGlyphCodes);
456 } else {
457 throw new Exception('Internal error: unknwon cmap format');
461 return $cmap;
464 public static function marshalCmap($cmap)
466 $lengths = array(); // To hold the length of each table
468 $sz = 4 + 8 * count($cmap['tables']);
469 foreach ($cmap['tables'] as $table) {
470 $format = $table['format'];
471 if ($format == 0) {
472 $length = 6 + 256; // Size for format 0 table
473 } elseif ($format == 4) {
474 $cnt1 = count($table['startCountArray']);
475 $cnt2 = count($table['glyphIdArray']);
476 $length = 14 + 4 * 2 * $cnt1 + 2 + 2 * $cnt2; // Size for format 4 table
477 } elseif ($format == 6) {
478 $cnt = count($table['glyphIdArray']);
479 $length = 10 + 2 * $cnt; // Size for format 6 table
480 } elseif ($format == '12.0') {
481 $cnt = count($table['startCharCodes']);
482 $length = 16 + 12 * $cnt; // Size for format 12.0 table
483 } else {
484 throw new Exception('Internal error');
486 $sz += $length;
487 $lengths[] = $length;
490 $b = str_repeat(chr(0), $sz);
491 $off = 0;
492 self::setUshort($b, $off, $cmap['version']);
493 self::setUshort($b, $off, $cmap['numTables']);
495 $offset = 4 + 8 * count($cmap['tables']);
496 $i = 0;
497 foreach ($cmap['tables'] as $table) {
498 self::setUshort($b, $off, $table['platformID']);
499 self::setUshort($b, $off, $table['platformSpecificID']);
500 self::setUlong($b, $off, $offset);
501 $offset += $lengths[$i++];
503 $i = 0;
504 $offset = 4 + 8 * count($cmap['tables']);
505 foreach ($cmap['tables'] as $table) {
506 $off = $offset;
508 $format = $table['format'];
509 $length = $lengths[$i];
510 $version = $table['version'];
511 if ($format == 0) {
512 self::setUshort($b, $off, $format);
513 self::setUshort($b, $off, $length);
514 self::setUshort($b, $off, $version);
515 $glyphIdArray = $table['glyphIdArray'];
516 for ($cid = 0; $cid < count($glyphIdArray); $cid++) {
517 self::setByte($b, $off, $glyphIdArray[$cid]);
519 } elseif ($format == 4) {
520 $segCount = $table['segCount'];
521 $endCountArray = $table['endCountArray'];
522 $startCountArray = $table['startCountArray'];
523 $idDeltaArray = $table['idDeltaArray'];
524 $idRangeOffsetArray = $table['idRangeOffsetArray'];
525 $glyphIdArray = $table['glyphIdArray'];
527 // Calculate searchRange, entrySelector and rangeShift
528 $binarySearchRegisters = self::calculateBinarySearchRegisters($segCount, 2, 1);
530 self::setUshort($b, $off, $format);
531 self::setUshort($b, $off, $length);
532 self::setUshort($b, $off, $version);
533 self::setUshort($b, $off, 2 * $segCount); // segCountX2
534 self::setUshort($b, $off, $binarySearchRegisters['SearchRange']);
535 self::setUshort($b, $off, $binarySearchRegisters['EntrySelector']);
536 self::setUshort($b, $off, $binarySearchRegisters['RangeShift']);
537 for ($seg = 0; $seg < $segCount; $seg++) {
538 self::setUshort($b, $off, $endCountArray[$seg]);
540 self::setUshort($b, $off, 0); // Reserved
541 for ($seg = 0; $seg < $segCount; $seg++) {
542 self::setUshort($b, $off, $startCountArray[$seg]);
544 for ($seg = 0; $seg < $segCount; $seg++) {
545 self::setUshort($b, $off, $idDeltaArray[$seg]);
547 for ($seg = 0; $seg < $segCount; $seg++) {
548 self::setUshort($b, $off, $idRangeOffsetArray[$seg]);
550 for ($cid = 0; $cid < count($glyphIdArray); $cid++) {
551 self::setUshort($b, $off, $glyphIdArray[$cid]);
553 } elseif ($format == 6) {
554 self::setUshort($b, $off, $format);
555 self::setUshort($b, $off, $length);
556 self::setUshort($b, $off, $version);
557 self::setUshort($b, $off, $table['firstCode']);
558 self::setUshort($b, $off, $table['entryCount']);
559 $glyphIdArray = $table['glyphIdArray'];
560 for ($cid = 0; $cid < count($glyphIdArray); $cid++) {
561 self::setShort($b, $off, $glyphIdArray[$cid]);
563 } elseif ($format == '12.0') {
564 $startCharCodes = $table['startCharCodes'];
565 $endCharCodes = $table['endCharCodes'];
566 $startGlyphCodes = $table['startGlyphCodes'];
567 $nGroups = count($startCharCodes);
568 self::setFixed($b, $off, '12.0');
569 self::setUlong($b, $off, $length);
570 self::setUlong($b, $off, 0);
571 self::setUlong($b, $off, $nGroups);
572 for ($grp = 0; $grp < $nGroups; $grp++) {
573 self::setUlong($b, $off, $startCharCodes[$grp]);
574 self::setUlong($b, $off, $endCharCodes[$grp]);
575 self::setUlong($b, $off, $startGlyphCodes[$grp]);
577 } else {
578 throw new Exception('Internal error');
580 $offset += $lengths[$i++];
582 return $b;
585 public function unmarshalPost()
587 $post = array(); // To return
588 $b = $this->getTableRaw('post'); // Get raw bytes for 'post' table
589 $off = 0;
590 // Collect standard header
591 $post['formatType'] = self::getFixed($b, $off);
592 $post['italicAngle'] = self::getFixed($b, $off);
593 $post['underlinePosition'] = self::getFword($b, $off);
594 $post['underlineThickness'] = self::getFword($b, $off);
595 $post['isFixedPitch'] = self::getUlong($b, $off);
596 $post['minMemType42'] = self::getUlong($b, $off);
597 $post['maxMemType42'] = self::getUlong($b, $off);
598 $post['minMemType1'] = self::getUlong($b, $off);
599 $post['maxMemType1'] = self::getUlong($b, $off);
601 if ($post['formatType'] == '1.0') {
602 ; // Nothing more
603 } elseif ($post['formatType'] == '2.0') {
604 // Collect numGlyphs, glyphNameIndex array and glyphNames (Pascal strings)
605 $numGlyphs = self::getUshort($b, $off);
606 $glyphNameIndex = array();
607 for ($i = 0; $i < $numGlyphs; $i++) {
608 $glyphNameIndex[] = self::getUshort($b, $off);
610 $glyphNames = array();
611 while ($off < strlen($b)) {
612 $len = self::getByte($b, $off);
613 $name = self::getRaw($b, $off, $len);
614 $glyphNames[] = $name;
617 // 'gn' will contain either a number (for Macintosh standard order glyph name)
618 // or a string (otherwise)
619 $gn = array();
620 for ($i = 0; $i < count($glyphNameIndex); $i++) {
621 $index = $glyphNameIndex[$i];
622 if ($index >= 0 && $index <= 257) {
623 $gn[] = $index;
624 } elseif ($index >= 258 && $index <= 32767) {
625 $gn[] = $glyphNames[$index - 258];
626 } else {
627 throw new Exception(sprintf('Internal error - glyphNameIndex is %d', $index));
630 $post['glyphNames'] = $gn;
631 } elseif ($post['formatType'] == '3.0') {
632 ; // Nothing more
633 } else {
634 throw new Exception(sprintf('Internal error - formatType is %s', $post['formatType']));
636 return $post;
639 public static function marshalPost($post)
641 // Calculate size
642 $sz = 32; // Standard header for all formatTypes
643 if ($post['formatType'] == '1.0') {
644 ; // Nothing more
645 } elseif ($post['formatType'] == '2.0') {
646 $gn = $post['glyphNames'];
647 $sz += 2; // for numberOfGlyphs
648 $sz += 2 * count($gn); // for glyphNameIndex
649 for ($i = 0; $i < count($gn); $i++) {
650 if (is_string($gn[$i])) {
651 $sz += 1 + strlen($gn[$i]);
654 } elseif ($post['formatType'] == '3.0') {
655 ; // Nothing more
656 } else {
657 throw new Exception(sprintf('Internal error - formatType is %s', $post['formatType']));
660 $b = str_repeat(chr(0), $sz);
661 $off = 0;
662 self::setFixed($b, $off, $post['formatType']);
663 self::setFixed($b, $off, $post['italicAngle']);
664 self::setFword($b, $off, $post['underlinePosition']);
665 self::setFword($b, $off, $post['underlineThickness']);
666 self::setUlong($b, $off, $post['isFixedPitch']);
667 self::setUlong($b, $off, $post['minMemType42']);
668 self::setUlong($b, $off, $post['maxMemType42']);
669 self::setUlong($b, $off, $post['minMemType1']);
670 self::setUlong($b, $off, $post['maxMemType1']);
671 if ($post['formatType'] == '1.0') {
672 ; // Nothing more
673 } elseif ($post['formatType'] == '2.0') {
674 $gn = $post['glyphNames'];
675 $numGlyphs = count($gn);
676 $glyphNames = array();
677 self::setUshort($b, $off, $numGlyphs); // Push numGlyphs
678 for ($i = 0; $i < $numGlyphs; $i++) {
679 if (is_string($gn[$i])) {
680 self::setUshort($b, $off, count($glyphNames) + 258);
681 $glyphNames[] = $gn[$i];
682 } else {
683 // Macintosh standard order glyph name
684 self::setUshort($b, $off, $gn[$i]);
687 for ($i = 0; $i < count($glyphNames); $i++) {
688 $len = strlen($glyphNames[$i]);
689 self::setByte($b, $off, $len);
690 self::setRaw($b, $off, $glyphNames[$i], $len);
692 } elseif ($post['formatType'] == '3.0') {
693 ; // Nothing more
694 } else {
695 throw new Exception(sprintf('Internal error - formatType is %s', $post['formatType']));
697 return $b;
700 private static $tableNamesOrderedByRank = array
701 ('head', 'hhea', 'maxp', 'OS/2', 'hmtx', 'LTSH', 'VDMX', 'hdmx', 'cmap', 'fpgm',
702 'prep', 'cvt ', 'loca', 'glyf', 'kern', 'name', 'post', 'gasp', 'PCLT', 'GDEF',
703 'GPOS', 'GSUB', 'JSTF', 'DSIG');
705 private static $tableNamesOrderedByName = array
706 ('DSIG', 'GDEF', 'GPOS', 'GSUB', 'JSTF', 'LTSH', 'OS/2', 'PCLT', 'VDMX', 'cmap',
707 'cvt ', 'fpgm', 'gasp', 'glyf', 'hdmx', 'head', 'hhea', 'hmtx', 'kern', 'loca',
708 'maxp', 'name', 'post', 'prep');
710 public static function marshalAll($tables)
712 $numTables = count($tables);
714 // Arrays to hold for each table, the checksum, the offset and the length
715 $checksums = array();
716 $offsets = array();
717 $lengths = array();
719 $sb = str_repeat(chr(0), 12 + $numTables * 16); // Allocate room for table directory
720 foreach (self::$tableNamesOrderedByRank as $tableName) {
721 if (isset($tables[$tableName])) {
722 $data = $tables[$tableName];
724 // Special handling for 'head' table - set checksum adjustment to zero
725 if ($tableName == 'head') {
726 $off = 8;
727 self::setUlong($data, $off, 0);
730 // Calculate checksums, offsets, lengths
731 $checksums[$tableName] = self::calculateTableChecksum($data);
732 $offsets[$tableName] = strlen($sb);
733 $lengths[$tableName] = strlen($data);
734 // Append data and right pad with '0' (align on four byte boundary)
735 $sb .= $data;
736 while ((strlen($sb) % 4) != 0) {
737 $sb .= chr(0);
742 // Dump the table directory
743 $off = 0;
744 self::setUlong($sb, $off, 0x00010000); // This is actually fixed
745 self::setUshort($sb, $off, $numTables);
746 // Calculate the binary search registers
747 $binarySearchRegisters = self::calculateBinarySearchRegisters($numTables, 16, 4);
748 self::setUshort($sb, $off, $binarySearchRegisters['SearchRange']);
749 self::setUshort($sb, $off, $binarySearchRegisters['EntrySelector']);
750 self::setUshort($sb, $off, $binarySearchRegisters['RangeShift']);
751 foreach (self::$tableNamesOrderedByName as $tableName) {
752 if (isset($tables[$tableName])) {
753 self::setRaw($sb, $off, $tableName, 4);
754 self::setUlong($sb, $off, $checksums[$tableName]);
755 self::setUlong($sb, $off, $offsets[$tableName]);
756 self::setUlong($sb, $off, $lengths[$tableName]);
760 // Calculate the checksum adjustment for 'head' table
761 $checksum = self::calculateTableChecksum(substr($sb, 0, 12 + 16 * $numTables));
762 foreach ($checksums as $chk) {
763 $checksum = bcadd($checksum, $chk);
765 $checksum = bcsub('2981146554', $checksum); // This is "0xB1B0AFBA"
766 while (bccomp($checksum, '0') < 0) {
767 $checksum = bcadd($checksum, '4294967296'); // This is "0x100000000"
769 $off = $offsets['head'] + 8;
770 self::setUlong($sb, $off, $checksum);
771 return $sb;
774 ////////////////////////////////////////////////////////////////////////////////
775 // Helper functions follow
776 ////////////////////////////////////////////////////////////////////////////////
778 // Search "cmap" for an encoding table having given "platformID" and "platformSpecificID"
779 // and return it. Return null if no such table exists
780 public static function getEncodingTable($cmap, $platformID, $platformSpecificID)
782 foreach ($cmap['tables'] as $table) {
783 if ($table['platformID'] == 3 && $table['platformSpecificID'] == 1) {
784 return $table;
787 return null;
790 // Map character "charCode" to index using the encoding table "encodingTable"
791 public static function characterToIndex($encodingTable, $charCode)
793 $format = $encodingTable['format'];
794 if ($format == 0) {
795 $glyphIdArray = $encodingTable['glyphIdArray'];
796 if ($charCode >= 0 && $charCode < 256) {
797 return $glyphIdArray[$charCode];
799 } elseif ($format == 4) {
800 $segCount = $encodingTable['segCount'];
801 $endCountArray = $encodingTable['endCountArray'];
802 $startCountArray = $encodingTable['startCountArray'];
803 $idDeltaArray = $encodingTable['idDeltaArray'];
804 $idRangeOffsetArray = $encodingTable['idRangeOffsetArray'];
805 $glyphIdArray = $encodingTable['glyphIdArray'];
807 for ($seg = 0; $seg < $segCount; $seg++) {
808 $endCount = $endCountArray[$seg];
809 $startCount = $startCountArray[$seg];
810 $idDelta = $idDeltaArray[$seg];
811 $idRangeOffset = $idRangeOffsetArray[$seg];
812 if ($charCode >= $startCount && $charCode <= $endCount) {
813 if ($idRangeOffset != 0) {
814 $j = $charCode - $startCount + $seg + $idRangeOffset / 2 - $segCount;
815 $gid = $glyphIdArray[$j];
816 } else {
817 $gid = $idDelta + $charCode;
819 return $gid %= 65536;
822 } elseif ($format == 6) {
823 $firstCode = $encodingTable['firstCode'];
824 $entryCount = $encodingTable['entryCount'];
825 $glyphIdArray = $encodingTable['glyphIdArray'];
826 if ($charCode >= $firstCode && $charCode < $firstCode + $entryCount) {
827 return $glyphIdArray[$charCode - $firstCode];
829 } else {
830 throw new Exception('Internal error');
832 return -1;
835 private static function indexToCharacter($encodingTable, $gid)
837 $format = $encodingTable['format'];
838 if ($format == 0) {
839 $glyphIdArray = $encodingTable['glyphIdArray'];
840 for ($charCode = 0; $charCode < count($glyphIdArray); $charCode++) {
841 $gid0 = $glyphIdArray[$i];
842 if ($gid == $gid0) {
843 return sprintf("%d", $charCode);
846 } elseif ($format == 4) {
847 $segCount = $encodingTable['segCount'];
848 $endCountArray = $encodingTable['endCountArray'];
849 $startCountArray = $encodingTable['startCountArray'];
850 $idDeltaArray = $encodingTable['idDeltaArray'];
851 $idRangeOffsetArray = $encodingTable['idRangeOffsetArray'];
852 $glyphIdArray = $encodingTable['glyphIdArray'];
854 for ($seg = 0; $seg < $segCount; $seg++) {
855 $endCount = $endCountArray[$seg];
856 $startCount = $startCountArray[$seg];
857 $idDelta = $idDeltaArray[$seg];
858 $idRangeOffset = $idRangeOffsetArray[$seg];
859 for ($charCode = $startCount; $charCode <= $endCount; $charCode++) {
860 if ($idRangeOffset != 0) {
861 $j = $charCode - $startCount + $seg + $idRangeOffset / 2 - $segCount;
862 $gid0 = $glyphIdArray[$j];
863 } else {
864 $gid0 = $idDelta + $charCode;
866 $gid0 %= 65536;
867 if ($gid == $gid0) {
868 return sprintf("%d", $charCode);
872 } elseif ($format == 6) {
873 $firstCode = $encodingTable['firstCode'];
874 $entryCount = $encodingTable['entryCount'];
875 $glyphIdArray = $encodingTable['glyphIdArray'];
876 for ($charCode = $firstCode; $charCode < $firstCode + $entryCount; $charCode++) {
877 $gid0 = $glyphIdArray[$charCode - $firstCode];
878 if ($gid == $gid0) {
879 return sprintf("%d", $charCode);
882 } else {
883 throw new Exception('Internal error');
885 return null;
888 // Get the horizontal metrics (advance width and left side bearing) for
889 // glyph with index "index"
890 public static function getHMetrics($hmtx, $numberOfHMetrics, $index)
892 $metrics = $hmtx['metrics'];
893 $lsbs = $hmtx['lsbs'];
894 if ($index < $numberOfHMetrics) {
895 return $metrics[$index];
896 } else {
897 // Get advance width from last element of metrics
898 return array($metrics[$numberOfHMetrics - 1][0], $lsbs[$index - $numberOfHMetrics]);
902 // Given the glyph description, parse it and return a PHP array
903 public static function getGlyph($description)
905 $off = 0;
907 $numberOfContours = self::getShort($description, $off);
908 $xMin = self::getFword($description, $off);
909 $yMin = self::getFword($description, $off);
910 $xMax = self::getFword($description, $off);
911 $yMax = self::getFword($description, $off);
912 if ($numberOfContours >= 0) {
913 // Collect the endPoints of contours. Save the last endPoint
914 $endPointsOfContours = array();
915 for ($i = 0; $i < $numberOfContours; $i++) {
916 $lastEndPoint = self::getUshort($description, $off);
917 $endPointsOfContours[] = $lastEndPoint;
920 // Collect the instructions
921 $instructionLength = self::getUshort($description, $off);
922 $instructions = self::getRaw($description, $off, $instructionLength);
924 // Collect the flags
925 $flags = array();
926 while (count($flags) <= $lastEndPoint) {
927 $flag = ord($description{$off});
928 $off++;
929 if (($flag & 0x08) != 0) {
930 $num = ord($description{$off}) + 1;
931 $off++;
932 } else {
933 $num = 1;
935 for ($j = 0; $j < $num; $j++) {
936 $flags[] = $flag;
940 // Collect the x coordinates
941 $xs = self::getCoordinates($description, $off, $flags, 0x02, 0x10);
943 // Collect the y coordinates
944 $ys = self::getCoordinates($description, $off, $flags, 0x04, 0x20);
946 return array('numberOfContours' => $numberOfContours,
947 'xMin' => $xMin, 'yMin' => $yMin,
948 'xMax' => $xMax, 'yMax' => $yMax,
949 'endPointsOfContours' => $endPointsOfContours,
950 'instructions' => $instructions,
951 'flags' => $flags, 'xs' => $xs, 'ys' => $ys);
952 } else {
953 $components = array();
955 do {
956 $flags = self::getUshort($description, $off);
957 $glyphIndex = self::getUshort($description, $off);
959 $argument1 = $argument2 = $arg1and2 = '';
960 $scale = $xscale = $yscale = $scale01 = $scale10 = '';
962 if (($flags & self::ARG_1_AND_2_ARE_WORDS) != 0) {
963 $argument1 = self::getShort($description, $off);
964 $argument2 = self::getShort($description, $off);
965 } else {
966 $arg1and2 = self::getUshort($description, $off);
968 if (($flags & self::WE_HAVE_A_SCALE) != 0) {
969 $scale = self::getF2Dot14($description, $off);
970 } elseif (($flags & self::WE_HAVE_AN_X_AND_Y_SCALE) != 0) {
971 $xscale = self::getF2Dot14($description, $off);
972 $yscale = self::getF2Dot14($description, $off);
973 } elseif (($flags & self::WE_HAVE_A_TWO_BY_TWO) != 0) {
974 $xscale = self::getF2Dot14($description, $off);
975 $scale01 = self::getF2Dot14($description, $off);
976 $scale10 = self::getF2Dot14($description, $off);
977 $yscale = self::getF2Dot14($description, $off);
980 if (self::VERBOSE) {
981 echo sprintf("arg1=[%s], arg2=[%s], arg1and2=[%s]\n", $argument1, $argument2, $arg1and2);
982 echo sprintf("flags=0x%02x, glyphIndex=%d\n", $flags, $glyphIndex);
983 if (($flags & self::ARG_1_AND_2_ARE_WORDS) != 0) {
984 echo " arg1and2areWords";
986 if (($flags & self::ARGS_ARE_XY_VALUES) != 0) {
987 echo " argsAreXyValues";
989 if (($flags & self::ROUND_XY_TO_GRID) != 0) {
990 echo " roundXyToGrid";
992 if (($flags & self::WE_HAVE_A_SCALE) != 0) {
993 echo " weHaveAScale";
995 if (($flags & self::MORE_COMPONENTS) != 0) {
996 echo " moreComponents";
998 if (($flags & self::WE_HAVE_AN_X_AND_Y_SCALE) != 0) {
999 echo " weHaveAnXandYscale";
1001 if (($flags & self::WE_HAVE_A_TWO_BY_TWO) != 0) {
1002 echo " weHaveATwoByTwo";
1004 if (($flags & self::WE_HAVE_INSTRUCTIONS) != 0) {
1005 echo " weHaveInstructions";
1007 if (($flags & self::USE_MY_METRICS) != 0) {
1008 echo " useMyMetrics";
1010 echo "\n\n";
1013 $components[] = array('flags' => $flags, 'glyphIndex' => $glyphIndex,
1014 'argument1' => $argument1, 'argument2' => $argument2, 'arg1and2' => $arg1and2,
1015 'scale' => $scale, 'xscale' => $xscale, 'yscale' => $yscale, 'scale01' => $scale01, 'scale10' => $scale10);
1016 } while (($flags & self::MORE_COMPONENTS) != 0);
1017 if (($flags & self::WE_HAVE_INSTRUCTIONS) != 0) {
1018 $numInstr = self::getUshort($description, $off);
1019 $instructions = self::getRaw($description, $off, $numInstr);
1020 } else {
1021 $instructions = '';
1023 return array('numberOfContours' => $numberOfContours,
1024 'xMin' => $xMin, 'yMin' => $yMin,
1025 'xMax' => $xMax, 'yMax' => $yMax,
1026 'components' => $components,
1027 'instructions' => $instructions);
1031 // Replace glyph indices of components of composite glyph
1032 public static function replaceComponentsOfCompositeGlyph($description, $replacements)
1034 $off = 0;
1036 $numberOfContours = self::getShort($description, $off);
1037 if ($numberOfContours >= 0) {
1038 return $description;
1040 $off += 8; // Skip xMin, yMin, xMax, yMax
1041 do {
1042 $flags = self::getUshort($description, $off);
1043 $glyphIndex = self::getUshort($description, $off);
1044 if (isset($replacements[$glyphIndex])) {
1045 $from = $glyphIndex;
1046 $to = $replacements[$from];
1047 $off -= 2; // Go back and replace
1048 self::setUshort($description, $off, $to);
1050 // Skip arguments
1051 if (($flags & self::ARG_1_AND_2_ARE_WORDS) != 0) {
1052 $off += 4;
1053 } else {
1054 $off += 2;
1056 if (($flags & self::WE_HAVE_A_SCALE) != 0) {
1057 $off += 2;
1058 } elseif (($flags & self::WE_HAVE_AN_X_AND_Y_SCALE) != 0) {
1059 $off += 4;
1060 } elseif (($flags & self::WE_HAVE_A_TWO_BY_TWO) != 0) {
1061 $off += 8;
1063 } while (($flags & self::MORE_COMPONENTS) != 0);
1064 return $description;
1068 // Calculate searchRange, entrySelector and rangeShift
1069 private static function calculateBinarySearchRegisters($count, $size, $logSize)
1071 $entrySelector = -$logSize;
1072 $searchRange = 1;
1073 while (2 * $searchRange < $count * $size) {
1074 $entrySelector++;
1075 $searchRange *= 2;
1077 $rangeShift = $count * $size - $searchRange;
1078 return array('SearchRange' => $searchRange, 'EntrySelector' => $entrySelector, 'RangeShift' => $rangeShift);
1081 private static function calculateTableChecksum($data)
1083 $ret = '0';
1085 // "Right" pad with zeros
1086 while ((strlen($data) % 4) != 0) {
1087 $data .= chr(0);
1089 $off = 0;
1090 $len = strlen($data);
1091 while ($off < $len) {
1092 $ret += self::getUlong($data, $off);
1094 $ret = bcmod($ret, '4294967296');
1095 return $ret;
1098 //////////////////// Function to get and set bytes, shorts, longs, etc ////////////////////
1099 private static function getByte($b, &$off)
1101 return ord($b[$off++]);
1104 private static function setByte(&$b, &$off, $val)
1106 $b{$off++} = chr($val);
1109 private static function getUshort($b, &$off)
1111 $num = ord($b[$off++]);
1112 $num = 256 * $num + ord($b[$off++]);
1113 return $num;
1116 private static function setUshort(&$b, &$off, $val)
1118 $b{$off++} = chr($val / 256);
1119 $b{$off++} = chr($val % 256);
1122 private static function getShort($b, &$off)
1124 $num = self::getUshort($b, $off);
1125 return $num < 32768 ? $num : $num - 65536;
1128 private static function setShort(&$b, &$off, $val)
1130 $b{$off++} = chr(($val >> 8) & 0xff);
1131 $b{$off++} = chr($val & 0xff);
1134 private static function getUlong($b, &$off)
1136 $ret = '0';
1137 $ret = bcadd($ret, bcmul(ord($b[$off++]), '16777216'));
1138 $ret = bcadd($ret, bcmul(ord($b[$off++]), '65536'));
1139 $ret = bcadd($ret, bcmul(ord($b[$off++]), '256'));
1140 $ret = bcadd($ret, ord($b[$off++]));
1141 return $ret;
1144 private static function setUlong(&$b, &$off, $val)
1146 $b{$off++} = chr(bcmod(bcdiv($val, '16777216', 0), '256'));
1147 $b{$off++} = chr(bcmod(bcdiv($val, '65536', 0), '256'));
1148 $b{$off++} = chr(bcmod(bcdiv($val, '256', 0), '256'));
1149 $b{$off++} = chr(bcmod($val, '256'));
1152 private static function getLong($b, &$off)
1154 $ret = self::getUlong($b, $off);
1155 return bccomp($ret, '2147483648') < 0 ? $ret : bcsub($ret, '4294967296');
1158 private static function getFixed($b, &$off)
1160 $b1 = ord($b[$off++]);
1161 $b2 = ord($b[$off++]);
1162 $b3 = ord($b[$off++]);
1163 $b4 = ord($b[$off++]);
1165 $mantissa = $b1 * 256 + $b2;
1166 if ($mantissa >= 32768) {
1167 $mantissa -= 65536;
1169 $fraction = $b3 * 256 + $b4;
1171 if ($fraction == 0) {
1172 return sprintf("%d.0", $mantissa); // Append one zero
1173 } else {
1174 $tmp = sprintf("%.6f", $fraction / 65536);
1175 $tmp = substr($tmp, 2); // Remove leading "0."
1176 return sprintf("%d.%s", $mantissa, $tmp);
1180 private static function setFixed(&$b, &$off, $val)
1182 if ($val{0} == '-') {
1183 $sign = -1;
1184 $val = substr($val, 1);
1185 } else {
1186 $sign = +1;
1188 if (($idx = strpos($val, '.')) === false) {
1189 $mantissa = intval($val);
1190 $fraction = 0;
1191 } else {
1192 $mantissa = intval(substr($val, 0, $idx));
1193 $fraction = intval(substr($val, $idx + 1));
1195 $mantissa *= $sign;
1197 $b{$off++} = chr(($mantissa >> 8) & 0xff);
1198 $b{$off++} = chr(($mantissa >> 0) & 0xff);
1199 $b{$off++} = chr(($fraction >> 8) & 0xff);
1200 $b{$off++} = chr(($fraction >> 0) & 0xff);
1203 private static function getFword($b, &$off)
1205 return self::getShort($b, $off);
1208 private static function setUFword(&$b, &$off, $val)
1210 self::setUshort($b, $off, $val);
1213 private static function getUFword($b, &$off)
1215 return self::getUshort($b, $off);
1218 private static function setFword(&$b, &$off, $val)
1220 self::setShort($b, $off, $val);
1223 private static function getF2dot14($b, &$off)
1225 $val1 = ord($b{$off});
1226 $val2 = ord($b{$off + 1});
1227 $val = 256 * $val1 + $val2;
1229 $mantissa = ($val >> 14) & 0x03;
1230 if ($mantissa >= 2) {
1231 $mantissa -= 4;
1233 $fraction = $val & 0x3fff;
1235 if ($fraction == 0) {
1236 // Append only one zero
1237 $ret = sprintf("%d.0", $mantissa);
1238 } else {
1239 $tmp = sprintf("%.6f", $fraction / 16384);
1240 $tmp = substr($tmp, 2); // Remove leading "0."
1241 $ret = sprintf("%d.%s", $mantissa, $tmp);
1243 return $ret;
1246 private static function getRaw($b, &$off, $num)
1248 $ret = substr($b, $off, $num);
1249 $off += $num;
1250 return $ret;
1253 private static function setRaw(&$b, &$off, $val, $num)
1255 $i = 0;
1256 while ($i < $num) {
1257 $b{$off++} = $val{$i++};
1261 private static function parseFixed($val)
1263 $b1 = ord($val[0]);
1264 $b2 = ord($val[1]);
1265 $b3 = ord($val[2]);
1266 $b4 = ord($val[3]);
1268 $mantissa = $b1 * 256 + $b2;
1269 if ($mantissa >= 32768) {
1270 $mantissa -= 65536;
1272 $fraction = $b3 * 256 + $b4;
1274 if ($fraction == 0) {
1275 // Append only one zero
1276 return sprintf("%d.0", $mantissa);
1277 } else {
1278 $tmp = sprintf("%.6f", $fraction / 65536);
1279 $tmp = substr($tmp, 2); // Remove leading ".0"
1280 return sprintf("%d.%s", $mantissa, $tmp);
1284 private static function getCoordinates($code, &$off, $flags, $mask1, $mask2)
1286 $ret = array();
1287 for ($i = 0; $i < count($flags); $i++) {
1288 $flag = $flags[$i];
1289 $bit1 = $flag & $mask1;
1290 $bit4 = $flag & $mask2;
1291 if ($bit1 != 0) {
1292 $b = ord($code{$off++});
1293 if ($bit4 != 0) {
1294 // Positive 8-bit
1295 $val = $b;
1296 } else {
1297 // Negative 8-bit
1298 $val = -$b;
1300 } else {
1301 if ($bit4 != 0) {
1302 // Same as previous (delta=0)
1303 $val = 0;
1304 } else {
1305 // Signed 16-bit
1306 $b1 = ord($code{$off++});
1307 $b2 = ord($code{$off++});
1308 $b = 256 * $b1 + $b2;
1309 if ($b >= 32768) {
1310 $b -= 65536;
1312 $val = $b;
1315 $ret[] = $val;
1317 return $ret;