1<?php 2 3define('OT_CMAP_PLATFORM_WINDOWS', 3); 4define('OT_CMAP_PLATFORM_WINDOWS_UNICODE', 1); 5 6define('OT_PLATFORM_ID_MICROSOFT', 3); 7 8define('OT_NAME_ID_SUBFAMILY_NAME', 2); 9define('OT_NAME_ID_UNIQUE_ID', 3); 10define('OT_NAME_ID_FULL_NAME', 4); 11define('OT_NAME_ID_POSTSCRIPT_NAME', 6); 12 13define('OT_CMAP_LANGUAGE_WINDOWS_ENGLISH_AMERICAN', 0x0409); 14 15/** 16 * This class allows parsing TrueType/OpenType font files 17 */ 18class OpenTypeFile { 19 var $_filehandle; 20 var $_sfnt; 21 22 function OpenTypeFile() { 23 $this->_filehandle = null; 24 $this->_sfnt = new OpenTypeFileSFNT(); 25 } 26 27 function open($filename) { 28 $this->_filehandle = fopen($filename, 'rb'); 29 $this->_read($this->_filehandle); 30 } 31 32 function close() { 33 fclose($this->_filehandle); 34 } 35 36 function _delete() { 37 $this->close(); 38 $this->_sfnt->_delete(); 39 } 40 41 function getFileHandle() { 42 return $this->_filehandle; 43 } 44 45 function &getTable($tag) { 46 $table =& $this->_sfnt->_getTable($tag, $this->_filehandle, $this); 47 return $table; 48 } 49 50 function &_getCMAPSubtable($offset) { 51 $table =& $this->_sfnt->_getCMAPSubtable($offset, $this->_filehandle, $this); 52 return $table; 53 } 54 55 function _read($filehandle) { 56 $this->_sfnt->_read($filehandle); 57 } 58} 59 60/** 61 * A key characteristic of the OpenType format is the TrueType sfnt 62 * "wrapper", which provides organization for a collection of tables 63 * in a general and extensible manner. 64 */ 65class OpenTypeFileSFNT { 66 var $_offsetTable; 67 var $_tableDirectory; 68 69 var $_tables; 70 71 function _delete() { 72 foreach ($this->_tables as $key => $value) { 73 $this->_tables[$key]->_delete(); 74 unset($this->_tables[$key]); 75 }; 76 $this->_tables = array(); 77 } 78 79 function OpenTypeFileSFNT() { 80 $this->_offsetTable = new OpenTypeFileOffsetTable(); 81 $this->_tableDirectory = array(); 82 } 83 84 function _read($filehandle) { 85 $this->_offsetTable->_read($filehandle); 86 87 for ($i=0; $i<$this->_offsetTable->_numTables; $i++) { 88 $tableDirectory = new OpenTypeFileTableDirectory(); 89 $tableDirectory->_read($filehandle); 90 $this->_tableDirectory[] = $tableDirectory; 91 }; 92 } 93 94 function &_getCMAPSubtable($offset, $filehandle, $file) { 95 $dir = $this->_getDirectory('cmap'); 96 if (is_null($dir)) { $dummy = null; return $dummy; }; 97 98 /** 99 * Store current file position, as _getCMAPSubtable could be 100 * called from another file-related operation 101 */ 102 $old_pos = ftell($filehandle); 103 104 fseek($filehandle, $dir->_offset, SEEK_SET); 105 fseek($filehandle, $offset, SEEK_CUR); 106 $subtable = new OpenTypeFileCMAPSubtable(); 107 $subtable->_read($filehandle); 108 109 /** 110 * Restore current file position 111 */ 112 fseek($filehandle, $old_pos, SEEK_SET); 113 114 return $subtable; 115 } 116 117 function &_getTable($tag, $filehandle, $file) { 118 if (!isset($this->_tables[$tag])) { 119 $table = $this->_createTableByTag($tag); 120 if (is_null($table)) { $dummy = null; return $dummy; }; 121 $table->setFontFile($file); 122 123 $dir = $this->_getDirectory($tag); 124 if (is_null($dir)) { $dummy = null; return $dummy; }; 125 126 /** 127 * Store current file position, as _getTable could be called 128 * from another _getTable 129 */ 130 $old_pos = ftell($filehandle); 131 132 fseek($filehandle, $dir->_offset, SEEK_SET); 133 $table->_read($filehandle); 134 135 /** 136 * Restore current file position 137 */ 138 fseek($filehandle, $old_pos, SEEK_SET); 139 140 $this->_tables[$tag] =& $table; 141 }; 142 143 return $this->_tables[$tag]; 144 } 145 146 function _getDirectory($tag) { 147 foreach ($this->_tableDirectory as $directoryEntry) { 148 if ($directoryEntry->_tag == $tag) { 149 return $directoryEntry; 150 }; 151 }; 152 153 return null; 154 } 155 156 function _createTableByTag($tag) { 157 switch ($tag) { 158 case 'hhea': 159 return new OpenTypeFileHHEA(); 160 case 'maxp': 161 return new OpenTypeFileMAXP(); 162 case 'cmap': 163 return new OpenTypeFileCMAP(); 164 case 'hmtx': 165 return new OpenTypeFileHMTX(); 166 case 'post': 167 return new OpenTypeFilePOST(); 168 case 'head': 169 return new OpenTypeFileHEAD(); 170 case 'name': 171 return new OpenTypeFileNAME(); 172 default: 173 return null; 174 } 175 } 176} 177 178/** 179 * The OpenType font with the Offset Table. If the font file contains only one font, the Offset Table will begin at byte 0 of the file. If the font file is a TrueType collection, the beginning point of the Offset Table for each font is indicated in the TTCHeader. 180 * 181 * Offset Table Type Name Description 182 * Fixed sfnt version 0x00010000 for version 1.0. 183 * USHORT numTables Number of tables. 184 * USHORT searchRange (Maximum power of 2 <= numTables) x 16. 185 * USHORT entrySelector Log2(maximum power of 2 <= numTables). 186 * USHORT rangeShift NumTables x 16-searchRange. 187 * 188 * OpenType fonts that contain TrueType outlines should use the value 189 * of 1.0 for the sfnt version. OpenType fonts containing CFF data 190 * should use the tag 'OTTO' as the sfnt version number. 191 * 192 * NOTE: The Apple specification for TrueType fonts allows for 'true' 193 * and 'typ1' for sfnt version. These version tags should not be used 194 * for fonts which contain OpenType tables. 195 */ 196class OpenTypeFileOffsetTable { 197 var $_numTables; 198 var $_searchRange; 199 var $_entrySelector; 200 var $_rangeShift; 201 202 function OpenTypeFileOffsetTable() { 203 $this->_numTables = 0; 204 $this->_searchRange = 0; 205 $this->_entrySelector = 0; 206 $this->_rangeShift = 0; 207 } 208 209 function _read($filehandle) { 210 $content = fread($filehandle, 4+4*2); 211 212 $unpacked = unpack("Nversion/nnumTables/nsearchRange/nentrySelector/nrangeShift", $content); 213 214 $fixed = $unpacked['version']; 215 $this->_numTables = $unpacked['numTables']; 216 $this->_searchRange = $unpacked['searchRange']; 217 $this->_entrySelector = $unpacked['entrySelector']; 218 $this->_rangeShift = $unpacked['rangeShift']; 219 } 220} 221 222/** 223 * The Offset Table is followed immediately by the Table Directory 224 * entries. Entries in the Table Directory must be sorted in ascending 225 * order by tag. Offset values in the Table Directory are measured 226 * from the start of the font file. 227 * 228 * Table Directory Type Name Description 229 * ULONG tag 4 -byte identifier. 230 * ULONG checkSum CheckSum for this table. 231 * ULONG offset Offset from beginning of TrueType font file. 232 * ULONG length Length of this table. 233 * 234 * The Table Directory makes it possible for a given font to contain 235 * only those tables it actually needs. As a result there is no 236 * standard value for numTables. 237 * 238 * Tags are the names given to tables in the OpenType font file. All 239 * tag names consist of four characters. Names with less than four 240 * letters are allowed if followed by the necessary trailing 241 * spaces. All tag names defined within a font (e.g., table names, 242 * feature tags, language tags) must be built from printing characters 243 * represented by ASCII values 32-126. 244 */ 245class OpenTypeFileTableDirectory { 246 var $_tag; 247 var $_checkSum; 248 var $_offset; 249 var $_length; 250 251 function OpenTypeFileTableDirectory() { 252 $this->_tag = null; 253 $this->_checkSum = 0; 254 $this->_offset = 0; 255 $this->_length = 0; 256 } 257 258 function _read($filehandle) { 259 $content = fread($filehandle, 4*4); 260 261 $unpacked = unpack("c4tag/NcheckSum/Noffset/Nlength", $content); 262 263 $this->_tag = chr($unpacked['tag1']).chr($unpacked['tag2']).chr($unpacked['tag3']).chr($unpacked['tag4']); 264 $this->_checkSum = $unpacked['checkSum']; 265 $this->_offset = $unpacked['offset']; 266 $this->_length = $unpacked['length']; 267 } 268} 269 270/* -------------- */ 271 272class OpenTypeFileTable { 273 var $_fontFile; 274 275 function _delete() { 276 } 277 278 function OpenTypeFileTable() { 279 $this->_fontFile = null; 280 } 281 282 function setFontFile(&$fontFile) { 283 $this->_fontFile =& $fontFile; 284 } 285 286 function &getFontFile() { 287 return $this->_fontFile; 288 } 289 290 function _fixFWord($value) { 291 if ($value > 65536/2) { 292 return $value - 65536; 293 } else { 294 return $value; 295 }; 296 } 297 298 function _fixShort($value) { 299 if ($value > 65536/2) { 300 return $value - 65536; 301 } else { 302 return $value; 303 }; 304 } 305} 306 307class OpenTypeFilePOST extends OpenTypeFileTable { 308 var $_version; 309 var $_italicAngle; 310 var $_underlinePosition; 311 var $_underlineThickness; 312 var $_isFixedPitch; 313 var $_minMemType42; 314 var $_maxMemType42; 315 var $_minMemType1; 316 var $_maxMemType1; 317 318 function OpenTypeFilePOST() { 319 $this->OpenTypeFileTable(); 320 } 321 322 function _read($filehandle) { 323 $content = fread($filehandle, 2*2 + 7*4); 324 $unpacked = unpack("Nversion/NitalicAngle/nunderlinePosition/nunderlineThickness/NisFixedPitch/NminMemType42/NmaxMemType42/NminMemType1/NmaxMemType1", $content); 325 $this->_version = $unpacked['version']; 326 $this->_italicAngle = $unpacked['italicAngle']; 327 $this->_underlinePosition = $this->_fixFWord($unpacked['underlinePosition']); 328 $this->_underlineThickness = $this->_fixFWord($unpacked['underlineThickness']); 329 $this->_isFixedPitch = $unpacked['isFixedPitch']; 330 $this->_minMemType42 = $unpacked['minMemType42']; 331 $this->_maxMemType42 = $unpacked['maxMemType42']; 332 $this->_minMemType1 = $unpacked['minMemType1']; 333 $this->_maxMemType1 = $unpacked['maxMemType1']; 334 } 335} 336 337class OpenTypeFileNAME extends OpenTypeFileTable { 338 var $_format; 339 var $_count; 340 var $_stringOffset; 341 var $_nameRecord; 342 343 function OpenTypeFileNAME() { 344 $this->OpenTypeFileTable(); 345 $this->_nameRecord = array(); 346 } 347 348 function _read($filehandle) { 349 $content = fread($filehandle, 2*3); 350 $unpacked = unpack("nformat/ncount/nstringOffset", $content); 351 352 $this->_format = $unpacked['format']; 353 $this->_count = $unpacked['count']; 354 $this->_stringOffset = $unpacked['stringOffset']; 355 356 $baseOffset = ftell($filehandle) + OpenTypeFileNAMERecord::sizeof()*$this->_count; 357 358 for ($i=0; $i<$this->_count; $i++) { 359 $record =& new OpenTypeFileNAMERecord(); 360 $record->setBaseOffset($baseOffset); 361 $record->setFontFile($this->getFontFile()); 362 $record->_read($filehandle); 363 $this->_nameRecord[] =& $record; 364 }; 365 } 366 367 /** 368 * Note that this function can perform "wildcard" lookups when one or more 369 * parameters is set to null value; in this case the first encountered name 370 * will be returned 371 * 372 * @return String corresponding name content or null is this name is 373 * not defined in the font file 374 */ 375 function lookup($platformId, $encodingId, $languageId, $nameId) { 376 $size = count($this->_nameRecord); 377 378 for ($i=0; $i<$size; $i++) { 379 if ($this->_nameRecord[$i]->match($platformId, $encodingId, $languageId, $nameId)) { 380 return $this->_nameRecord[$i]->getName(); 381 }; 382 } 383 384 return null; 385 } 386} 387 388class OpenTypeFileNAMERecord extends OpenTypeFileTable { 389 var $_platformId; 390 var $_encodingId; 391 var $_languageId; 392 var $_nameId; 393 var $_length; 394 var $_offset; 395 396 var $_content; 397 var $_baseOffset; 398 399 function OpenTypeFileNAMERecord() { 400 $this->OpenTypeFileTable(); 401 $this->_content = null; 402 } 403 404 function sizeof() { 405 return 6*2; 406 } 407 408 function setBaseOffset($offset) { 409 $this->_baseOffset = $offset; 410 } 411 412 function match($platformId, $encodingId, $languageId, $nameId) { 413 return 414 (is_null($platformId) || $platformId == $this->_platformId) && 415 (is_null($encodingId) || $encodingId == $this->_encodingId) && 416 (is_null($languageId) || $languageId == $this->_languageId) && 417 (is_null($nameId) || $nameId == $this->_nameId); 418 } 419 420 function getBaseOffset() { 421 return $this->_baseOffset; 422 } 423 424 function getName() { 425 if (is_null($this->_content)) { 426 $file =& $this->getFontFile(); 427 $filehandle = $file->getFileHandle(); 428 $old_offset = ftell($filehandle); 429 430 fseek($filehandle, $this->getBaseOffset() + $this->_offset, SEEK_SET); 431 $this->_content = fread($filehandle, $this->_length); 432 433 fseek($filehandle, $old_offset, SEEK_SET); 434 }; 435 436 return $this->_content; 437 } 438 439 function _read($filehandle) { 440 $content = fread($filehandle, 6*2); 441 442 $unpacked = unpack("nplatformId/nencodingId/nlanguageId/nnameId/nlength/noffset", $content); 443 444 $this->_platformId = $unpacked['platformId']; 445 $this->_encodingId = $unpacked['encodingId']; 446 $this->_languageId = $unpacked['languageId']; 447 $this->_nameId = $unpacked['nameId']; 448 $this->_length = $unpacked['length']; 449 $this->_offset = $unpacked['offset']; 450 } 451} 452 453/** 454 * This table gives global information about the font. The bounding 455 * box values should be computed using only glyphs that have 456 * contours. Glyphs with no contours should be ignored for the 457 * purposes of these calculations. 458 * 459 * Type Name Description 460 * Fixed Table version number 0x00010000 for version 1.0. 461 * Fixed fontRevision Set by font manufacturer. 462 * ULONG checkSumAdjustment To compute: set it to 0, sum the entire font as ULONG, then store 0xB1B0AFBA - sum. 463 * ULONG magicNumber Set to 0x5F0F3CF5. 464 * USHORT flags Bit 0: Baseline for font at y=0; 465 * Bit 1: Left sidebearing point at x=0; 466 * Bit 2: Instructions may depend on point size; 467 * Bit 3: Force ppem to integer values for all internal scaler math; may use fractional ppem sizes if this bit is clear; 468 * Bit 4: Instructions may alter advance width (the advance widths might not scale linearly); 469 * Bits 5-10: These should be set according to Apple's specification . However, they are not implemented in OpenType. 470 * Bit 11: Font data is 'lossless,' as a result of having been compressed and decompressed with the Agfa MicroType Express engine. 471 * Bit 12: Font converted (produce compatible metrics) 472 * Bit 13: Font optimised for ClearType 473 * Bit 14: Reserved, set to 0 474 * Bit 15: Reserved, set to 0 475 * USHORT unitsPerEm Valid range is from 16 to 16384. This value should be a power of 2 for fonts that have TrueType outlines. 476 * LONGDATETIME created Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer 477 * LONGDATETIME modified Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer 478 * SHORT xMin For all glyph bounding boxes. 479 * SHORT yMin For all glyph bounding boxes. 480 * SHORT xMax For all glyph bounding boxes. 481 * SHORT yMax For all glyph bounding boxes. 482 * USHORT macStyle Bit 0: Bold (if set to 1); 483 * Bit 1: Italic (if set to 1) 484 * Bit 2: Underline (if set to 1) 485 * Bit 3: Outline (if set to 1) 486 * Bit 4: Shadow (if set to 1) 487 * Bit 5: Condensed (if set to 1) 488 * Bit 6: Extended (if set to 1) 489 * Bits 7-15: Reserved (set to 0). 490 * USHORT lowestRecPPEM Smallest readable size in pixels. 491 * SHORT fontDirectionHint 0: Fully mixed directional glyphs; 492 * 1: Only strongly left to right; 493 * 2: Like 1 but also contains neutrals; 494 * -1: Only strongly right to left; 495 * -2: Like -1 but also contains neutrals. 1 496 * SHORT indexToLocFormat 0 for short offsets, 1 for long. 497 * SHORT glyphDataFormat 0 for current format. 498 */ 499class OpenTypeFileHEAD extends OpenTypeFileTable { 500 var $_version; 501 var $_fontRevision; 502 var $_checkSumAdjustment; 503 var $_magicNumber; 504 var $_flags; 505 var $_unitsPerEm; 506 var $_created; 507 var $_modified; 508 var $_xMin; 509 var $_yMin; 510 var $_xMax; 511 var $_yMax; 512 var $_macStyle; 513 var $_lowestRecPPEM; 514 var $_fontDirectionHint; 515 var $_indexToLocFormat; 516 var $_glyphDataFormat; 517 518 function OpenTypeFileHEAD() { 519 $this->OpenTypeFileTable(); 520 } 521 522 function _read($filehandle) { 523 $content = fread($filehandle, 4*4 + 11*2 + 2*8); 524 525 $unpacked = unpack("Nversion/NfontRevision/NcheckSumAdjustment/NmagicNumber/nflags/nunitsPerEm/N2created/N2modified/nxMin/nyMin/nxMax/nyMax/nmacStyle/nlowestRecPPEM/nfontDirectionHint/nindexToLocFormat/nglyphDataFormat", $content); 526 $this->_version = $unpacked['version']; 527 $this->_fontRevision = $unpacked['fontRevision']; 528 $this->_checkSumAdjustment = $unpacked['checkSumAdjustment']; 529 $this->_magicNumber = $unpacked['magicNumber']; 530 $this->_flags = $unpacked['flags']; 531 $this->_unitsPerEm = $unpacked['unitsPerEm']; 532 $this->_created = $unpacked['created1'] << 32 | $unpacked['created2']; 533 $this->_modified = $unpacked['modified1'] << 32 | $unpacked['modified2']; 534 $this->_xMin = $this->_fixShort($unpacked['xMin']); 535 $this->_yMin = $this->_fixShort($unpacked['yMin']); 536 $this->_xMax = $this->_fixShort($unpacked['xMax']); 537 $this->_yMax = $this->_fixShort($unpacked['yMax']); 538 $this->_macStyle = $unpacked['macStyle']; 539 $this->_lowestRecPPEM = $unpacked['lowestRecPPEM']; 540 $this->_fontDirectionHint = $this->_fixShort($unpacked['fontDirectionHint']); 541 $this->_indexToLocFormat = $this->_fixShort($unpacked['indexToLocFormat']); 542 $this->_glyphDataFormat = $this->_fixShort($unpacked['glyphDataFormat']); 543 } 544} 545 546class OpenTypeFileCMAP extends OpenTypeFileTable { 547 var $_header; 548 var $_encodings; 549 var $_subtables; 550 551 function OpenTypeFileCMAP() { 552 $this->OpenTypeFileTable(); 553 $this->_header = new OpenTypeFileCMAPHeader(); 554 $this->_encodings = array(); 555 $this->_subtables = array(); 556 } 557 558 function _read($filehandle) { 559 $this->_header->_read($filehandle); 560 561 for ($i=0; $i<$this->_header->_numTables; $i++) { 562 $encoding = new OpenTypeFileCMAPEncoding(); 563 $encoding->_read($filehandle); 564 $this->_encodings[] =& $encoding; 565 }; 566 } 567 568 /** 569 * It is assumed that current file position is set to the beginning 570 * of CMAP table 571 */ 572 function _getSubtable($filehandle, $offset) { 573 fseek($filehandle, $offset, SEEK_CUR); 574 575 $subtable = new OpenTypeFileCMAPSubtable(); 576 $subtable->_read($filehandle); 577 578 return $subtable; 579 } 580 581 function &findSubtable($platformId, $encodingId) { 582 $file = $this->getFontFile(); 583 584 $index = 0; 585 foreach ($this->_encodings as $encoding) { 586 if ($encoding->_platformId == $platformId && 587 $encoding->_encodingId == $encodingId) { 588 return $this->getSubtable($index); 589 }; 590 }; 591 592 $dummy = null; return $dummy; 593 } 594 595 function &getSubtable($index) { 596 if (!isset($this->_subtables[$index])) { 597 $file =& $this->getFontFile(); 598 $subtable =& $file->_getCMAPSubtable($this->_encodings[$index]->_offset); 599 $this->_subtables[$index] =& $subtable; 600 return $subtable; 601 } else { 602 return $this->_subtables[$index]; 603 }; 604 } 605} 606 607/** 608 * TODO: support for CMAP subtable formats other than 4 609 */ 610class OpenTypeFileCMAPSubtable { 611 var $_format; 612 var $_content; 613 614 function OpenTypeFileCMAPSubtable() { 615 $this->_content = null; 616 } 617 618 function lookup($unicode) { 619 return $this->_content->lookup($unicode); 620 } 621 622 function _read($filehandle) { 623 $content = fread($filehandle, 2); 624 625 $unpacked = unpack("nformat", $content); 626 $this->_format = $unpacked['format']; 627 628 switch ($this->_format) { 629 case 4: 630 $this->_content = new OpenTypeFileCMAPSubtable4(); 631 $this->_content->_read($filehandle); 632 return; 633 634 default: 635 die(sprintf("Unsupported CMAP subtable format: %i", $this->_format)); 636 } 637 } 638} 639 640class OpenTypeFileCMAPSubtable4 extends OpenTypeFileTable { 641 var $_length; 642 var $_language; 643 var $_segCountX2; 644 var $_searchRange; 645 var $_entrySelector; 646 var $_rangeShift; 647 var $_endCount; 648 var $_startCount; 649 var $_idDelta; 650 var $_idRangeOffset; 651 var $_glyphIdArray; 652 653 function OpenTypeFileCMAPSubtable4() { 654 $this->_endCount = array(); 655 $this->_startCount = array(); 656 $this->_idDelta = array(); 657 $this->_idRangeOffset = array(); 658 $this->_glyphIdArray = array(); 659 } 660 661 function lookup($unicode) { 662 $index = $this->_lookupSegment($unicode); 663 if (is_null($index)) { return null; }; 664 665 if ($this->_idRangeOffset[$index] != 0) { 666 /** 667 * If the idRangeOffset value for the segment is not 0, the 668 * mapping of character codes relies on glyphIdArray. The 669 * character code offset from startCode is added to the 670 * idRangeOffset value. This sum is used as an offset from the 671 * current location within idRangeOffset itself to index out the 672 * correct glyphIdArray value. This obscure indexing trick works 673 * because glyphIdArray immediately follows idRangeOffset in the 674 * font file. The C expression that yields the glyph index is: 675 * 676 * *(idRangeOffset[i]/2 + (c - startCount[i]) + &idRangeOffset[i]) 677 * 678 * The value c is the character code in question, and i is the 679 * segment index in which c appears. If the value obtained from 680 * the indexing operation is not 0 (which indicates 681 * missingGlyph), idDelta[i] is added to it to get the glyph 682 * index. The idDelta arithmetic is modulo 65536. 683 */ 684 $value = $this->_glyphIdArray[$unicode - $this->_startCount[$index]]; 685 return ($value + $this->_idDelta[$index]) % 65536; 686 687 } else { 688 /** 689 * If the idRangeOffset is 0, the idDelta value is added 690 * directly to the character code offset (i.e. idDelta[i] + c) 691 * to get the corresponding glyph index. Again, the idDelta 692 * arithmetic is modulo 65536. 693 */ 694 return ($this->_idDelta[$index] + $unicode) % 65536; 695 }; 696 } 697 698 /** 699 * The segments are sorted in order of increasing endCode values, 700 * and the segment values are specified in four parallel arrays. You 701 * search for the first endCode that is greater than or equal to the 702 * character code you want to map. 703 */ 704 function _lookupSegment($unicode) { 705 for ($i=0; $i<$this->_segCountX2/2; $i++) { 706 if ($unicode <= $this->_endCount[$i]) { 707 /** 708 * If the corresponding startCode is less than or equal to the 709 * character code, then you use the corresponding idDelta and 710 * idRangeOffset to map the character code to a glyph index 711 * (otherwise, the missingGlyph is returned). 712 */ 713 if ($this->_startCount[$i] <= $unicode) { 714 return $i; 715 } else { 716 return null; 717 }; 718 }; 719 }; 720 return null; 721 } 722 723 function _read($filehandle) { 724 $content = fread($filehandle, 6*2); 725 $unpacked = unpack("nlength/nlanguage/nsegCountX2/nsearchRange/nentrySelector/nrangeShift", $content); 726 $this->_length = $unpacked['length']; 727 $this->_language = $unpacked['language']; 728 $this->_segCountX2 = $unpacked['segCountX2']; 729 $this->_searchRange = $unpacked['searchRange']; 730 $this->_entrySelector = $unpacked['entrySelector']; 731 $this->_rangeShift = $unpacked['rangeShift']; 732 733 for ($i=0; $i<floor($this->_segCountX2/2); $i++) { 734 $content = fread($filehandle, 2); 735 $unpacked = unpack("nendCount", $content); 736 $this->_endCount[] = $unpacked['endCount']; 737 }; 738 739 // Skip 'reservedPad' field 740 $content = fread($filehandle, 2); 741 742 for ($i=0; $i<$this->_segCountX2/2; $i++) { 743 $content = fread($filehandle, 2); 744 $unpacked = unpack("nstartCount", $content); 745 $this->_startCount[] = $unpacked['startCount']; 746 }; 747 748 for ($i=0; $i<$this->_segCountX2/2; $i++) { 749 $content = fread($filehandle, 2); 750 $unpacked = unpack("nidDelta", $content); 751 $this->_idDelta[] = $this->_fixShort($unpacked['idDelta']); 752 }; 753 754 for ($i=0; $i<$this->_segCountX2/2; $i++) { 755 $content = fread($filehandle, 2); 756 $unpacked = unpack("nidRangeOffset", $content); 757 $this->_idRangeOffset[] = $unpacked['idRangeOffset']; 758 }; 759 760 for ($i=0; $i<$this->_length - 2*12; $i+=2) { 761 $content = fread($filehandle, 2); 762 $unpacked = unpack("nglyphId", $content); 763 $this->_glyphIdArray[] = $unpacked['glyphId']; 764 }; 765 } 766} 767 768class OpenTypeFileCMAPEncoding { 769 var $_platformId; 770 var $_encodingId; 771 var $_offset; 772 773 function _read($filehandle) { 774 $content = fread($filehandle, 2*2+4); 775 776 $unpacked = unpack("nplatformId/nencodingId/Noffset", $content); 777 $this->_platformId = $unpacked['platformId']; 778 $this->_encodingId = $unpacked['encodingId']; 779 $this->_offset = $unpacked['offset']; 780 } 781} 782 783class OpenTypeFileCMAPHeader { 784 var $_version; 785 var $_numTables; 786 787 function _read($filehandle) { 788 $content = fread($filehandle, 2*2); 789 790 $unpacked = unpack("nversion/nnumTables", $content); 791 $this->_version = $unpacked['version']; 792 $this->_numTables = $unpacked['numTables']; 793 } 794} 795 796// @TODO: v 1.0 support 797class OpenTypeFileMAXP extends OpenTypeFileTable { 798 var $_numGlyphs; 799 800 function OpenTypeFileMAXP() { 801 $this->OpenTypeFileTable(); 802 } 803 804 function _read($filehandle) { 805 $content = fread($filehandle, 4+2*1); 806 807 $unpacked = unpack("Nversion/nnumGlyphs", $content); 808 809 $version = $unpacked['version']; 810 $this->_numGlyphs = $unpacked['numGlyphs']; 811 } 812} 813 814class OpenTypeFileHHEA extends OpenTypeFileTable { 815 var $_ascender; 816 var $_descender; 817 var $_lineGap; 818 var $_advanceWidthMax; 819 var $_minLeftSideBearing; 820 var $_minRightSideBearing; 821 var $_xMaxExtent; 822 var $_caretSlopeRise; 823 var $_caretSlopeRun; 824 var $_caretOffset; 825 var $_metricDataFormat; 826 var $_numberOfHMetrics; 827 828 function OpenTypeFileHHEA() { 829 $this->OpenTypeFileTable(); 830 } 831 832 function _read($filehandle) { 833 $content = fread($filehandle, 4+16*2); 834 835 $unpacked = unpack("Nversion/nascender/ndescender/nlineGap/nadvanceWidthMax/nminLeftSideBearing/". 836 "nminRightSideBearing/nxMaxExtent/ncaretSlopeRise/ncaretSlopeRun/ncaretOffset/n4reserved/". 837 "nmetricDataFormat/nnumberOfHMetrics", $content); 838 839 $version = $unpacked['version']; 840 $this->_ascender = $this->_fixFWord($unpacked['ascender']); 841 $this->_descender = $this->_fixFWord($unpacked['descender']); 842 $this->_lineGap = $this->_fixFWord($unpacked['lineGap']); 843 $this->_advanceWidthMax = $unpacked['advanceWidthMax']; 844 $this->_minLeftSideBearing = $this->_fixFWord($unpacked['minLeftSideBearing']); 845 $this->_minRightSideBearing = $this->_fixFWord($unpacked['minRightSideBearing']); 846 $this->_xMaxExtent = $this->_fixFWord($unpacked['xMaxExtent']); 847 $this->_caretSlopeRise = $this->_fixShort($unpacked['caretSlopeRise']); 848 $this->_caretSlopeRun = $this->_fixShort($unpacked['caretSlopeRun']); 849 $this->_caretOffset = $this->_fixShort($unpacked['caretOffset']); 850 $this->_metricDataFormat = $this->_fixShort($unpacked['metricDataFormat']); 851 $this->_numberOfHMetrics = $unpacked['numberOfHMetrics']; 852 } 853} 854 855class OpenTypeFileHMTX extends OpenTypeFileTable { 856 var $_hMetrics; 857 var $_leftSideBearing; 858 859 function _delete() { 860 unset($this->_hMetrics); 861 unset($this->_leftSideBearing); 862 } 863 864 function OpenTypeFileHMTX() { 865 $this->OpenTypeFileTable(); 866 867 $this->_hMetrics = array(); 868 $this->_leftSideBearing = array(); 869 } 870 871 function _read($filehandle) { 872 $fontFile =& $this->getFontFile(); 873 $hhea =& $fontFile->getTable('hhea'); 874 $maxp =& $fontFile->getTable('maxp'); 875 876 for ($i=0; $i<$hhea->_numberOfHMetrics; $i++) { 877 $content = fread($filehandle, 2*2); 878 $unpacked = unpack("nadvanceWidth/nlsb", $content); 879 $this->_hMetrics[] = array('advanceWidth' => $unpacked['advanceWidth'], 880 'lsb' => $this->_fixShort($unpacked['lsb'])); 881 }; 882 883 for ($i=0; $i<$maxp->_numGlyphs; $i++) { 884 $content = fread($filehandle, 2); 885 $unpacked = unpack("nitem", $content); 886 $this->_leftSideBearing[] = $unpacked['item']; 887 }; 888 } 889} 890 891 892?>