1<?php 2 3namespace Mpdf; 4 5// Define the value used in the "head" table of a created TTF file 6// 0x74727565 "true" for Mac 7// 0x00010000 for Windows 8// Either seems to work for a font embedded in a PDF file 9// when read by Adobe Reader on a Windows PC(!) 10use Mpdf\Fonts\GlyphOperator; 11 12if (!defined('_TTF_MAC_HEADER')) { 13 define("_TTF_MAC_HEADER", false); 14} 15 16// Recalculate correct metadata/profiles when making subset fonts (not SIP/SMP) 17// e.g. xMin, xMax, maxNContours 18if (!defined('_RECALC_PROFILE')) { 19 define("_RECALC_PROFILE", false); 20} 21 22// mPDF 5.7.1 23if (!function_exists('Mpdf\unicode_hex')) { 24 function unicode_hex($unicode_dec) 25 { 26 return (sprintf("%05s", strtoupper(dechex($unicode_dec)))); 27 } 28} 29 30class OtlDump 31{ 32 33 var $GPOSFeatures; // mPDF 5.7.1 34 35 var $GPOSLookups; // mPDF 5.7.1 36 37 var $GPOSScriptLang; // mPDF 5.7.1 38 39 var $ignoreStrings; // mPDF 5.7.1 40 41 var $MarkAttachmentType; // mPDF 5.7.1 42 43 var $MarkGlyphSets; // mPDF 7.5.1 44 45 var $GlyphClassMarks; // mPDF 5.7.1 46 47 var $GlyphClassLigatures; // mPDF 5.7.1 48 49 var $GlyphClassBases; // mPDF 5.7.1 50 51 var $GlyphClassComponents; // mPDF 5.7.1 52 53 var $GSUBScriptLang; // mPDF 5.7.1 54 55 var $rtlPUAstr; // mPDF 5.7.1 56 57 var $rtlPUAarr; // mPDF 5.7.1 58 59 var $fontkey; // mPDF 5.7.1 60 61 var $useOTL; // mPDF 5.7.1 62 63 var $panose; 64 65 var $maxUni; 66 67 var $sFamilyClass; 68 69 var $sFamilySubClass; 70 71 var $sipset; 72 73 var $smpset; 74 75 var $_pos; 76 77 var $numTables; 78 79 var $searchRange; 80 81 var $entrySelector; 82 83 var $rangeShift; 84 85 var $tables; 86 87 var $otables; 88 89 var $filename; 90 91 var $fh; 92 93 var $glyphPos; 94 95 var $charToGlyph; 96 97 var $ascent; 98 99 var $descent; 100 101 var $name; 102 103 var $familyName; 104 105 var $styleName; 106 107 var $fullName; 108 109 var $uniqueFontID; 110 111 var $unitsPerEm; 112 113 var $bbox; 114 115 var $capHeight; 116 117 var $stemV; 118 119 var $italicAngle; 120 121 var $flags; 122 123 var $underlinePosition; 124 125 var $underlineThickness; 126 127 var $charWidths; 128 129 var $defaultWidth; 130 131 var $maxStrLenRead; 132 133 var $numTTCFonts; 134 135 var $TTCFonts; 136 137 var $maxUniChar; 138 139 var $kerninfo; 140 141 var $mode; 142 143 var $glyphToChar; 144 145 var $fontRevision; 146 147 var $glyphdata; 148 149 var $glyphIDtoUn; 150 151 var $restrictedUse; 152 153 var $GSUBFeatures; 154 155 var $GSUBLookups; 156 157 var $glyphIDtoUni; 158 159 var $GSLuCoverage; 160 161 var $version; 162 163 private $mpdf; 164 165 public function __construct(Mpdf $mpdf) 166 { 167 $this->mpdf = $mpdf; 168 $this->maxStrLenRead = 200000; // Maximum size of glyf table to read in as string (otherwise reads each glyph from file) 169 } 170 171 function getMetrics($file, $fontkey, $TTCfontID = 0, $debug = false, $BMPonly = false, $kerninfo = false, $useOTL = 0, $mode = null) 172 { 173 // mPDF 5.7.1 174 $this->mode = $mode; 175 $this->useOTL = $useOTL; // mPDF 5.7.1 176 $this->fontkey = $fontkey; // mPDF 5.7.1 177 $this->filename = $file; 178 $this->fh = fopen($file, 'rb'); 179 180 if (!$this->fh) { 181 throw new \Mpdf\Exception\FontException(sprintf('Unable to open file "%s"', $file)); 182 } 183 184 $this->_pos = 0; 185 $this->charWidths = ''; 186 $this->glyphPos = []; 187 $this->charToGlyph = []; 188 $this->tables = []; 189 $this->otables = []; 190 $this->kerninfo = []; 191 $this->ascent = 0; 192 $this->descent = 0; 193 $this->numTTCFonts = 0; 194 $this->TTCFonts = []; 195 $this->version = $version = $this->read_ulong(); 196 $this->panose = []; 197 198 if ($version == 0x4F54544F) { 199 throw new \Mpdf\Exception\FontException(sprintf('Fonts with postscript outlines are not supported (%s)', $file)); 200 } 201 202 if ($version == 0x74746366 && !$TTCfontID) { 203 throw new \Mpdf\Exception\FontException("TTCfontID for a TrueType Collection has to be defined in ttfontdata configuration key (" . $file . ")"); 204 } 205 206 if (!in_array($version, [0x00010000, 0x74727565]) && !$TTCfontID) { 207 throw new \Mpdf\Exception\FontException("Not a TrueType font: version=" . $version); 208 } 209 210 if ($TTCfontID > 0) { 211 $this->version = $version = $this->read_ulong(); // TTC Header version now 212 if (!in_array($version, [0x00010000, 0x00020000])) { 213 throw new \Mpdf\Exception\FontException("Error parsing TrueType Collection: version=" . $version . " - " . $file); 214 } 215 $this->numTTCFonts = $this->read_ulong(); 216 for ($i = 1; $i <= $this->numTTCFonts; $i++) { 217 $this->TTCFonts[$i]['offset'] = $this->read_ulong(); 218 } 219 $this->seek($this->TTCFonts[$TTCfontID]['offset']); 220 $this->version = $version = $this->read_ulong(); // TTFont version again now 221 } 222 $this->readTableDirectory($debug); 223 $this->extractInfo($debug, $BMPonly, $kerninfo, $useOTL); 224 fclose($this->fh); 225 } 226 227 function readTableDirectory($debug = false) 228 { 229 $this->numTables = $this->read_ushort(); 230 $this->searchRange = $this->read_ushort(); 231 $this->entrySelector = $this->read_ushort(); 232 $this->rangeShift = $this->read_ushort(); 233 $this->tables = []; 234 for ($i = 0; $i < $this->numTables; $i++) { 235 $record = []; 236 $record['tag'] = $this->read_tag(); 237 $record['checksum'] = [$this->read_ushort(), $this->read_ushort()]; 238 $record['offset'] = $this->read_ulong(); 239 $record['length'] = $this->read_ulong(); 240 $this->tables[$record['tag']] = $record; 241 } 242 if ($debug) { 243 $this->checksumTables(); 244 } 245 } 246 247 function checksumTables() 248 { 249 // Check the checksums for all tables 250 foreach ($this->tables as $t) { 251 if ($t['length'] > 0 && $t['length'] < $this->maxStrLenRead) { // 1.02 252 $table = $this->get_chunk($t['offset'], $t['length']); 253 $checksum = $this->calcChecksum($table); 254 if ($t['tag'] == 'head') { 255 $up = unpack('n*', substr($table, 8, 4)); 256 $adjustment[0] = $up[1]; 257 $adjustment[1] = $up[2]; 258 $checksum = $this->sub32($checksum, $adjustment); 259 } 260 $xchecksum = $t['checksum']; 261 if ($xchecksum != $checksum) { 262 throw new \Mpdf\Exception\FontException(sprintf('TTF file "%s": invalid checksum %s table: %s (expected %s)', $this->filename, dechex($checksum[0]) . dechex($checksum[1]), $t['tag'], dechex($xchecksum[0]) . dechex($xchecksum[1]))); 263 } 264 } 265 } 266 } 267 268 function sub32($x, $y) 269 { 270 $xlo = $x[1]; 271 $xhi = $x[0]; 272 $ylo = $y[1]; 273 $yhi = $y[0]; 274 if ($ylo > $xlo) { 275 $xlo += 1 << 16; 276 $yhi += 1; 277 } 278 $reslo = $xlo - $ylo; 279 if ($yhi > $xhi) { 280 $xhi += 1 << 16; 281 } 282 $reshi = $xhi - $yhi; 283 $reshi = $reshi & 0xFFFF; 284 285 return [$reshi, $reslo]; 286 } 287 288 function calcChecksum($data) 289 { 290 if (strlen($data) % 4) { 291 $data .= str_repeat("\0", (4 - (strlen($data) % 4))); 292 } 293 $len = strlen($data); 294 $hi = 0x0000; 295 $lo = 0x0000; 296 for ($i = 0; $i < $len; $i += 4) { 297 $hi += (ord($data[$i]) << 8) + ord($data[$i + 1]); 298 $lo += (ord($data[$i + 2]) << 8) + ord($data[$i + 3]); 299 $hi += ($lo >> 16) & 0xFFFF; 300 $lo = $lo & 0xFFFF; 301 } 302 303 return [$hi, $lo]; 304 } 305 306 function get_table_pos($tag) 307 { 308 $offset = isset($this->tables[$tag]['offset']) ? $this->tables[$tag]['offset'] : null; 309 $length = isset($this->tables[$tag]['length']) ? $this->tables[$tag]['length'] : null; 310 311 return [$offset, $length]; 312 } 313 314 function seek($pos) 315 { 316 $this->_pos = $pos; 317 fseek($this->fh, $this->_pos); 318 } 319 320 function skip($delta) 321 { 322 $this->_pos = $this->_pos + $delta; 323 fseek($this->fh, $delta, SEEK_CUR); 324 } 325 326 function seek_table($tag, $offset_in_table = 0) 327 { 328 $tpos = $this->get_table_pos($tag); 329 $this->_pos = $tpos[0] + $offset_in_table; 330 fseek($this->fh, $this->_pos); 331 332 return $this->_pos; 333 } 334 335 function read_tag() 336 { 337 $this->_pos += 4; 338 339 return fread($this->fh, 4); 340 } 341 342 function read_short() 343 { 344 $this->_pos += 2; 345 $s = fread($this->fh, 2); 346 $a = (ord($s[0]) << 8) + ord($s[1]); 347 if ($a & (1 << 15)) { 348 $a = ($a - (1 << 16)); 349 } 350 351 return $a; 352 } 353 354 function unpack_short($s) 355 { 356 $a = (ord($s[0]) << 8) + ord($s[1]); 357 if ($a & (1 << 15)) { 358 $a = ($a - (1 << 16)); 359 } 360 361 return $a; 362 } 363 364 function read_ushort() 365 { 366 $this->_pos += 2; 367 $s = fread($this->fh, 2); 368 369 return (ord($s[0]) << 8) + ord($s[1]); 370 } 371 372 function read_ulong() 373 { 374 $this->_pos += 4; 375 $s = fread($this->fh, 4); 376 377 // if large uInt32 as an integer, PHP converts it to -ve 378 return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 16777216 = 1<<24 379 } 380 381 function get_ushort($pos) 382 { 383 fseek($this->fh, $pos); 384 $s = fread($this->fh, 2); 385 386 return (ord($s[0]) << 8) + ord($s[1]); 387 } 388 389 function get_ulong($pos) 390 { 391 fseek($this->fh, $pos); 392 $s = fread($this->fh, 4); 393 394 // iF large uInt32 as an integer, PHP converts it to -ve 395 return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 16777216 = 1<<24 396 } 397 398 function pack_short($val) 399 { 400 if ($val < 0) { 401 $val = abs($val); 402 $val = ~$val; 403 $val += 1; 404 } 405 406 return pack("n", $val); 407 } 408 409 function splice($stream, $offset, $value) 410 { 411 return substr($stream, 0, $offset) . $value . substr($stream, $offset + strlen($value)); 412 } 413 414 function _set_ushort($stream, $offset, $value) 415 { 416 $up = pack("n", $value); 417 418 return $this->splice($stream, $offset, $up); 419 } 420 421 function _set_short($stream, $offset, $val) 422 { 423 if ($val < 0) { 424 $val = abs($val); 425 $val = ~$val; 426 $val += 1; 427 } 428 $up = pack("n", $val); 429 430 return $this->splice($stream, $offset, $up); 431 } 432 433 function get_chunk($pos, $length) 434 { 435 fseek($this->fh, $pos); 436 if ($length < 1) { 437 return ''; 438 } 439 440 return (fread($this->fh, $length)); 441 } 442 443 function get_table($tag) 444 { 445 list($pos, $length) = $this->get_table_pos($tag); 446 if ($length == 0) { 447 return ''; 448 } 449 fseek($this->fh, $pos); 450 451 return (fread($this->fh, $length)); 452 } 453 454 function add($tag, $data) 455 { 456 if ($tag == 'head') { 457 $data = $this->splice($data, 8, "\0\0\0\0"); 458 } 459 $this->otables[$tag] = $data; 460 } 461 462 ///////////////////////////////////////////////////////////////////////////////////////// 463 ///////////////////////////////////////////////////////////////////////////////////////// 464 465 function extractInfo($debug = false, $BMPonly = false, $kerninfo = false, $useOTL = 0) 466 { 467 $this->panose = []; 468 $this->sFamilyClass = 0; 469 $this->sFamilySubClass = 0; 470 /////////////////////////////////// 471 // name - Naming table 472 /////////////////////////////////// 473 $name_offset = $this->seek_table("name"); 474 $format = $this->read_ushort(); 475 if ($format != 0 && $format != 1) { 476 throw new \Mpdf\Exception\FontException("Error loading font: Unknown name table format " . $format); 477 } 478 $numRecords = $this->read_ushort(); 479 $string_data_offset = $name_offset + $this->read_ushort(); 480 $names = [1 => '', 2 => '', 3 => '', 4 => '', 6 => '']; 481 $K = array_keys($names); 482 $nameCount = count($names); 483 for ($i = 0; $i < $numRecords; $i++) { 484 $platformId = $this->read_ushort(); 485 $encodingId = $this->read_ushort(); 486 $languageId = $this->read_ushort(); 487 $nameId = $this->read_ushort(); 488 $length = $this->read_ushort(); 489 $offset = $this->read_ushort(); 490 if (!in_array($nameId, $K)) { 491 continue; 492 } 493 $N = ''; 494 if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name 495 $opos = $this->_pos; 496 $this->seek($string_data_offset + $offset); 497 if ($length % 2 != 0) { 498 throw new \Mpdf\Exception\FontException("Error loading font: PostScript name is UTF-16BE string of odd length"); 499 } 500 $length /= 2; 501 $N = ''; 502 while ($length > 0) { 503 $char = $this->read_ushort(); 504 $N .= (chr($char)); 505 $length -= 1; 506 } 507 $this->_pos = $opos; 508 $this->seek($opos); 509 } else { 510 if ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name 511 $opos = $this->_pos; 512 $N = $this->get_chunk($string_data_offset + $offset, $length); 513 $this->_pos = $opos; 514 $this->seek($opos); 515 } 516 } 517 if ($N && $names[$nameId] == '') { 518 $names[$nameId] = $N; 519 $nameCount -= 1; 520 if ($nameCount == 0) { 521 break; 522 } 523 } 524 } 525 if ($names[6]) { 526 $psName = $names[6]; 527 } else { 528 if ($names[4]) { 529 $psName = preg_replace('/ /', '-', $names[4]); 530 } else { 531 if ($names[1]) { 532 $psName = preg_replace('/ /', '-', $names[1]); 533 } else { 534 $psName = ''; 535 } 536 } 537 } 538 if (!$psName) { 539 throw new \Mpdf\Exception\FontException("Error loading font: Could not find PostScript font name: " . $this->filename); 540 } 541 if ($debug) { 542 for ($i = 0; $i < count($psName); $i++) { 543 $c = $psName[$i]; 544 $oc = ord($c); 545 if ($oc > 126 || strpos(' [](){}<>/%', $c) !== false) { 546 throw new \Mpdf\Exception\FontException("psName=" . $psName . " contains invalid character " . $c . " ie U+" . ord($c)); 547 } 548 } 549 } 550 $this->name = $psName; 551 if ($names[1]) { 552 $this->familyName = $names[1]; 553 } else { 554 $this->familyName = $psName; 555 } 556 if ($names[2]) { 557 $this->styleName = $names[2]; 558 } else { 559 $this->styleName = 'Regular'; 560 } 561 if ($names[4]) { 562 $this->fullName = $names[4]; 563 } else { 564 $this->fullName = $psName; 565 } 566 if ($names[3]) { 567 $this->uniqueFontID = $names[3]; 568 } else { 569 $this->uniqueFontID = $psName; 570 } 571 572 if ($names[6]) { 573 $this->fullName = $names[6]; 574 } 575 576 /////////////////////////////////// 577 // head - Font header table 578 /////////////////////////////////// 579 $this->seek_table("head"); 580 if ($debug) { 581 $ver_maj = $this->read_ushort(); 582 $ver_min = $this->read_ushort(); 583 if ($ver_maj != 1) { 584 throw new \Mpdf\Exception\FontException('Error loading font: Unknown head table version ' . $ver_maj . '.' . $ver_min); 585 } 586 $this->fontRevision = $this->read_ushort() . $this->read_ushort(); 587 588 $this->skip(4); 589 $magic = $this->read_ulong(); 590 if ($magic != 0x5F0F3CF5) { 591 throw new \Mpdf\Exception\FontException('Error loading font: Invalid head table magic ' . $magic); 592 } 593 $this->skip(2); 594 } else { 595 $this->skip(18); 596 } 597 $this->unitsPerEm = $unitsPerEm = $this->read_ushort(); 598 $scale = 1000 / $unitsPerEm; 599 $this->skip(16); 600 $xMin = $this->read_short(); 601 $yMin = $this->read_short(); 602 $xMax = $this->read_short(); 603 $yMax = $this->read_short(); 604 $this->bbox = [($xMin * $scale), ($yMin * $scale), ($xMax * $scale), ($yMax * $scale)]; 605 $this->skip(3 * 2); 606 $indexToLocFormat = $this->read_ushort(); 607 $glyphDataFormat = $this->read_ushort(); 608 if ($glyphDataFormat != 0) { 609 throw new \Mpdf\Exception\FontException('Error loading font: Unknown glyph data format ' . $glyphDataFormat); 610 } 611 612 /////////////////////////////////// 613 // hhea metrics table 614 /////////////////////////////////// 615 // ttf2t1 seems to use this value rather than the one in OS/2 - so put in for compatibility 616 if (isset($this->tables["hhea"])) { 617 $this->seek_table("hhea"); 618 $this->skip(4); 619 $hheaAscender = $this->read_short(); 620 $hheaDescender = $this->read_short(); 621 $this->ascent = ($hheaAscender * $scale); 622 $this->descent = ($hheaDescender * $scale); 623 } 624 625 /////////////////////////////////// 626 // OS/2 - OS/2 and Windows metrics table 627 /////////////////////////////////// 628 if (isset($this->tables["OS/2"])) { 629 $this->seek_table("OS/2"); 630 $version = $this->read_ushort(); 631 $this->skip(2); 632 $usWeightClass = $this->read_ushort(); 633 $this->skip(2); 634 $fsType = $this->read_ushort(); 635 if ($fsType == 0x0002 || ($fsType & 0x0300) != 0) { 636 global $overrideTTFFontRestriction; 637 if (!$overrideTTFFontRestriction) { 638 throw new \Mpdf\Exception\FontException('Font file ' . $this->filename . ' cannot be embedded due to copyright restrictions.'); 639 } 640 $this->restrictedUse = true; 641 } 642 $this->skip(20); 643 $sF = $this->read_short(); 644 $this->sFamilyClass = ($sF >> 8); 645 $this->sFamilySubClass = ($sF & 0xFF); 646 $this->_pos += 10; //PANOSE = 10 byte length 647 $panose = fread($this->fh, 10); 648 $this->panose = []; 649 for ($p = 0; $p < strlen($panose); $p++) { 650 $this->panose[] = ord($panose[$p]); 651 } 652 $this->skip(26); 653 $sTypoAscender = $this->read_short(); 654 $sTypoDescender = $this->read_short(); 655 if (!$this->ascent) { 656 $this->ascent = ($sTypoAscender * $scale); 657 } 658 if (!$this->descent) { 659 $this->descent = ($sTypoDescender * $scale); 660 } 661 if ($version > 1) { 662 $this->skip(16); 663 $sCapHeight = $this->read_short(); 664 $this->capHeight = ($sCapHeight * $scale); 665 } else { 666 $this->capHeight = $this->ascent; 667 } 668 } else { 669 $usWeightClass = 500; 670 if (!$this->ascent) { 671 $this->ascent = ($yMax * $scale); 672 } 673 if (!$this->descent) { 674 $this->descent = ($yMin * $scale); 675 } 676 $this->capHeight = $this->ascent; 677 } 678 $this->stemV = 50 + intval(pow(($usWeightClass / 65.0), 2)); 679 680 /////////////////////////////////// 681 // post - PostScript table 682 /////////////////////////////////// 683 $this->seek_table("post"); 684 if ($debug) { 685 $ver_maj = $this->read_ushort(); 686 $ver_min = $this->read_ushort(); 687 if ($ver_maj < 1 || $ver_maj > 4) { 688 throw new \Mpdf\Exception\FontException('Error loading font: Unknown post table version ' . $ver_maj); 689 } 690 } else { 691 $this->skip(4); 692 } 693 $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0; 694 $this->underlinePosition = $this->read_short() * $scale; 695 $this->underlineThickness = $this->read_short() * $scale; 696 $isFixedPitch = $this->read_ulong(); 697 698 $this->flags = 4; 699 700 if ($this->italicAngle != 0) { 701 $this->flags = $this->flags | 64; 702 } 703 if ($usWeightClass >= 600) { 704 $this->flags = $this->flags | 262144; 705 } 706 if ($isFixedPitch) { 707 $this->flags = $this->flags | 1; 708 } 709 710 /////////////////////////////////// 711 // hhea - Horizontal header table 712 /////////////////////////////////// 713 $this->seek_table("hhea"); 714 if ($debug) { 715 $ver_maj = $this->read_ushort(); 716 $ver_min = $this->read_ushort(); 717 if ($ver_maj != 1) { 718 throw new \Mpdf\Exception\FontException(sprintf('Error loading font: Unknown hhea table version %s', $ver_maj)); 719 } 720 $this->skip(28); 721 } else { 722 $this->skip(32); 723 } 724 $metricDataFormat = $this->read_ushort(); 725 if ($metricDataFormat != 0) { 726 throw new \Mpdf\Exception\FontException('Error loading font: Unknown horizontal metric data format ' . $metricDataFormat); 727 } 728 $numberOfHMetrics = $this->read_ushort(); 729 if ($numberOfHMetrics == 0) { 730 throw new \Mpdf\Exception\FontException('Error loading font: Number of horizontal metrics is 0'); 731 } 732 733 /////////////////////////////////// 734 // maxp - Maximum profile table 735 /////////////////////////////////// 736 $this->seek_table("maxp"); 737 if ($debug) { 738 $ver_maj = $this->read_ushort(); 739 $ver_min = $this->read_ushort(); 740 if ($ver_maj != 1) { 741 throw new \Mpdf\Exception\FontException('Error loading font: Unknown maxp table version ' . $ver_maj); 742 } 743 } else { 744 $this->skip(4); 745 } 746 $numGlyphs = $this->read_ushort(); 747 748 /////////////////////////////////// 749 // cmap - Character to glyph index mapping table 750 /////////////////////////////////// 751 $cmap_offset = $this->seek_table("cmap"); 752 $this->skip(2); 753 $cmapTableCount = $this->read_ushort(); 754 $unicode_cmap_offset = 0; 755 for ($i = 0; $i < $cmapTableCount; $i++) { 756 $platformID = $this->read_ushort(); 757 $encodingID = $this->read_ushort(); 758 $offset = $this->read_ulong(); 759 $save_pos = $this->_pos; 760 if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode 761 $format = $this->get_ushort($cmap_offset + $offset); 762 if ($format == 4) { 763 if (!$unicode_cmap_offset) { 764 $unicode_cmap_offset = $cmap_offset + $offset; 765 } 766 if ($BMPonly) { 767 break; 768 } 769 } 770 } // Microsoft, Unicode Format 12 table HKCS 771 else { 772 if ((($platformID == 3 && $encodingID == 10) || $platformID == 0) && !$BMPonly) { 773 $format = $this->get_ushort($cmap_offset + $offset); 774 if ($format == 12) { 775 $unicode_cmap_offset = $cmap_offset + $offset; 776 break; 777 } 778 } 779 } 780 $this->seek($save_pos); 781 } 782 783 if (!$unicode_cmap_offset) { 784 throw new \Mpdf\Exception\FontException('Font (' . $this->filename . ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)'); 785 } 786 787 $sipset = false; 788 $smpset = false; 789 790 // mPDF 5.7.1 791 $this->GSUBScriptLang = []; 792 $this->rtlPUAstr = ''; 793 $this->rtlPUAarr = []; 794 $this->GSUBFeatures = []; 795 $this->GSUBLookups = []; 796 $this->GPOSScriptLang = []; 797 $this->GPOSFeatures = []; 798 $this->GPOSLookups = []; 799 $this->glyphIDtoUni = ''; 800 801 // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above 802 if ($format == 12 && !$BMPonly) { 803 $this->maxUniChar = 0; 804 $this->seek($unicode_cmap_offset + 4); 805 $length = $this->read_ulong(); 806 $limit = $unicode_cmap_offset + $length; 807 $this->skip(4); 808 809 $nGroups = $this->read_ulong(); 810 811 $glyphToChar = []; 812 $charToGlyph = []; 813 for ($i = 0; $i < $nGroups; $i++) { 814 $startCharCode = $this->read_ulong(); 815 $endCharCode = $this->read_ulong(); 816 $startGlyphCode = $this->read_ulong(); 817 if ($endCharCode > 0x20000 && $endCharCode < 0x2FFFF) { 818 $sipset = true; 819 } else { 820 if ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) { 821 $smpset = true; 822 } 823 } 824 $offset = 0; 825 for ($unichar = $startCharCode; $unichar <= $endCharCode; $unichar++) { 826 $glyph = $startGlyphCode + $offset; 827 $offset++; 828 if ($unichar < 0x30000) { 829 $charToGlyph[$unichar] = $glyph; 830 $this->maxUniChar = max($unichar, $this->maxUniChar); 831 $glyphToChar[$glyph][] = $unichar; 832 } 833 } 834 } 835 } else { 836 $glyphToChar = []; 837 $charToGlyph = []; 838 $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph); 839 } 840 $this->sipset = $sipset; 841 $this->smpset = $smpset; 842 843 /////////////////////////////////// 844 // mPDF 5.7.1 845 // Map Unmapped glyphs - from $numGlyphs 846 if ($this->useOTL) { 847 $bctr = 0xE000; 848 for ($gid = 1; $gid < $numGlyphs; $gid++) { 849 if (!isset($glyphToChar[$gid])) { 850 while (isset($charToGlyph[$bctr])) { 851 $bctr++; 852 } // Avoid overwriting a glyph already mapped in PUA 853 if (($bctr > 0xF8FF) && ($bctr < 0x2CEB0)) { 854 if (!$BMPonly) { 855 $bctr = 0x2CEB0; // Use unassigned area 0x2CEB0 to 0x2F7FF (space for 10,000 characters) 856 $this->sipset = $sipset = true; // forces subsetting; also ensure charwidths are saved 857 while (isset($charToGlyph[$bctr])) { 858 $bctr++; 859 } 860 } else { 861 throw new \Mpdf\Exception\FontException(sprintf('Font "%s" does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)', $this->filename)); 862 } 863 } 864 $glyphToChar[$gid][] = $bctr; 865 $charToGlyph[$bctr] = $gid; 866 $this->maxUniChar = max($bctr, $this->maxUniChar); 867 $bctr++; 868 } 869 } 870 } 871 $this->glyphToChar = $glyphToChar; 872 $this->charToGlyph = $charToGlyph; 873 /////////////////////////////////// 874 // mPDF 5.7.1 OpenType Layout tables 875 $this->GSUBScriptLang = []; 876 $this->rtlPUAstr = ''; 877 $this->rtlPUAarr = []; 878 if ($useOTL) { 879 $this->_getGDEFtables(); 880 list($this->GSUBScriptLang, $this->GSUBFeatures, $this->GSUBLookups, $this->rtlPUAstr, $this->rtlPUAarr) = $this->_getGSUBtables(); 881 list($this->GPOSScriptLang, $this->GPOSFeatures, $this->GPOSLookups) = $this->_getGPOStables(); 882 $this->glyphIDtoUni = str_pad('', 256 * 256 * 3, "\x00"); 883 foreach ($glyphToChar as $gid => $arr) { 884 if (isset($glyphToChar[$gid][0])) { 885 $char = $glyphToChar[$gid][0]; 886 if ($char != 0 && $char != 65535) { 887 $this->glyphIDtoUni[$gid * 3] = chr($char >> 16); 888 $this->glyphIDtoUni[$gid * 3 + 1] = chr(($char >> 8) & 0xFF); 889 $this->glyphIDtoUni[$gid * 3 + 2] = chr($char & 0xFF); 890 } 891 } 892 } 893 } 894 /////////////////////////////////// 895 /////////////////////////////////// 896 // hmtx - Horizontal metrics table 897 /////////////////////////////////// 898 $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale); 899 900 /////////////////////////////////// 901 // kern - Kerning pair table 902 /////////////////////////////////// 903 if ($kerninfo) { 904 // Recognises old form of Kerning table - as required by Windows - Format 0 only 905 $kern_offset = $this->seek_table("kern"); 906 $version = $this->read_ushort(); 907 $nTables = $this->read_ushort(); 908 // subtable header 909 $sversion = $this->read_ushort(); 910 $slength = $this->read_ushort(); 911 $scoverage = $this->read_ushort(); 912 $format = $scoverage >> 8; 913 if ($kern_offset && $version == 0 && $format == 0) { 914 // Format 0 915 $nPairs = $this->read_ushort(); 916 $this->skip(6); 917 for ($i = 0; $i < $nPairs; $i++) { 918 $left = $this->read_ushort(); 919 $right = $this->read_ushort(); 920 $val = $this->read_short(); 921 if (count($glyphToChar[$left]) == 1 && count($glyphToChar[$right]) == 1) { 922 if ($left != 32 && $right != 32) { 923 $this->kerninfo[$glyphToChar[$left][0]][$glyphToChar[$right][0]] = intval($val * $scale); 924 } 925 } 926 } 927 } 928 } 929 } 930 931 ///////////////////////////////////////////////////////////////////////////////////////// 932 function _getGDEFtables() 933 { 934 /////////////////////////////////// 935 // GDEF - Glyph Definition 936 /////////////////////////////////// 937 // http://www.microsoft.com/typography/otspec/gdef.htm 938 if (isset($this->tables["GDEF"])) { 939 if ($this->mode == 'summary') { 940 $this->mpdf->WriteHTML('<h1>GDEF table</h1>'); 941 } 942 $gdef_offset = $this->seek_table("GDEF"); 943 // ULONG Version of the GDEF table-currently 0x00010000 944 $ver_maj = $this->read_ushort(); 945 $ver_min = $this->read_ushort(); 946 // Version 0x00010002 of GDEF header contains additional Offset to a list defining mark glyph set definitions (MarkGlyphSetDef) 947 $GlyphClassDef_offset = $this->read_ushort(); 948 $AttachList_offset = $this->read_ushort(); 949 $LigCaretList_offset = $this->read_ushort(); 950 $MarkAttachClassDef_offset = $this->read_ushort(); 951 if ($ver_min == 2) { 952 $MarkGlyphSetsDef_offset = $this->read_ushort(); 953 } 954 955 // GlyphClassDef 956 $this->seek($gdef_offset + $GlyphClassDef_offset); 957 /* 958 1 Base glyph (single character, spacing glyph) 959 2 Ligature glyph (multiple character, spacing glyph) 960 3 Mark glyph (non-spacing combining glyph) 961 4 Component glyph (part of single character, spacing glyph) 962 */ 963 $GlyphByClass = $this->_getClassDefinitionTable(); 964 965 if ($this->mode == 'summary') { 966 $this->mpdf->WriteHTML('<h2>Glyph classes</h2>'); 967 } 968 969 if (isset($GlyphByClass[1]) && count($GlyphByClass[1]) > 0) { 970 $this->GlyphClassBases = $this->formatClassArr($GlyphByClass[1]); 971 if ($this->mode == 'summary') { 972 $this->mpdf->WriteHTML('<h3>Glyph class 1</h3>'); 973 $this->mpdf->WriteHTML('<h5>Base glyph (single character, spacing glyph)</h5>'); 974 $html = ''; 975 $html .= '<div class="glyphs">'; 976 foreach ($GlyphByClass[1] as $g) { 977 $html .= '&#x' . $g . '; '; 978 } 979 $html .= '</div>'; 980 $this->mpdf->WriteHTML($html); 981 } 982 } else { 983 $this->GlyphClassBases = ''; 984 } 985 if (isset($GlyphByClass[2]) && count($GlyphByClass[2]) > 0) { 986 $this->GlyphClassLigatures = $this->formatClassArr($GlyphByClass[2]); 987 if ($this->mode == 'summary') { 988 $this->mpdf->WriteHTML('<h3>Glyph class 2</h3>'); 989 $this->mpdf->WriteHTML('<h5>Ligature glyph (multiple character, spacing glyph)</h5>'); 990 $html = ''; 991 $html .= '<div class="glyphs">'; 992 foreach ($GlyphByClass[2] as $g) { 993 $html .= '&#x' . $g . '; '; 994 } 995 $html .= '</div>'; 996 $this->mpdf->WriteHTML($html); 997 } 998 } else { 999 $this->GlyphClassLigatures = ''; 1000 } 1001 if (isset($GlyphByClass[3]) && count($GlyphByClass[3]) > 0) { 1002 $this->GlyphClassMarks = $this->formatClassArr($GlyphByClass[3]); 1003 if ($this->mode == 'summary') { 1004 $this->mpdf->WriteHTML('<h3>Glyph class 3</h3>'); 1005 $this->mpdf->WriteHTML('<h5>Mark glyph (non-spacing combining glyph)</h5>'); 1006 $html = ''; 1007 $html .= '<div class="glyphs">'; 1008 foreach ($GlyphByClass[3] as $g) { 1009 $html .= '◌&#x' . $g . '; '; 1010 } 1011 $html .= '</div>'; 1012 $this->mpdf->WriteHTML($html); 1013 } 1014 } else { 1015 $this->GlyphClassMarks = ''; 1016 } 1017 if (isset($GlyphByClass[4]) && count($GlyphByClass[4]) > 0) { 1018 $this->GlyphClassComponents = $this->formatClassArr($GlyphByClass[4]); 1019 if ($this->mode == 'summary') { 1020 $this->mpdf->WriteHTML('<h3>Glyph class 4</h3>'); 1021 $this->mpdf->WriteHTML('<h5>Component glyph (part of single character, spacing glyph)</h5>'); 1022 $html = ''; 1023 $html .= '<div class="glyphs">'; 1024 foreach ($GlyphByClass[4] as $g) { 1025 $html .= '&#x' . $g . '; '; 1026 } 1027 $html .= '</div>'; 1028 $this->mpdf->WriteHTML($html); 1029 } 1030 } else { 1031 $this->GlyphClassComponents = ''; 1032 } 1033 1034 $Marks = $GlyphByClass[3]; // to use for MarkAttachmentType 1035 1036 /* Required for GPOS 1037 // Attachment List 1038 if ($AttachList_offset) { 1039 $this->seek($gdef_offset+$AttachList_offset ); 1040 } 1041 The Attachment Point List table (AttachmentList) identifies all the attachment points defined in the GPOS table and their associated glyphs so a client can quickly access coordinates for each glyph's attachment points. As a result, the client can cache coordinates for attachment points along with glyph bitmaps and avoid recalculating the attachment points each time it displays a glyph. Without this table, processing speed would be slower because the client would have to decode the GPOS lookups that define attachment points and compile the points in a list. 1042 1043 The Attachment List table (AttachList) may be used to cache attachment point coordinates along with glyph bitmaps. 1044 1045 The table consists of an offset to a Coverage table (Coverage) listing all glyphs that define attachment points in the GPOS table, a count of the glyphs with attachment points (GlyphCount), and an array of offsets to AttachPoint tables (AttachPoint). The array lists the AttachPoint tables, one for each glyph in the Coverage table, in the same order as the Coverage Index. 1046 AttachList table 1047 Type Name Description 1048 Offset Coverage Offset to Coverage table - from beginning of AttachList table 1049 uint16 GlyphCount Number of glyphs with attachment points 1050 Offset AttachPoint[GlyphCount] Array of offsets to AttachPoint tables-from beginning of AttachList table-in Coverage Index order 1051 1052 An AttachPoint table consists of a count of the attachment points on a single glyph (PointCount) and an array of contour indices of those points (PointIndex), listed in increasing numerical order. 1053 1054 AttachPoint table 1055 Type Name Description 1056 uint16 PointCount Number of attachment points on this glyph 1057 uint16 PointIndex[PointCount] Array of contour point indices -in increasing numerical order 1058 1059 See Example 3 - http://www.microsoft.com/typography/otspec/gdef.htm 1060 */ 1061 1062 // Ligature Caret List 1063 // The Ligature Caret List table (LigCaretList) defines caret positions for all the ligatures in a font. 1064 // Not required for mDPF 1065 // MarkAttachmentType 1066 if ($MarkAttachClassDef_offset) { 1067 if ($this->mode == 'summary') { 1068 $this->mpdf->WriteHTML('<h1>Mark Attachment Types</h1>'); 1069 } 1070 $this->seek($gdef_offset + $MarkAttachClassDef_offset); 1071 $MarkAttachmentTypes = $this->_getClassDefinitionTable(); 1072 foreach ($MarkAttachmentTypes as $class => $glyphs) { 1073 if (is_array($Marks) && count($Marks)) { 1074 $mat = array_diff($Marks, $MarkAttachmentTypes[$class]); 1075 sort($mat, SORT_STRING); 1076 } else { 1077 $mat = []; 1078 } 1079 1080 $this->MarkAttachmentType[$class] = $this->formatClassArr($mat); 1081 1082 if ($this->mode == 'summary') { 1083 $this->mpdf->WriteHTML('<h3>Mark Attachment Type: ' . $class . '</h3>'); 1084 $html = ''; 1085 $html .= '<div class="glyphs">'; 1086 foreach ($glyphs as $g) { 1087 $html .= '◌&#x' . $g . '; '; 1088 } 1089 $html .= '</div>'; 1090 $this->mpdf->WriteHTML($html); 1091 } 1092 } 1093 } else { 1094 $this->MarkAttachmentType = []; 1095 } 1096 1097 // MarkGlyphSets only in Version 0x00010002 of GDEF 1098 if ($ver_min == 2 && $MarkGlyphSetsDef_offset) { 1099 if ($this->mode == 'summary') { 1100 $this->mpdf->WriteHTML('<h1>Mark Glyph Sets</h1>'); 1101 } 1102 $this->seek($gdef_offset + $MarkGlyphSetsDef_offset); 1103 $MarkSetTableFormat = $this->read_ushort(); 1104 $MarkSetCount = $this->read_ushort(); 1105 $MarkSetOffset = []; 1106 for ($i = 0; $i < $MarkSetCount; $i++) { 1107 $MarkSetOffset[] = $this->read_ulong(); 1108 } 1109 for ($i = 0; $i < $MarkSetCount; $i++) { 1110 $this->seek($MarkSetOffset[$i]); 1111 $glyphs = $this->_getCoverage(); 1112 $this->MarkGlyphSets[$i] = $this->formatClassArr($glyphs); 1113 if ($this->mode == 'summary') { 1114 $this->mpdf->WriteHTML('<h3>Mark Glyph Set class: ' . $i . '</h3>'); 1115 $html = ''; 1116 $html .= '<div class="glyphs">'; 1117 foreach ($glyphs as $g) { 1118 $html .= '◌&#x' . $g . '; '; 1119 } 1120 $html .= '</div>'; 1121 $this->mpdf->WriteHTML($html); 1122 } 1123 } 1124 } else { 1125 $this->MarkGlyphSets = []; 1126 } 1127 } else { 1128 $this->mpdf->WriteHTML('<div>GDEF table not defined</div>'); 1129 } 1130 1131//echo $this->GlyphClassMarks ; exit; 1132//print_r($GlyphClass); exit; 1133//print_r($GlyphByClass); exit; 1134 } 1135 1136 function _getClassDefinitionTable($offset = 0) 1137 { 1138 1139 if ($offset > 0) { 1140 $this->seek($offset); 1141 } 1142 1143 // NB Any glyph not included in the range of covered GlyphIDs automatically belongs to Class 0. This is not returned by this function 1144 $ClassFormat = $this->read_ushort(); 1145 $GlyphByClass = []; 1146 if ($ClassFormat == 1) { 1147 $StartGlyph = $this->read_ushort(); 1148 $GlyphCount = $this->read_ushort(); 1149 for ($i = 0; $i < $GlyphCount; $i++) { 1150 $gid = $StartGlyph + $i; 1151 $class = $this->read_ushort(); 1152 $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]); 1153 } 1154 } else { 1155 if ($ClassFormat == 2) { 1156 $tableCount = $this->read_ushort(); 1157 for ($i = 0; $i < $tableCount; $i++) { 1158 $startGlyphID = $this->read_ushort(); 1159 $endGlyphID = $this->read_ushort(); 1160 $class = $this->read_ushort(); 1161 for ($gid = $startGlyphID; $gid <= $endGlyphID; $gid++) { 1162 $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]); 1163 } 1164 } 1165 } 1166 } 1167 ksort($GlyphByClass); 1168 1169 return $GlyphByClass; 1170 } 1171 1172 function _getGSUBtables() 1173 { 1174 /////////////////////////////////// 1175 // GSUB - Glyph Substitution 1176 /////////////////////////////////// 1177 if (isset($this->tables["GSUB"])) { 1178 $this->mpdf->WriteHTML('<h1>GSUB Tables</h1>'); 1179 $ffeats = []; 1180 $gsub_offset = $this->seek_table("GSUB"); 1181 $this->skip(4); 1182 $ScriptList_offset = $gsub_offset + $this->read_ushort(); 1183 $FeatureList_offset = $gsub_offset + $this->read_ushort(); 1184 $LookupList_offset = $gsub_offset + $this->read_ushort(); 1185 1186 // ScriptList 1187 $this->seek($ScriptList_offset); 1188 $ScriptCount = $this->read_ushort(); 1189 for ($i = 0; $i < $ScriptCount; $i++) { 1190 $ScriptTag = $this->read_tag(); // = "beng", "deva" etc. 1191 $ScriptTableOffset = $this->read_ushort(); 1192 $ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset; 1193 } 1194 1195 // Script Table 1196 foreach ($ffeats as $t => $o) { 1197 $ls = []; 1198 $this->seek($o); 1199 $DefLangSys_offset = $this->read_ushort(); 1200 if ($DefLangSys_offset > 0) { 1201 $ls['DFLT'] = $DefLangSys_offset + $o; 1202 } 1203 $LangSysCount = $this->read_ushort(); 1204 for ($i = 0; $i < $LangSysCount; $i++) { 1205 $LangTag = $this->read_tag(); // = 1206 $LangTableOffset = $this->read_ushort(); 1207 $ls[$LangTag] = $o + $LangTableOffset; 1208 } 1209 $ffeats[$t] = $ls; 1210 } 1211//print_r($ffeats); exit; 1212 // Get FeatureIndexList 1213 // LangSys Table - from first listed langsys 1214 foreach ($ffeats as $st => $scripts) { 1215 foreach ($scripts as $t => $o) { 1216 $FeatureIndex = []; 1217 $langsystable_offset = $o; 1218 $this->seek($langsystable_offset); 1219 $LookUpOrder = $this->read_ushort(); //==NULL 1220 $ReqFeatureIndex = $this->read_ushort(); 1221 if ($ReqFeatureIndex != 0xFFFF) { 1222 $FeatureIndex[] = $ReqFeatureIndex; 1223 } 1224 $FeatureCount = $this->read_ushort(); 1225 for ($i = 0; $i < $FeatureCount; $i++) { 1226 $FeatureIndex[] = $this->read_ushort(); // = index of feature 1227 } 1228 $ffeats[$st][$t] = $FeatureIndex; 1229 } 1230 } 1231//print_r($ffeats); exit; 1232 // Feauture List => LookupListIndex es 1233 $this->seek($FeatureList_offset); 1234 $FeatureCount = $this->read_ushort(); 1235 $Feature = []; 1236 for ($i = 0; $i < $FeatureCount; $i++) { 1237 $Feature[$i] = ['tag' => $this->read_tag()]; 1238 $Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort(); 1239 } 1240 for ($i = 0; $i < $FeatureCount; $i++) { 1241 $this->seek($Feature[$i]['offset']); 1242 $this->read_ushort(); // null 1243 $Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort(); 1244 $Feature[$i]['LookupListIndex'] = []; 1245 for ($c = 0; $c < $Lookupcount; $c++) { 1246 $Feature[$i]['LookupListIndex'][] = $this->read_ushort(); 1247 } 1248 } 1249 1250 foreach ($ffeats as $st => $scripts) { 1251 foreach ($scripts as $t => $o) { 1252 $FeatureIndex = $ffeats[$st][$t]; 1253 foreach ($FeatureIndex as $k => $fi) { 1254 $ffeats[$st][$t][$k] = $Feature[$fi]; 1255 } 1256 } 1257 } 1258 //===================================================================================== 1259 $gsub = []; 1260 $GSUBScriptLang = []; 1261 foreach ($ffeats as $st => $scripts) { 1262 foreach ($scripts as $t => $langsys) { 1263 $lg = []; 1264 foreach ($langsys as $ft) { 1265 $lg[$ft['LookupListIndex'][0]] = $ft; 1266 } 1267 // list of Lookups in order they need to be run i.e. order listed in Lookup table 1268 ksort($lg); 1269 foreach ($lg as $ft) { 1270 $gsub[$st][$t][$ft['tag']] = $ft['LookupListIndex']; 1271 } 1272 if (!isset($GSUBScriptLang[$st])) { 1273 $GSUBScriptLang[$st] = ''; 1274 } 1275 $GSUBScriptLang[$st] .= $t . ' '; 1276 } 1277 } 1278 1279//print_r($gsub); exit; 1280 1281 if ($this->mode == 'summary') { 1282 $this->mpdf->WriteHTML('<h3>GSUB Scripts & Languages</h3>'); 1283 $this->mpdf->WriteHTML('<div class="glyphs">'); 1284 $html = ''; 1285 if (count($gsub)) { 1286 foreach ($gsub as $st => $g) { 1287 $html .= '<h5>' . $st . '</h5>'; 1288 foreach ($g as $l => $t) { 1289 $html .= '<div><a href="font_dump_OTL.php?script=' . $st . '&lang=' . $l . '">' . $l . '</a></b>: '; 1290 foreach ($t as $tag => $o) { 1291 $html .= $tag . ' '; 1292 } 1293 $html .= '</div>'; 1294 } 1295 } 1296 } else { 1297 $html .= '<div>No entries in GSUB table.</div>'; 1298 } 1299 $this->mpdf->WriteHTML($html); 1300 $this->mpdf->WriteHTML('</div>'); 1301 1302 return 0; 1303 } 1304 1305 //===================================================================================== 1306 // Get metadata and offsets for whole Lookup List table 1307 $this->seek($LookupList_offset); 1308 $LookupCount = $this->read_ushort(); 1309 $GSLookup = []; 1310 $Offsets = []; 1311 $SubtableCount = []; 1312 for ($i = 0; $i < $LookupCount; $i++) { 1313 $Offsets[$i] = $LookupList_offset + $this->read_ushort(); 1314 } 1315 for ($i = 0; $i < $LookupCount; $i++) { 1316 $this->seek($Offsets[$i]); 1317 $GSLookup[$i]['Type'] = $this->read_ushort(); 1318 $GSLookup[$i]['Flag'] = $flag = $this->read_ushort(); 1319 $GSLookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort(); 1320 for ($c = 0; $c < $SubtableCount[$i]; $c++) { 1321 $GSLookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort(); 1322 } 1323 // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure 1324 if (($flag & 0x0010) == 0x0010) { 1325 $GSLookup[$i]['MarkFilteringSet'] = $this->read_ushort(); 1326 } 1327 // else { $GSLookup[$i]['MarkFilteringSet'] = ''; } 1328 // Lookup Type 7: Extension 1329 if ($GSLookup[$i]['Type'] == 7) { 1330 // Overwrites new offset (32-bit) for each subtable, and a new lookup Type 1331 for ($c = 0; $c < $SubtableCount[$i]; $c++) { 1332 $this->seek($GSLookup[$i]['Subtables'][$c]); 1333 $ExtensionPosFormat = $this->read_ushort(); 1334 $type = $this->read_ushort(); 1335 $GSLookup[$i]['Subtables'][$c] = $GSLookup[$i]['Subtables'][$c] + $this->read_ulong(); 1336 } 1337 $GSLookup[$i]['Type'] = $type; 1338 } 1339 } 1340 1341//print_r($GSLookup); exit; 1342 //===================================================================================== 1343 // Process Whole LookupList - Get LuCoverage = Lookup coverage just for first glyph 1344 $this->GSLuCoverage = []; 1345 for ($i = 0; $i < $LookupCount; $i++) { 1346 for ($c = 0; $c < $GSLookup[$i]['SubtableCount']; $c++) { 1347 $this->seek($GSLookup[$i]['Subtables'][$c]); 1348 $PosFormat = $this->read_ushort(); 1349 1350 if ($GSLookup[$i]['Type'] == 5 && $PosFormat == 3) { 1351 $this->skip(4); 1352 } else { 1353 if ($GSLookup[$i]['Type'] == 6 && $PosFormat == 3) { 1354 $BacktrackGlyphCount = $this->read_ushort(); 1355 $this->skip(2 * $BacktrackGlyphCount + 2); 1356 } 1357 } 1358 // NB Coverage only looks at glyphs for position 1 (i.e. 5.3 and 6.3) // NEEDS TO READ ALL ******************** 1359 $Coverage = $GSLookup[$i]['Subtables'][$c] + $this->read_ushort(); 1360 $this->seek($Coverage); 1361 $glyphs = $this->_getCoverage(); 1362 $this->GSLuCoverage[$i][$c] = implode('|', $glyphs); 1363 } 1364 } 1365 1366// $this->GSLuCoverage and $GSLookup 1367 //===================================================================================== 1368 $s = '<?php 1369$GSLuCoverage = ' . var_export($this->GSLuCoverage, true) . '; 1370?>'; 1371 1372 //===================================================================================== 1373 $s = '<?php 1374$GlyphClassBases = \'' . $this->GlyphClassBases . '\'; 1375$GlyphClassMarks = \'' . $this->GlyphClassMarks . '\'; 1376$GlyphClassLigatures = \'' . $this->GlyphClassLigatures . '\'; 1377$GlyphClassComponents = \'' . $this->GlyphClassComponents . '\'; 1378$MarkGlyphSets = ' . var_export($this->MarkGlyphSets, true) . '; 1379$MarkAttachmentType = ' . var_export($this->MarkAttachmentType, true) . '; 1380?>'; 1381 1382 //===================================================================================== 1383 //===================================================================================== 1384 //===================================================================================== 1385// Now repeats as original to get Substitution rules 1386 //===================================================================================== 1387 //===================================================================================== 1388 //===================================================================================== 1389 // Get metadata and offsets for whole Lookup List table 1390 $this->seek($LookupList_offset); 1391 $LookupCount = $this->read_ushort(); 1392 $Lookup = []; 1393 for ($i = 0; $i < $LookupCount; $i++) { 1394 $Lookup[$i]['offset'] = $LookupList_offset + $this->read_ushort(); 1395 } 1396 for ($i = 0; $i < $LookupCount; $i++) { 1397 $this->seek($Lookup[$i]['offset']); 1398 $Lookup[$i]['Type'] = $this->read_ushort(); 1399 $Lookup[$i]['Flag'] = $flag = $this->read_ushort(); 1400 $Lookup[$i]['SubtableCount'] = $this->read_ushort(); 1401 for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { 1402 $Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['offset'] + $this->read_ushort(); 1403 } 1404 // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure 1405 if (($flag & 0x0010) == 0x0010) { 1406 $Lookup[$i]['MarkFilteringSet'] = $this->read_ushort(); 1407 } else { 1408 $Lookup[$i]['MarkFilteringSet'] = ''; 1409 } 1410 1411 // Lookup Type 7: Extension 1412 if ($Lookup[$i]['Type'] == 7) { 1413 // Overwrites new offset (32-bit) for each subtable, and a new lookup Type 1414 for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { 1415 $this->seek($Lookup[$i]['Subtable'][$c]['Offset']); 1416 $ExtensionPosFormat = $this->read_ushort(); 1417 $type = $this->read_ushort(); 1418 $Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ulong(); 1419 } 1420 $Lookup[$i]['Type'] = $type; 1421 } 1422 } 1423 1424//print_r($Lookup); exit; 1425 //===================================================================================== 1426 // Process (1) Whole LookupList 1427 for ($i = 0; $i < $LookupCount; $i++) { 1428 for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { 1429 $this->seek($Lookup[$i]['Subtable'][$c]['Offset']); 1430 $SubstFormat = $this->read_ushort(); 1431 $Lookup[$i]['Subtable'][$c]['Format'] = $SubstFormat; 1432 1433 /* 1434 Lookup['Type'] Enumeration table for glyph substitution 1435 Value Type Description 1436 1 Single Replace one glyph with one glyph 1437 2 Multiple Replace one glyph with more than one glyph 1438 3 Alternate Replace one glyph with one of many glyphs 1439 4 Ligature Replace multiple glyphs with one glyph 1440 5 Context Replace one or more glyphs in context 1441 6 Chaining Context Replace one or more glyphs in chained context 1442 7 Extension Substitution Extension mechanism for other substitutions (i.e. this excludes the Extension type substitution itself) 1443 8 Reverse chaining context single Applied in reverse order, replace single glyph in chaining context 1444 */ 1445 1446 // LookupType 1: Single Substitution Subtable 1447 if ($Lookup[$i]['Type'] == 1) { 1448 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1449 if ($SubstFormat == 1) { // Calculated output glyph indices 1450 $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'] = $this->read_short(); 1451 } else { 1452 if ($SubstFormat == 2) { // Specified output glyph indices 1453 $GlyphCount = $this->read_ushort(); 1454 for ($g = 0; $g < $GlyphCount; $g++) { 1455 $Lookup[$i]['Subtable'][$c]['Glyphs'][] = $this->read_ushort(); 1456 } 1457 } 1458 } 1459 } // LookupType 2: Multiple Substitution Subtable 1460 else { 1461 if ($Lookup[$i]['Type'] == 2) { 1462 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1463 $Lookup[$i]['Subtable'][$c]['SequenceCount'] = $SequenceCount = $this->read_short(); 1464 for ($s = 0; $s < $SequenceCount; $s++) { 1465 $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short(); 1466 } 1467 for ($s = 0; $s < $SequenceCount; $s++) { 1468 // Sequence Tables 1469 $this->seek($Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset']); 1470 $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount'] = $this->read_short(); 1471 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount']; $g++) { 1472 $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['SubstituteGlyphID'][] = $this->read_ushort(); 1473 } 1474 } 1475 } // LookupType 3: Alternate Forms 1476 else { 1477 if ($Lookup[$i]['Type'] == 3) { 1478 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1479 $Lookup[$i]['Subtable'][$c]['AlternateSetCount'] = $AlternateSetCount = $this->read_short(); 1480 for ($s = 0; $s < $AlternateSetCount; $s++) { 1481 $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short(); 1482 } 1483 1484 for ($s = 0; $s < $AlternateSetCount; $s++) { 1485 // AlternateSet Tables 1486 $this->seek($Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset']); 1487 $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount'] = $this->read_short(); 1488 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount']; $g++) { 1489 $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['SubstituteGlyphID'][] = $this->read_ushort(); 1490 } 1491 } 1492 } // LookupType 4: Ligature Substitution Subtable 1493 else { 1494 if ($Lookup[$i]['Type'] == 4) { 1495 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1496 $Lookup[$i]['Subtable'][$c]['LigSetCount'] = $LigSetCount = $this->read_short(); 1497 for ($s = 0; $s < $LigSetCount; $s++) { 1498 $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short(); 1499 } 1500 for ($s = 0; $s < $LigSetCount; $s++) { 1501 // LigatureSet Tables 1502 $this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset']); 1503 $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount'] = $this->read_short(); 1504 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) { 1505 $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g] = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] + $this->read_ushort(); 1506 } 1507 } 1508 for ($s = 0; $s < $LigSetCount; $s++) { 1509 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) { 1510 // Ligature tables 1511 $this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g]); 1512 $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph'] = $this->read_ushort(); 1513 $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount'] = $this->read_ushort(); 1514 for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) { 1515 $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l] = $this->read_ushort(); 1516 } 1517 } 1518 } 1519 } // LookupType 5: Contextual Substitution Subtable 1520 else { 1521 if ($Lookup[$i]['Type'] == 5) { 1522 // Format 1: Context Substitution 1523 if ($SubstFormat == 1) { 1524 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1525 $Lookup[$i]['Subtable'][$c]['SubRuleSetCount'] = $SubRuleSetCount = $this->read_short(); 1526 for ($s = 0; $s < $SubRuleSetCount; $s++) { 1527 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short(); 1528 } 1529 for ($s = 0; $s < $SubRuleSetCount; $s++) { 1530 // SubRuleSet Tables 1531 $this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset']); 1532 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount'] = $this->read_short(); 1533 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) { 1534 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] + $this->read_ushort(); 1535 } 1536 } 1537 for ($s = 0; $s < $SubRuleSetCount; $s++) { 1538 // SubRule Tables 1539 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) { 1540 // Ligature tables 1541 $this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g]); 1542 1543 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount'] = $this->read_ushort(); 1544 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount'] = $this->read_ushort(); 1545 // "Input"::[GlyphCount - 1]::Array of input GlyphIDs-start with second glyph 1546 for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount']; $l++) { 1547 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['Input'][$l] = $this->read_ushort(); 1548 } 1549 // "SubstLookupRecord"::[SubstCount]::Array of SubstLookupRecords-in design order 1550 for ($l = 0; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount']; $l++) { 1551 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['SequenceIndex'] = $this->read_ushort(); 1552 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['LookupListIndex'] = $this->read_ushort(); 1553 } 1554 } 1555 } 1556 } // Format 2: Class-based Context Glyph Substitution 1557 else { 1558 if ($SubstFormat == 2) { 1559 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1560 $Lookup[$i]['Subtable'][$c]['ClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1561 $Lookup[$i]['Subtable'][$c]['SubClassSetCnt'] = $this->read_ushort(); 1562 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $b++) { 1563 $offset = $this->read_ushort(); 1564 if ($offset == 0x0000) { 1565 $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = 0; 1566 } else { 1567 $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset; 1568 } 1569 } 1570 } else { 1571 throw new \Mpdf\Exception\FontException("GPOS Lookup Type " . $Lookup[$i]['Type'] . ", Format " . $SubstFormat . " not supported (ttfontsuni.php)."); 1572 } 1573 } 1574 } // LookupType 6: Chaining Contextual Substitution Subtable 1575 else { 1576 if ($Lookup[$i]['Type'] == 6) { 1577 // Format 1: Simple Chaining Context Glyph Substitution p255 1578 if ($SubstFormat == 1) { 1579 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1580 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount'] = $this->read_ushort(); 1581 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $b++) { 1582 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1583 } 1584 } // Format 2: Class-based Chaining Context Glyph Substitution p257 1585 else { 1586 if ($SubstFormat == 2) { 1587 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1588 $Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1589 $Lookup[$i]['Subtable'][$c]['InputClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1590 $Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1591 $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt'] = $this->read_ushort(); 1592 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $b++) { 1593 $offset = $this->read_ushort(); 1594 if ($offset == 0x0000) { 1595 $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $offset; 1596 } else { 1597 $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset; 1598 } 1599 } 1600 } // Format 3: Coverage-based Chaining Context Glyph Substitution p259 1601 else { 1602 if ($SubstFormat == 3) { 1603 $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount'] = $this->read_ushort(); 1604 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) { 1605 $Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1606 } 1607 $Lookup[$i]['Subtable'][$c]['InputGlyphCount'] = $this->read_ushort(); 1608 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) { 1609 $Lookup[$i]['Subtable'][$c]['CoverageInput'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1610 } 1611 $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount'] = $this->read_ushort(); 1612 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) { 1613 $Lookup[$i]['Subtable'][$c]['CoverageLookahead'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); 1614 } 1615 $Lookup[$i]['Subtable'][$c]['SubstCount'] = $this->read_ushort(); 1616 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) { 1617 $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'] = $this->read_ushort(); 1618 $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'] = $this->read_ushort(); 1619 /* 1620 Substitution Lookup Record 1621 All contextual substitution subtables specify the substitution data in a Substitution Lookup Record (SubstLookupRecord). Each record contains a SequenceIndex, which indicates the position where the substitution will occur in the glyph sequence. In addition, a LookupListIndex identifies the lookup to be applied at the glyph position specified by the SequenceIndex. 1622 */ 1623 } 1624 } 1625 } 1626 } 1627 } else { 1628 throw new \Mpdf\Exception\FontException("Lookup Type " . $Lookup[$i]['Type'] . " not supported."); 1629 } 1630 } 1631 } 1632 } 1633 } 1634 } 1635 } 1636 } 1637//print_r($Lookup); exit; 1638 //===================================================================================== 1639 // Process (2) Whole LookupList 1640 // Get Coverage tables and prepare preg_replace 1641 for ($i = 0; $i < $LookupCount; $i++) { 1642 for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { 1643 $SubstFormat = $Lookup[$i]['Subtable'][$c]['Format']; 1644 1645 // LookupType 1: Single Substitution Subtable 1 => 1 1646 if ($Lookup[$i]['Type'] == 1) { 1647 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); 1648 $glyphs = $this->_getCoverage(false); 1649 for ($g = 0; $g < count($glyphs); $g++) { 1650 $replace = []; 1651 $substitute = []; 1652 $replace[] = unicode_hex($this->glyphToChar[$glyphs[$g]][0]); 1653 // Flag = Ignore 1654 if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) { 1655 continue; 1656 } 1657 if (isset($Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])) { // Format 1 1658 $substitute[] = unicode_hex($this->glyphToChar[($glyphs[$g] + $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])][0]); 1659 } else { // Format 2 1660 $substitute[] = unicode_hex($this->glyphToChar[($Lookup[$i]['Subtable'][$c]['Glyphs'][$g])][0]); 1661 } 1662 $Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute]; 1663 } 1664 } // LookupType 2: Multiple Substitution Subtable 1 => n 1665 else { 1666 if ($Lookup[$i]['Type'] == 2) { 1667 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); 1668 $glyphs = $this->_getCoverage(); 1669 for ($g = 0; $g < count($glyphs); $g++) { 1670 $replace = []; 1671 $substitute = []; 1672 $replace[] = $glyphs[$g]; 1673 // Flag = Ignore 1674 if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) { 1675 continue; 1676 } 1677 if (!isset($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) || count($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) == 0) { 1678 continue; 1679 } // Illegal for GlyphCount to be 0; either error in font, or something has gone wrong - lets carry on for now! 1680 foreach ($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID'] as $sub) { 1681 $substitute[] = unicode_hex($this->glyphToChar[$sub][0]); 1682 } 1683 $Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute]; 1684 } 1685 } // LookupType 3: Alternate Forms 1 => 1 (only first alternate form is used) 1686 else { 1687 if ($Lookup[$i]['Type'] == 3) { 1688 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); 1689 $glyphs = $this->_getCoverage(); 1690 for ($g = 0; $g < count($glyphs); $g++) { 1691 $replace = []; 1692 $substitute = []; 1693 $replace[] = $glyphs[$g]; 1694 // Flag = Ignore 1695 if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) { 1696 continue; 1697 } 1698 1699 for ($gl = 0; $gl < $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['GlyphCount']; $gl++) { 1700 $gid = $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['SubstituteGlyphID'][$gl]; 1701 $substitute[] = unicode_hex($this->glyphToChar[$gid][0]); 1702 } 1703 1704 //$gid = $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['SubstituteGlyphID'][0]; 1705 //$substitute[] = unicode_hex($this->glyphToChar[$gid][0]); 1706 1707 $Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute]; 1708 } 1709 if ($i == 166) { 1710 print_r($Lookup[$i]['Subtable']); 1711 exit; 1712 } 1713 } // LookupType 4: Ligature Substitution Subtable n => 1 1714 else { 1715 if ($Lookup[$i]['Type'] == 4) { 1716 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); 1717 $glyphs = $this->_getCoverage(); 1718 $LigSetCount = $Lookup[$i]['Subtable'][$c]['LigSetCount']; 1719 for ($s = 0; $s < $LigSetCount; $s++) { 1720 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) { 1721 $replace = []; 1722 $substitute = []; 1723 $replace[] = $glyphs[$s]; 1724 // Flag = Ignore 1725 if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) { 1726 continue; 1727 } 1728 for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) { 1729 $gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l]; 1730 $rpl = unicode_hex($this->glyphToChar[$gid][0]); 1731 // Flag = Ignore 1732 if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $rpl, $Lookup[$i]['MarkFilteringSet'])) { 1733 continue 2; 1734 } 1735 $replace[] = $rpl; 1736 } 1737 $gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph']; 1738 $substitute[] = unicode_hex($this->glyphToChar[$gid][0]); 1739 $Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute, 'CompCount' => $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']]; 1740 } 1741 } 1742 } // LookupType 5: Contextual Substitution Subtable 1743 else { 1744 if ($Lookup[$i]['Type'] == 5) { 1745 // Format 1: Context Substitution 1746 if ($SubstFormat == 1) { 1747 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); 1748 $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage(); 1749 1750 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) { 1751 $SubRuleSet = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]; 1752 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph'] = $CoverageGlyphs[$s]; 1753 for ($r = 0; $r < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $r++) { 1754 $GlyphCount = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['GlyphCount']; 1755 for ($g = 1; $g < $GlyphCount; $g++) { 1756 $glyphID = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['Input'][$g]; 1757 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]); 1758 } 1759 } 1760 } 1761 } // Format 2: Class-based Context Glyph Substitution 1762 else { 1763 if ($SubstFormat == 2) { 1764 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); 1765 $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage(); 1766 1767 $InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['ClassDefOffset']); 1768 $Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses; 1769 1770 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) { 1771 if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) { 1772 $this->seek($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s]); 1773 $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt'] = $SubClassRuleCnt = $this->read_ushort(); 1774 $SubClassRule = []; 1775 for ($b = 0; $b < $SubClassRuleCnt; $b++) { 1776 $SubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] + $this->read_ushort(); 1777 $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $SubClassRule[$b]; 1778 } 1779 } 1780 } 1781 1782 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) { 1783 $SubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt']; 1784 for ($b = 0; $b < $SubClassRuleCnt; $b++) { 1785 if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) { 1786 $this->seek($Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b]); 1787 $Rule = []; 1788 $Rule['InputGlyphCount'] = $this->read_ushort(); 1789 $Rule['SubstCount'] = $this->read_ushort(); 1790 for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) { 1791 $Rule['Input'][$r] = $this->read_ushort(); 1792 } 1793 for ($r = 0; $r < $Rule['SubstCount']; $r++) { 1794 $Rule['SequenceIndex'][$r] = $this->read_ushort(); 1795 $Rule['LookupListIndex'][$r] = $this->read_ushort(); 1796 } 1797 1798 $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $Rule; 1799 } 1800 } 1801 } 1802 } // Format 3: Coverage-based Context Glyph Substitution 1803 else { 1804 if ($SubstFormat == 3) { 1805 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) { 1806 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]); 1807 $glyphs = $this->_getCoverage(); 1808 $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs); 1809 } 1810 throw new \Mpdf\Exception\FontException("Lookup Type 5, SubstFormat 3 not tested. Please report this with the name of font used - " . $this->fontkey); 1811 } 1812 } 1813 } 1814 } // LookupType 6: Chaining Contextual Substitution Subtable 1815 else { 1816 if ($Lookup[$i]['Type'] == 6) { 1817 // Format 1: Simple Chaining Context Glyph Substitution p255 1818 if ($SubstFormat == 1) { 1819 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); 1820 $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage(); 1821 1822 $ChainSubRuleSetCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; 1823 1824 for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) { 1825 $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s]); 1826 $ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount'] = $this->read_ushort(); 1827 for ($r = 0; $r < $ChainSubRuleCnt; $r++) { 1828 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r] = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s] + $this->read_ushort(); 1829 } 1830 } 1831 for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) { 1832 $ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount']; 1833 for ($r = 0; $r < $ChainSubRuleCnt; $r++) { 1834 // ChainSubRule 1835 $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r]); 1836 1837 $BacktrackGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphCount'] = $this->read_ushort(); 1838 for ($g = 0; $g < $BacktrackGlyphCount; $g++) { 1839 $glyphID = $this->read_ushort(); 1840 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]); 1841 } 1842 1843 $InputGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphCount'] = $this->read_ushort(); 1844 for ($g = 1; $g < $InputGlyphCount; $g++) { 1845 $glyphID = $this->read_ushort(); 1846 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]); 1847 } 1848 1849 $LookaheadGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphCount'] = $this->read_ushort(); 1850 for ($g = 0; $g < $LookaheadGlyphCount; $g++) { 1851 $glyphID = $this->read_ushort(); 1852 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]); 1853 } 1854 1855 $SubstCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SubstCount'] = $this->read_ushort(); 1856 for ($lu = 0; $lu < $SubstCount; $lu++) { 1857 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SequenceIndex'][$lu] = $this->read_ushort(); 1858 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookupListIndex'][$lu] = $this->read_ushort(); 1859 } 1860 } 1861 } 1862 } // Format 2: Class-based Chaining Context Glyph Substitution p257 1863 else { 1864 if ($SubstFormat == 2) { 1865 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); 1866 $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage(); 1867 1868 $BacktrackClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset']); 1869 $Lookup[$i]['Subtable'][$c]['BacktrackClasses'] = $BacktrackClasses; 1870 1871 $InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['InputClassDefOffset']); 1872 $Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses; 1873 1874 $LookaheadClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset']); 1875 $Lookup[$i]['Subtable'][$c]['LookaheadClasses'] = $LookaheadClasses; 1876 1877 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) { 1878 if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) { 1879 $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s]); 1880 $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'] = $ChainSubClassRuleCnt = $this->read_ushort(); 1881 $ChainSubClassRule = []; 1882 for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) { 1883 $ChainSubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] + $this->read_ushort(); 1884 $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $ChainSubClassRule[$b]; 1885 } 1886 } 1887 } 1888 1889 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) { 1890 $ChainSubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt']; 1891 for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) { 1892 if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) { 1893 $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b]); 1894 $Rule = []; 1895 $Rule['BacktrackGlyphCount'] = $this->read_ushort(); 1896 for ($r = 0; $r < $Rule['BacktrackGlyphCount']; $r++) { 1897 $Rule['Backtrack'][$r] = $this->read_ushort(); 1898 } 1899 $Rule['InputGlyphCount'] = $this->read_ushort(); 1900 for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) { 1901 $Rule['Input'][$r] = $this->read_ushort(); 1902 } 1903 $Rule['LookaheadGlyphCount'] = $this->read_ushort(); 1904 for ($r = 0; $r < $Rule['LookaheadGlyphCount']; $r++) { 1905 $Rule['Lookahead'][$r] = $this->read_ushort(); 1906 } 1907 $Rule['SubstCount'] = $this->read_ushort(); 1908 for ($r = 0; $r < $Rule['SubstCount']; $r++) { 1909 $Rule['SequenceIndex'][$r] = $this->read_ushort(); 1910 $Rule['LookupListIndex'][$r] = $this->read_ushort(); 1911 } 1912 1913 $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $Rule; 1914 } 1915 } 1916 } 1917 } // Format 3: Coverage-based Chaining Context Glyph Substitution p259 1918 else { 1919 if ($SubstFormat == 3) { 1920 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) { 1921 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][$b]); 1922 $glyphs = $this->_getCoverage(); 1923 $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'][] = implode("|", $glyphs); 1924 } 1925 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) { 1926 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]); 1927 $glyphs = $this->_getCoverage(); 1928 $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs); 1929 // Don't use above value as these are ordered numerically not as need to process 1930 } 1931 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) { 1932 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageLookahead'][$b]); 1933 $glyphs = $this->_getCoverage(); 1934 $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'][] = implode("|", $glyphs); 1935 } 1936 } 1937 } 1938 } 1939 } 1940 } 1941 } 1942 } 1943 } 1944 } 1945 } 1946 } 1947 1948 //===================================================================================== 1949 //===================================================================================== 1950 //===================================================================================== 1951 1952 $st = $this->mpdf->OTLscript; 1953 $t = $this->mpdf->OTLlang; 1954 $langsys = $gsub[$st][$t]; 1955 1956 $lul = []; // array of LookupListIndexes 1957 $tags = []; // corresponding array of feature tags e.g. 'ccmp' 1958 foreach ($langsys as $tag => $ft) { 1959 foreach ($ft as $ll) { 1960 $lul[$ll] = $tag; 1961 } 1962 } 1963 ksort($lul); // Order the Lookups in the order they are in the GUSB table, regardless of Feature order 1964 $this->_getGSUBarray($Lookup, $lul, $st); 1965//print_r($lul); exit; 1966 } 1967 1968//print_r($Lookup); exit; 1969 1970 return [$GSUBScriptLang, $gsub, $GSLookup, $rtlPUAstr, $rtlPUAarr]; 1971 } 1972 1973///////////////////////////////////////////////////////////////////////////////////////// 1974 // GSUB functions 1975 function _getGSUBarray(&$Lookup, &$lul, $scripttag, $level = 1, $coverage = '', $exB = '', $exL = '') 1976 { 1977 // Process (3) LookupList for specific Script-LangSys 1978 // Generate preg_replace 1979 $html = ''; 1980 if ($level == 1) { 1981 $html .= '<bookmark level="0" content="GSUB features">'; 1982 } 1983 foreach ($lul as $i => $tag) { 1984 $html .= '<div class="level' . $level . '">'; 1985 $html .= '<h5 class="level' . $level . '">'; 1986 if ($level == 1) { 1987 $html .= '<bookmark level="1" content="' . $tag . ' [#' . $i . ']">'; 1988 } 1989 $html .= 'Lookup #' . $i . ' [tag: <span style="color:#000066;">' . $tag . '</span>]</h5>'; 1990 $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']); 1991 if ($ignore) { 1992 $html .= '<div class="ignore">Ignoring: ' . $ignore . '</div> '; 1993 } 1994 1995 $Type = $Lookup[$i]['Type']; 1996 $Flag = $Lookup[$i]['Flag']; 1997 if (($Flag & 0x0001) == 1) { 1998 $dir = 'RTL'; 1999 } else { 2000 $dir = 'LTR'; 2001 } 2002 2003 for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { 2004 $html .= '<div class="subtable">Subtable #' . $c; 2005 if ($level == 1) { 2006 $html .= '<bookmark level="2" content="Subtable #' . $c . '">'; 2007 } 2008 $html .= '</div>'; 2009 2010 $SubstFormat = $Lookup[$i]['Subtable'][$c]['Format']; 2011 2012 // LookupType 1: Single Substitution Subtable 2013 if ($Lookup[$i]['Type'] == 1) { 2014 $html .= '<div class="lookuptype">LookupType 1: Single Substitution Subtable</div>'; 2015 for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) { 2016 $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace']; 2017 $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0]; 2018 if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) { 2019 continue; 2020 } 2021 $html .= '<div class="substitution">'; 2022 $html .= '<span class="unicode">' . $this->formatUni($inputGlyphs[0]) . ' </span> '; 2023 if ($level == 2 && $exB) { 2024 $html .= $exB; 2025 } 2026 $html .= '<span class="unchanged"> ' . $this->formatEntity($inputGlyphs[0]) . '</span>'; 2027 if ($level == 2 && $exL) { 2028 $html .= $exL; 2029 } 2030 $html .= ' » » '; 2031 if ($level == 2 && $exB) { 2032 $html .= $exB; 2033 } 2034 $html .= '<span class="changed"> ' . $this->formatEntity($substitute) . '</span>'; 2035 if ($level == 2 && $exL) { 2036 $html .= $exL; 2037 } 2038 $html .= ' <span class="unicode">' . $this->formatUni($substitute) . '</span> '; 2039 $html .= '</div>'; 2040 } 2041 } // LookupType 2: Multiple Substitution Subtable 2042 else { 2043 if ($Lookup[$i]['Type'] == 2) { 2044 $html .= '<div class="lookuptype">LookupType 2: Multiple Substitution Subtable</div>'; 2045 for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) { 2046 $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace']; 2047 $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']; 2048 if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) { 2049 continue; 2050 } 2051 $html .= '<div class="substitution">'; 2052 $html .= '<span class="unicode">' . $this->formatUni($inputGlyphs[0]) . ' </span> '; 2053 if ($level == 2 && $exB) { 2054 $html .= $exB; 2055 } 2056 $html .= '<span class="unchanged"> ' . $this->formatEntity($inputGlyphs[0]) . '</span>'; 2057 if ($level == 2 && $exL) { 2058 $html .= $exL; 2059 } 2060 $html .= ' » » '; 2061 if ($level == 2 && $exB) { 2062 $html .= $exB; 2063 } 2064 $html .= '<span class="changed"> ' . $this->formatEntityArr($substitute) . '</span>'; 2065 if ($level == 2 && $exL) { 2066 $html .= $exL; 2067 } 2068 $html .= ' <span class="unicode">' . $this->formatUniArr($substitute) . '</span> '; 2069 $html .= '</div>'; 2070 } 2071 } // LookupType 3: Alternate Forms 2072 else { 2073 if ($Lookup[$i]['Type'] == 3) { 2074 $html .= '<div class="lookuptype">LookupType 3: Alternate Forms</div>'; 2075 for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) { 2076 $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace']; 2077 $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0]; 2078 if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) { 2079 continue; 2080 } 2081 $html .= '<div class="substitution">'; 2082 $html .= '<span class="unicode">' . $this->formatUni($inputGlyphs[0]) . ' </span> '; 2083 if ($level == 2 && $exB) { 2084 $html .= $exB; 2085 } 2086 $html .= '<span class="unchanged"> ' . $this->formatEntity($inputGlyphs[0]) . '</span>'; 2087 if ($level == 2 && $exL) { 2088 $html .= $exL; 2089 } 2090 $html .= ' » » '; 2091 if ($level == 2 && $exB) { 2092 $html .= $exB; 2093 } 2094 $html .= '<span class="changed"> ' . $this->formatEntity($substitute) . '</span>'; 2095 if ($level == 2 && $exL) { 2096 $html .= $exL; 2097 } 2098 $html .= ' <span class="unicode">' . $this->formatUni($substitute) . '</span> '; 2099 if (count($Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']) > 1) { 2100 for ($alt = 1; $alt < count($Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']); $alt++) { 2101 $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][$alt]; 2102 $html .= ' | ALT #' . $alt . ' '; 2103 $html .= '<span class="changed"> ' . $this->formatEntity($substitute) . '</span>'; 2104 $html .= ' <span class="unicode">' . $this->formatUni($substitute) . '</span> '; 2105 } 2106 } 2107 $html .= '</div>'; 2108 } 2109 } // LookupType 4: Ligature Substitution Subtable 2110 else { 2111 if ($Lookup[$i]['Type'] == 4) { 2112 $html .= '<div class="lookuptype">LookupType 4: Ligature Substitution Subtable</div>'; 2113 for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) { 2114 $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace']; 2115 $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0]; 2116 if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) { 2117 continue; 2118 } 2119 $html .= '<div class="substitution">'; 2120 $html .= '<span class="unicode">' . $this->formatUniArr($inputGlyphs) . ' </span> '; 2121 if ($level == 2 && $exB) { 2122 $html .= $exB; 2123 } 2124 $html .= '<span class="unchanged"> ' . $this->formatEntityArr($inputGlyphs) . '</span>'; 2125 if ($level == 2 && $exL) { 2126 $html .= $exL; 2127 } 2128 $html .= ' » » '; 2129 if ($level == 2 && $exB) { 2130 $html .= $exB; 2131 } 2132 $html .= '<span class="changed"> ' . $this->formatEntity($substitute) . '</span>'; 2133 if ($level == 2 && $exL) { 2134 $html .= $exL; 2135 } 2136 $html .= ' <span class="unicode">' . $this->formatUni($substitute) . '</span> '; 2137 $html .= '</div>'; 2138 } 2139 } // LookupType 5: Contextual Substitution Subtable 2140 else { 2141 if ($Lookup[$i]['Type'] == 5) { 2142 $html .= '<div class="lookuptype">LookupType 5: Contextual Substitution Subtable</div>'; 2143 // Format 1: Context Substitution 2144 if ($SubstFormat == 1) { 2145 $html .= '<div class="lookuptypesub">Format 1: Context Substitution</div>'; 2146 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) { 2147 // SubRuleSet 2148 $subRule = []; 2149 $html .= '<div class="rule">Subrule Set: ' . $s . '</div>'; 2150 foreach ($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'] as $rctr => $rule) { 2151 // SubRule 2152 $html .= '<div class="rule">SubRule: ' . $rctr . '</div>'; 2153 $inputGlyphs = []; 2154 if ($rule['GlyphCount'] > 1) { 2155 $inputGlyphs = $rule['InputGlyphs']; 2156 } 2157 $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph']; 2158 ksort($inputGlyphs); 2159 $nInput = count($inputGlyphs); 2160 2161 $exampleI = []; 2162 $html .= '<div class="context">CONTEXT: '; 2163 for ($ff = 0; $ff < count($inputGlyphs); $ff++) { 2164 $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; 2165 $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); 2166 } 2167 $html .= '</div>'; 2168 2169 for ($b = 0; $b < $rule['SubstCount']; $b++) { 2170 $lup = $rule['SubstLookupRecord'][$b]['LookupListIndex']; 2171 $seqIndex = $rule['SubstLookupRecord'][$b]['SequenceIndex']; 2172 2173 // GENERATE exampleI[<seqIndex] .... exampleI[>seqIndex] 2174 $exB = ''; 2175 $exL = ''; 2176 if ($seqIndex > 0) { 2177 $exB .= '<span class="inputother">'; 2178 for ($ip = 0; $ip < $seqIndex; $ip++) { 2179 $exB .= $this->formatEntity($inputGlyphs[$ip]) . '‍'; 2180 } 2181 $exB .= '</span>'; 2182 } 2183 if (count($inputGlyphs) > ($seqIndex + 1)) { 2184 $exL .= '<span class="inputother">'; 2185 for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { 2186 $exL .= $this->formatEntity($inputGlyphs[$ip]) . '‍'; 2187 } 2188 $exL .= '</span>'; 2189 } 2190 $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; 2191 2192 $lul2 = [$lup => $tag]; 2193 2194 // Only apply if the (first) 'Replace' glyph from the 2195 // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] 2196 // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 2197 // to level 2 and only apply if first Replace glyph is in this list 2198 $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); 2199 } 2200 2201 if (count($subRule['rules'])) { 2202 $volt[] = $subRule; 2203 } 2204 } 2205 } 2206 } // Format 2: Class-based Context Glyph Substitution 2207 else { 2208 if ($SubstFormat == 2) { 2209 $html .= '<div class="lookuptypesub">Format 2: Class-based Context Glyph Substitution</div>'; 2210 foreach ($Lookup[$i]['Subtable'][$c]['SubClassSet'] as $inputClass => $cscs) { 2211 $html .= '<div class="rule">Input Class: ' . $inputClass . '</div>'; 2212 for ($cscrule = 0; $cscrule < $cscs['SubClassRuleCnt']; $cscrule++) { 2213 $html .= '<div class="rule">Rule: ' . $cscrule . '</div>'; 2214 $rule = $cscs['SubClassRule'][$cscrule]; 2215 2216 $inputGlyphs = []; 2217 2218 $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass]; 2219 2220 if ($rule['InputGlyphCount'] > 1) { 2221 // NB starts at 1 2222 for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) { 2223 $classindex = $rule['Input'][$gcl]; 2224 $inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex]; 2225 } 2226 } 2227 2228 // Class 0 contains all the glyphs NOT in the other classes 2229 $class0excl = implode('|', $Lookup[$i]['Subtable'][$c]['InputClasses']); 2230 2231 $exampleI = []; 2232 $html .= '<div class="context">CONTEXT: '; 2233 for ($ff = 0; $ff < count($inputGlyphs); $ff++) { 2234 if (!$inputGlyphs[$ff]) { 2235 $html .= '<div>Input #' . $ff . ': <span class="unchanged"> [NOT ' . $this->formatEntityStr($class0excl) . '] </span></div>'; 2236 $exampleI[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']'; 2237 } else { 2238 $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; 2239 $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); 2240 } 2241 } 2242 $html .= '</div>'; 2243 2244 for ($b = 0; $b < $rule['SubstCount']; $b++) { 2245 $lup = $rule['LookupListIndex'][$b]; 2246 $seqIndex = $rule['SequenceIndex'][$b]; 2247 2248 // GENERATE exampleI[<seqIndex] .... exampleI[>seqIndex] 2249 $exB = ''; 2250 $exL = ''; 2251 2252 if ($seqIndex > 0) { 2253 $exB .= '<span class="inputother">'; 2254 for ($ip = 0; $ip < $seqIndex; $ip++) { 2255 if (!$inputGlyphs[$ip]) { 2256 $exB .= '[*]'; 2257 } else { 2258 $exB .= $this->formatEntityFirst($inputGlyphs[$ip]) . '‍'; 2259 } 2260 } 2261 $exB .= '</span>'; 2262 } 2263 2264 if (count($inputGlyphs) > ($seqIndex + 1)) { 2265 $exL .= '<span class="inputother">'; 2266 for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { 2267 if (!$inputGlyphs[$ip]) { 2268 $exL .= '[*]'; 2269 } else { 2270 $exL .= $this->formatEntityFirst($inputGlyphs[$ip]) . '‍'; 2271 } 2272 } 2273 $exL .= '</span>'; 2274 } 2275 2276 $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; 2277 2278 $lul2 = [$lup => $tag]; 2279 2280 // Only apply if the (first) 'Replace' glyph from the 2281 // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] 2282 // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 2283 // to level 2 and only apply if first Replace glyph is in this list 2284 $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); 2285 } 2286 if (count($subRule['rules'])) { 2287 $volt[] = $subRule; 2288 } 2289 } 2290 } 2291 } // Format 3: Coverage-based Context Glyph Substitution p259 2292 else { 2293 if ($SubstFormat == 3) { 2294 $html .= '<div class="lookuptypesub">Format 3: Coverage-based Context Glyph Substitution </div>'; 2295 // IgnoreMarks flag set on main Lookup table 2296 $inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs']; 2297 $CoverageInputGlyphs = implode('|', $inputGlyphs); 2298 $nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; 2299 2300 $exampleI = []; 2301 $html .= '<div class="context">CONTEXT: '; 2302 for ($ff = 0; $ff < count($inputGlyphs); $ff++) { 2303 $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; 2304 $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); 2305 } 2306 $html .= '</div>'; 2307 2308 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) { 2309 $lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex']; 2310 $seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex']; 2311 // GENERATE exampleI[<seqIndex] .... exampleI[>seqIndex] 2312 $exB = ''; 2313 $exL = ''; 2314 if ($seqIndex > 0) { 2315 $exB .= '<span class="inputother">'; 2316 for ($ip = 0; $ip < $seqIndex; $ip++) { 2317 $exB .= $exampleI[$ip] . '‍'; 2318 } 2319 $exB .= '</span>'; 2320 } 2321 2322 if (count($inputGlyphs) > ($seqIndex + 1)) { 2323 $exL .= '<span class="inputother">'; 2324 for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { 2325 $exL .= $exampleI[$ip] . '‍'; 2326 } 2327 $exL .= '</span>'; 2328 } 2329 2330 $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; 2331 2332 $lul2 = [$lup => $tag]; 2333 2334 // Only apply if the (first) 'Replace' glyph from the 2335 // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] 2336 // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 2337 // to level 2 and only apply if first Replace glyph is in this list 2338 $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); 2339 } 2340 if (count($subRule['rules'])) { 2341 $volt[] = $subRule; 2342 } 2343 } 2344 } 2345 } 2346 2347//print_r($Lookup[$i]); 2348//print_r($volt[(count($volt)-1)]); exit; 2349 } // LookupType 6: Chaining Contextual Substitution Subtable 2350 else { 2351 if ($Lookup[$i]['Type'] == 6) { 2352 $html .= '<div class="lookuptype">LookupType 6: Chaining Contextual Substitution Subtable</div>'; 2353 // Format 1: Simple Chaining Context Glyph Substitution p255 2354 if ($SubstFormat == 1) { 2355 $html .= '<div class="lookuptypesub">Format 1: Simple Chaining Context Glyph Substitution </div>'; 2356 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $s++) { 2357 // ChainSubRuleSet 2358 $subRule = []; 2359 $html .= '<div class="rule">Subrule Set: ' . $s . '</div>'; 2360 $firstInputGlyph = $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'][$s]; // First input gyyph 2361 foreach ($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'] as $rctr => $rule) { 2362 $html .= '<div class="rule">SubRule: ' . $rctr . '</div>'; 2363 // ChainSubRule 2364 $inputGlyphs = []; 2365 if ($rule['InputGlyphCount'] > 1) { 2366 $inputGlyphs = $rule['InputGlyphs']; 2367 } 2368 $inputGlyphs[0] = $firstInputGlyph; 2369 ksort($inputGlyphs); 2370 $nInput = count($inputGlyphs); 2371 2372 if ($rule['BacktrackGlyphCount']) { 2373 $backtrackGlyphs = $rule['BacktrackGlyphs']; 2374 } else { 2375 $backtrackGlyphs = []; 2376 } 2377 2378 if ($rule['LookaheadGlyphCount']) { 2379 $lookaheadGlyphs = $rule['LookaheadGlyphs']; 2380 } else { 2381 $lookaheadGlyphs = []; 2382 } 2383 2384 $exampleB = []; 2385 $exampleI = []; 2386 $exampleL = []; 2387 $html .= '<div class="context">CONTEXT: '; 2388 for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) { 2389 $html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>'; 2390 $exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]); 2391 } 2392 for ($ff = 0; $ff < count($inputGlyphs); $ff++) { 2393 $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; 2394 $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); 2395 } 2396 for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) { 2397 $html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>'; 2398 $exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]); 2399 } 2400 $html .= '</div>'; 2401 2402 for ($b = 0; $b < $rule['SubstCount']; $b++) { 2403 $lup = $rule['LookupListIndex'][$b]; 2404 $seqIndex = $rule['SequenceIndex'][$b]; 2405 2406 // GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n] 2407 $exB = ''; 2408 $exL = ''; 2409 if (count($exampleB)) { 2410 $exB .= '<span class="backtrack">' . implode('‍', $exampleB) . '</span>'; 2411 } 2412 2413 if ($seqIndex > 0) { 2414 $exB .= '<span class="inputother">'; 2415 for ($ip = 0; $ip < $seqIndex; $ip++) { 2416 $exB .= $this->formatEntity($inputGlyphs[$ip]) . '‍'; 2417 } 2418 $exB .= '</span>'; 2419 } 2420 2421 if (count($inputGlyphs) > ($seqIndex + 1)) { 2422 $exL .= '<span class="inputother">'; 2423 for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { 2424 $exL .= $this->formatEntity($inputGlyphs[$ip]) . '‍'; 2425 } 2426 $exL .= '</span>'; 2427 } 2428 2429 if (count($exampleL)) { 2430 $exL .= '<span class="lookahead">' . implode('‍', $exampleL) . '</span>'; 2431 } 2432 2433 $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; 2434 2435 $lul2 = [$lup => $tag]; 2436 2437 // Only apply if the (first) 'Replace' glyph from the 2438 // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] 2439 // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 2440 // to level 2 and only apply if first Replace glyph is in this list 2441 $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); 2442 } 2443 2444 if (count($subRule['rules'])) { 2445 $volt[] = $subRule; 2446 } 2447 } 2448 } 2449 } // Format 2: Class-based Chaining Context Glyph Substitution p257 2450 else { 2451 if ($SubstFormat == 2) { 2452 $html .= '<div class="lookuptypesub">Format 2: Class-based Chaining Context Glyph Substitution </div>'; 2453 foreach ($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'] as $inputClass => $cscs) { 2454 $html .= '<div class="rule">Input Class: ' . $inputClass . '</div>'; 2455 for ($cscrule = 0; $cscrule < $cscs['ChainSubClassRuleCnt']; $cscrule++) { 2456 $html .= '<div class="rule">Rule: ' . $cscrule . '</div>'; 2457 $rule = $cscs['ChainSubClassRule'][$cscrule]; 2458 2459 // These contain classes of glyphs as strings 2460 // $Lookup[$i]['Subtable'][$c]['InputClasses'][(class)] e.g. 02E6|02E7|02E8 2461 // $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][(class)] 2462 // $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][(class)] 2463 // These contain arrays of classIndexes 2464 // [Backtrack] [Lookahead] and [Input] (Input is from the second position only) 2465 2466 $inputGlyphs = []; 2467 2468 $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass]; 2469 if ($rule['InputGlyphCount'] > 1) { 2470 // NB starts at 1 2471 for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) { 2472 $classindex = $rule['Input'][$gcl]; 2473 $inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex]; 2474 } 2475 } 2476 // Class 0 contains all the glyphs NOT in the other classes 2477 $class0excl = implode('|', $Lookup[$i]['Subtable'][$c]['InputClasses']); 2478 2479 $nInput = $rule['InputGlyphCount']; 2480 2481 if ($rule['BacktrackGlyphCount']) { 2482 for ($gcl = 0; $gcl < $rule['BacktrackGlyphCount']; $gcl++) { 2483 $classindex = $rule['Backtrack'][$gcl]; 2484 $backtrackGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][$classindex]; 2485 } 2486 } else { 2487 $backtrackGlyphs = []; 2488 } 2489 2490 if ($rule['LookaheadGlyphCount']) { 2491 for ($gcl = 0; $gcl < $rule['LookaheadGlyphCount']; $gcl++) { 2492 $classindex = $rule['Lookahead'][$gcl]; 2493 $lookaheadGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][$classindex]; 2494 } 2495 } else { 2496 $lookaheadGlyphs = []; 2497 } 2498 2499 $exampleB = []; 2500 $exampleI = []; 2501 $exampleL = []; 2502 $html .= '<div class="context">CONTEXT: '; 2503 for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) { 2504 if (!$backtrackGlyphs[$ff]) { 2505 $html .= '<div>Backtrack #' . $ff . ': <span class="unchanged"> [NOT ' . $this->formatEntityStr($class0excl) . '] </span></div>'; 2506 $exampleB[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']'; 2507 } else { 2508 $html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>'; 2509 $exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]); 2510 } 2511 } 2512 for ($ff = 0; $ff < count($inputGlyphs); $ff++) { 2513 if (!$inputGlyphs[$ff]) { 2514 $html .= '<div>Input #' . $ff . ': <span class="unchanged"> [NOT ' . $this->formatEntityStr($class0excl) . '] </span></div>'; 2515 $exampleI[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']'; 2516 } else { 2517 $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; 2518 $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); 2519 } 2520 } 2521 for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) { 2522 if (!$lookaheadGlyphs[$ff]) { 2523 $html .= '<div>Lookahead #' . $ff . ': <span class="unchanged"> [NOT ' . $this->formatEntityStr($class0excl) . '] </span></div>'; 2524 $exampleL[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']'; 2525 } else { 2526 $html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>'; 2527 $exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]); 2528 } 2529 } 2530 $html .= '</div>'; 2531 2532 for ($b = 0; $b < $rule['SubstCount']; $b++) { 2533 $lup = $rule['LookupListIndex'][$b]; 2534 $seqIndex = $rule['SequenceIndex'][$b]; 2535 2536 // GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n] 2537 $exB = ''; 2538 $exL = ''; 2539 if (count($exampleB)) { 2540 $exB .= '<span class="backtrack">' . implode('‍', $exampleB) . '</span>'; 2541 } 2542 2543 if ($seqIndex > 0) { 2544 $exB .= '<span class="inputother">'; 2545 for ($ip = 0; $ip < $seqIndex; $ip++) { 2546 if (!$inputGlyphs[$ip]) { 2547 $exB .= '[*]'; 2548 } else { 2549 $exB .= $this->formatEntityFirst($inputGlyphs[$ip]) . '‍'; 2550 } 2551 } 2552 $exB .= '</span>'; 2553 } 2554 2555 if (count($inputGlyphs) > ($seqIndex + 1)) { 2556 $exL .= '<span class="inputother">'; 2557 for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { 2558 if (!$inputGlyphs[$ip]) { 2559 $exL .= '[*]'; 2560 } else { 2561 $exL .= $this->formatEntityFirst($inputGlyphs[$ip]) . '‍'; 2562 } 2563 } 2564 $exL .= '</span>'; 2565 } 2566 2567 if (count($exampleL)) { 2568 $exL .= '<span class="lookahead">' . implode('‍', $exampleL) . '</span>'; 2569 } 2570 2571 $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; 2572 2573 $lul2 = [$lup => $tag]; 2574 2575 // Only apply if the (first) 'Replace' glyph from the 2576 // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] 2577 // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 2578 // to level 2 and only apply if first Replace glyph is in this list 2579 $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); 2580 } 2581 } 2582 } 2583 2584//print_r($Lookup[$i]['Subtable'][$c]); exit; 2585 } // Format 3: Coverage-based Chaining Context Glyph Substitution p259 2586 else { 2587 if ($SubstFormat == 3) { 2588 $html .= '<div class="lookuptypesub">Format 3: Coverage-based Chaining Context Glyph Substitution </div>'; 2589 // IgnoreMarks flag set on main Lookup table 2590 $inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs']; 2591 $CoverageInputGlyphs = implode('|', $inputGlyphs); 2592 $nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; 2593 2594 if ($Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']) { 2595 $backtrackGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs']; 2596 } else { 2597 $backtrackGlyphs = []; 2598 } 2599 2600 if ($Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']) { 2601 $lookaheadGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs']; 2602 } else { 2603 $lookaheadGlyphs = []; 2604 } 2605 2606 $exampleB = []; 2607 $exampleI = []; 2608 $exampleL = []; 2609 $html .= '<div class="context">CONTEXT: '; 2610 for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) { 2611 $html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>'; 2612 $exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]); 2613 } 2614 for ($ff = 0; $ff < count($inputGlyphs); $ff++) { 2615 $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; 2616 $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); 2617 } 2618 for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) { 2619 $html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>'; 2620 $exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]); 2621 } 2622 $html .= '</div>'; 2623 2624 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) { 2625 $lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex']; 2626 $seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex']; 2627 2628 // GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n] 2629 $exB = ''; 2630 $exL = ''; 2631 if (count($exampleB)) { 2632 $exB .= '<span class="backtrack">' . implode('‍', $exampleB) . '</span>'; 2633 } 2634 2635 if ($seqIndex > 0) { 2636 $exB .= '<span class="inputother">'; 2637 for ($ip = 0; $ip < $seqIndex; $ip++) { 2638 $exB .= $exampleI[$ip] . '‍'; 2639 } 2640 $exB .= '</span>'; 2641 } 2642 2643 if (count($inputGlyphs) > ($seqIndex + 1)) { 2644 $exL .= '<span class="inputother">'; 2645 for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { 2646 $exL .= $exampleI[$ip] . '‍'; 2647 } 2648 $exL .= '</span>'; 2649 } 2650 2651 if (count($exampleL)) { 2652 $exL .= '<span class="lookahead">' . implode('‍', $exampleL) . '</span>'; 2653 } 2654 2655 $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; 2656 2657 $lul2 = [$lup => $tag]; 2658 2659 // Only apply if the (first) 'Replace' glyph from the 2660 // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] 2661 // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 2662 // to level 2 and only apply if first Replace glyph is in this list 2663 $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); 2664 } 2665 } 2666 } 2667 } 2668 } 2669 } 2670 } 2671 } 2672 } 2673 } 2674 } 2675 $html .= '</div>'; 2676 } 2677 if ($level == 1) { 2678 $this->mpdf->WriteHTML($html); 2679 } else { 2680 return $html; 2681 } 2682//print_r($Lookup); exit; 2683 } 2684 2685 //===================================================================================== 2686 //===================================================================================== 2687 // mPDF 5.7.1 2688 function _checkGSUBignore($flag, $glyph, $MarkFilteringSet) 2689 { 2690 $ignore = false; 2691 // Flag & 0x0008 = Ignore Marks 2692 if ((($flag & 0x0008) == 0x0008) && strpos($this->GlyphClassMarks, $glyph)) { 2693 $ignore = true; 2694 } 2695 if ((($flag & 0x0004) == 0x0004) && strpos($this->GlyphClassLigatures, $glyph)) { 2696 $ignore = true; 2697 } 2698 if ((($flag & 0x0002) == 0x0002) && strpos($this->GlyphClassBases, $glyph)) { 2699 $ignore = true; 2700 } 2701 // Flag & 0xFF?? = MarkAttachmentType 2702 if (($flag & 0xFF00) && strpos($this->MarkAttachmentType[($flag >> 8)], $glyph)) { 2703 $ignore = true; 2704 } 2705 // Flag & 0x0010 = UseMarkFilteringSet 2706 if (($flag & 0x0010) && strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) { 2707 $ignore = true; 2708 } 2709 2710 return $ignore; 2711 } 2712 2713 function _getGSUBignoreString($flag, $MarkFilteringSet) 2714 { 2715 // If ignoreFlag set, combine all ignore glyphs into -> "((?:(?: FBA1| FBA2| FBA3))*)" 2716 // else "()" 2717 // for Input - set on secondary Lookup table if in Context, and set Backtrack and Lookahead on Context Lookup 2718 $str = ""; 2719 $ignoreflag = 0; 2720 2721 // Flag & 0xFF?? = MarkAttachmentType 2722 if ($flag & 0xFF00) { 2723 $MarkAttachmentType = $flag >> 8; 2724 $ignoreflag = $flag; 2725 //$str = $this->MarkAttachmentType[$MarkAttachmentType]; 2726 $str = "MarkAttachmentType[" . $MarkAttachmentType . "] "; 2727 } 2728 2729 // Flag & 0x0010 = UseMarkFilteringSet 2730 if ($flag & 0x0010) { 2731 throw new \Mpdf\Exception\FontException("This font " . $this->fontkey . " contains MarkGlyphSets"); 2732 $str = "Mark Glyph Set: "; 2733 $str .= $this->MarkGlyphSets[$MarkFilteringSet]; 2734 } 2735 2736 // If Ignore Marks set, supercedes any above 2737 // Flag & 0x0008 = Ignore Marks 2738 if (($flag & 0x0008) == 0x0008) { 2739 $ignoreflag = 8; 2740 //$str = $this->GlyphClassMarks; 2741 $str = "Mark Glyphs "; 2742 } 2743 2744 // Flag & 0x0004 = Ignore Ligatures 2745 if (($flag & 0x0004) == 0x0004) { 2746 $ignoreflag += 4; 2747 if ($str) { 2748 $str .= "|"; 2749 } 2750 //$str .= $this->GlyphClassLigatures; 2751 $str .= "Ligature Glyphs "; 2752 } 2753 // Flag & 0x0002 = Ignore BaseGlyphs 2754 if (($flag & 0x0002) == 0x0002) { 2755 $ignoreflag += 2; 2756 if ($str) { 2757 $str .= "|"; 2758 } 2759 //$str .= $this->GlyphClassBases; 2760 $str .= "Base Glyphs "; 2761 } 2762 if ($str) { 2763 return $str; 2764 } else { 2765 return ""; 2766 } 2767 } 2768 2769 // GSUB Patterns 2770 2771 /* 2772 BACKTRACK INPUT LOOKAHEAD 2773 ================================== ================== ================================== 2774 (FEEB|FEEC)(ign) ¦(FD12|FD13)(ign) ¦(0612)¦(ign) (0613)¦(ign) (FD12|FD13)¦(ign) (FEEB|FEEC) 2775 ---------------- ---------------- ----- ------------ --------------- --------------- 2776 Backtrack 1 Backtrack 2 Input 1 Input 2 Lookahead 1 Lookahead 2 2777 -------- --- --------- --- ---- --- ---- --- --------- --- ------- 2778 \${1} \${2} \${3} \${4} \${5+} \${6+} \${7+} \${8+} 2779 2780 nBacktrack = 2 nInput = 2 nLookahead = 2 2781 2782 nBsubs = 2xnBack nIsubs = (nBsubs+) nLsubs = (nBsubs+nIsubs+) 2xnLookahead 2783 "\${1}\${2} " (nInput*2)-1 "\${5+} \${6+}" 2784 "REPL" 2785 2786 ¦\${1}\${2} ¦\${3}\${4} ¦REPL¦\${5+} \${6+}¦\${7+} \${8+}¦ 2787 2788 2789 INPUT nInput = 5 2790 ============================================================ 2791 ¦(0612)¦(ign) (0613)¦(ign) (0614)¦(ign) (0615)¦(ign) (0615)¦ 2792 \${1} \${2} \${3} \${4} \${5} \${6} \${7} \${8} \${9} (All backreference numbers are + nBsubs) 2793 ----- ------------ ------------ ------------ ------------ 2794 Input 1 Input 2 Input 3 Input 4 Input 5 2795 2796 A====== SequenceIndex=1 ; Lookup match nGlyphs=1 2797 B=================== SequenceIndex=1 ; Lookup match nGlyphs=2 2798 C=============================== SequenceIndex=1 ; Lookup match nGlyphs=3 2799 D======================= SequenceIndex=2 ; Lookup match nGlyphs=2 2800 E===================================== SequenceIndex=2 ; Lookup match nGlyphs=3 2801 F====================== SequenceIndex=4 ; Lookup match nGlyphs=2 2802 2803 All backreference numbers are + nBsubs 2804 A - "REPL\${2} \${3}\${4} \${5}\${6} \${7}\${8} \${9}" 2805 B - "REPL\${2}\${4} \${5}\${6} \${7}\${8} \${9}" 2806 C - "REPL\${2}\${4}\${6} \${7}\${8} \${9}" 2807 D - "\${1} REPL\${2}\${4}\${6} \${7}\${8} \${9}" 2808 E - "\${1} REPL\${2}\${4}\${6}\${8} \${9}" 2809 F - "\${1}\${2} \${3}\${4} \${5} REPL\${6}\${8}" 2810 */ 2811 2812 function _makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex) 2813 { 2814 // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" 2815 // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦ 2816 // $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context 2817 // $lookupGlyphs = array of glyphs (single Glyphs) making up Lookup Input sequence 2818 $mLen = count($lookupGlyphs); // nGlyphs in the secondary Lookup match 2819 $nInput = count($inputGlyphs); // nGlyphs in the Primary Input sequence 2820 $str = ""; 2821 for ($i = 0; $i < $nInput; $i++) { 2822 if ($i > 0) { 2823 $str .= $ignore . " "; 2824 } 2825 if ($i >= $seqIndex && $i < ($seqIndex + $mLen)) { 2826 $str .= "" . $lookupGlyphs[($i - $seqIndex)] . ""; 2827 } else { 2828 $str .= "" . $inputGlyphs[($i)] . ""; 2829 } 2830 } 2831 2832 return $str; 2833 } 2834 2835 function _makeGSUBinputMatch($inputGlyphs, $ignore) 2836 { 2837 // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" 2838 // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦ 2839 // $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context 2840 // $lookupGlyphs = array of glyphs making up Lookup Input sequence - if applicable 2841 $str = ""; 2842 for ($i = 1; $i <= count($inputGlyphs); $i++) { 2843 if ($i > 1) { 2844 $str .= $ignore . " "; 2845 } 2846 $str .= "" . $inputGlyphs[($i - 1)] . ""; 2847 } 2848 2849 return $str; 2850 } 2851 2852 function _makeGSUBbacktrackMatch($backtrackGlyphs, $ignore) 2853 { 2854 // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" 2855 // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦ 2856 // $backtrackGlyphs = array of glyphstrings making up Backtrack sequence 2857 // 3 2 1 0 2858 // each item being e.g. E0AD|E0AF|F1FD 2859 $str = ""; 2860 for ($i = (count($backtrackGlyphs) - 1); $i >= 0; $i--) { 2861 $str .= "" . $backtrackGlyphs[$i] . " " . $ignore . " "; 2862 } 2863 2864 return $str; 2865 } 2866 2867 function _makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore) 2868 { 2869 // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" 2870 // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦ 2871 // $lookaheadGlyphs = array of glyphstrings making up Lookahead sequence 2872 // 0 1 2 3 2873 // each item being e.g. E0AD|E0AF|F1FD 2874 $str = ""; 2875 for ($i = 0; $i < count($lookaheadGlyphs); $i++) { 2876 $str .= $ignore . " " . $lookaheadGlyphs[$i] . ""; 2877 } 2878 2879 return $str; 2880 } 2881 2882 function _makeGSUBinputReplacement($nInput, $REPL, $ignore, $nBsubs, $mLen, $seqIndex) 2883 { 2884 // Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}" 2885 // $nInput nGlyphs in the Primary Input sequence 2886 // $REPL replacement glyphs from secondary lookup 2887 // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" 2888 // $nBsubs Number of Backtrack substitutions (= 2x Number of Backtrack glyphs) 2889 // $mLen nGlyphs in the secondary Lookup match - if no secondary lookup, should=$nInput 2890 // $seqIndex Sequence Index to apply the secondary match 2891 if ($ignore == "()") { 2892 $ign = false; 2893 } else { 2894 $ign = true; 2895 } 2896 $str = ""; 2897 if ($nInput == 1) { 2898 $str = $REPL; 2899 } else { 2900 if ($nInput > 1) { 2901 if ($mLen == $nInput) { // whole string replaced 2902 $str = $REPL; 2903 if ($ign) { 2904 // for every nInput over 1, add another replacement backreference, to move IGNORES after replacement 2905 for ($x = 2; $x <= $nInput; $x++) { 2906 $str .= '\\' . ($nBsubs + (2 * ($x - 1))); 2907 } 2908 } 2909 } else { // if only part of string replaced: 2910 for ($x = 1; $x < ($seqIndex + 1); $x++) { 2911 if ($x == 1) { 2912 $str .= '\\' . ($nBsubs + 1); 2913 } else { 2914 if ($ign) { 2915 $str .= '\\' . ($nBsubs + (2 * ($x - 1))); 2916 } 2917 $str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1))); 2918 } 2919 } 2920 if ($seqIndex > 0) { 2921 $str .= " "; 2922 } 2923 $str .= $REPL; 2924 if ($ign) { 2925 for ($x = (max(($seqIndex + 1), 2)); $x < ($seqIndex + 1 + $mLen); $x++) { // move IGNORES after replacement 2926 $str .= '\\' . ($nBsubs + (2 * ($x - 1))); 2927 } 2928 } 2929 for ($x = ($seqIndex + 1 + $mLen); $x <= $nInput; $x++) { 2930 if ($ign) { 2931 $str .= '\\' . ($nBsubs + (2 * ($x - 1))); 2932 } 2933 $str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1))); 2934 } 2935 } 2936 } 2937 } 2938 2939 return $str; 2940 } 2941 2942 ////////////////////////////////////////////////////////////////////////////////// 2943 function _getCoverage($convert2hex = true) 2944 { 2945 $g = []; 2946 $CoverageFormat = $this->read_ushort(); 2947 if ($CoverageFormat == 1) { 2948 $CoverageGlyphCount = $this->read_ushort(); 2949 for ($gid = 0; $gid < $CoverageGlyphCount; $gid++) { 2950 $glyphID = $this->read_ushort(); 2951 if ($convert2hex) { 2952 $g[] = unicode_hex($this->glyphToChar[$glyphID][0]); 2953 } else { 2954 $g[] = $glyphID; 2955 } 2956 } 2957 } 2958 if ($CoverageFormat == 2) { 2959 $RangeCount = $this->read_ushort(); 2960 for ($r = 0; $r < $RangeCount; $r++) { 2961 $start = $this->read_ushort(); 2962 $end = $this->read_ushort(); 2963 $StartCoverageIndex = $this->read_ushort(); // n/a 2964 for ($gid = $start; $gid <= $end; $gid++) { 2965 $glyphID = $gid; 2966 if ($convert2hex) { 2967 $g[] = unicode_hex($this->glyphToChar[$glyphID][0]); 2968 } else { 2969 $g[] = $glyphID; 2970 } 2971 } 2972 } 2973 } 2974 2975 return $g; 2976 } 2977 2978 ////////////////////////////////////////////////////////////////////////////////// 2979 function _getClasses($offset) 2980 { 2981 $this->seek($offset); 2982 $ClassFormat = $this->read_ushort(); 2983 $GlyphByClass = []; 2984 if ($ClassFormat == 1) { 2985 $StartGlyph = $this->read_ushort(); 2986 $GlyphCount = $this->read_ushort(); 2987 for ($i = 0; $i < $GlyphCount; $i++) { 2988 $startGlyphID = $StartGlyph + $i; 2989 $endGlyphID = $StartGlyph + $i; 2990 $class = $this->read_ushort(); 2991 for ($g = $startGlyphID; $g <= $endGlyphID; $g++) { 2992 if ($this->glyphToChar[$g][0]) { 2993 $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]); 2994 } 2995 } 2996 } 2997 } else { 2998 if ($ClassFormat == 2) { 2999 $tableCount = $this->read_ushort(); 3000 for ($i = 0; $i < $tableCount; $i++) { 3001 $startGlyphID = $this->read_ushort(); 3002 $endGlyphID = $this->read_ushort(); 3003 $class = $this->read_ushort(); 3004 for ($g = $startGlyphID; $g <= $endGlyphID; $g++) { 3005 if ($this->glyphToChar[$g][0]) { 3006 $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]); 3007 } 3008 } 3009 } 3010 } 3011 } 3012 $gbc = []; 3013 foreach ($GlyphByClass as $class => $garr) { 3014 $gbc[$class] = implode('|', $garr); 3015 } 3016 3017 return $gbc; 3018 } 3019 3020 ////////////////////////////////////////////////////////////////////////////////// 3021 ////////////////////////////////////////////////////////////////////////////////// 3022 ////////////////////////////////////////////////////////////////////////////////// 3023 ////////////////////////////////////////////////////////////////////////////////// 3024 ////////////////////////////////////////////////////////////////////////////////// 3025 function _getGPOStables() 3026 { 3027 /////////////////////////////////// 3028 // GPOS - Glyph Positioning 3029 /////////////////////////////////// 3030 if (isset($this->tables["GPOS"])) { 3031 $this->mpdf->WriteHTML('<h1>GPOS Tables</h1>'); 3032 $ffeats = []; 3033 $gpos_offset = $this->seek_table("GPOS"); 3034 $this->skip(4); 3035 $ScriptList_offset = $gpos_offset + $this->read_ushort(); 3036 $FeatureList_offset = $gpos_offset + $this->read_ushort(); 3037 $LookupList_offset = $gpos_offset + $this->read_ushort(); 3038 3039 // ScriptList 3040 $this->seek($ScriptList_offset); 3041 $ScriptCount = $this->read_ushort(); 3042 for ($i = 0; $i < $ScriptCount; $i++) { 3043 $ScriptTag = $this->read_tag(); // = "beng", "deva" etc. 3044 $ScriptTableOffset = $this->read_ushort(); 3045 $ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset; 3046 } 3047 3048 // Script Table 3049 foreach ($ffeats as $t => $o) { 3050 $ls = []; 3051 $this->seek($o); 3052 $DefLangSys_offset = $this->read_ushort(); 3053 if ($DefLangSys_offset > 0) { 3054 $ls['DFLT'] = $DefLangSys_offset + $o; 3055 } 3056 $LangSysCount = $this->read_ushort(); 3057 for ($i = 0; $i < $LangSysCount; $i++) { 3058 $LangTag = $this->read_tag(); // = 3059 $LangTableOffset = $this->read_ushort(); 3060 $ls[$LangTag] = $o + $LangTableOffset; 3061 } 3062 $ffeats[$t] = $ls; 3063 } 3064 3065 // Get FeatureIndexList 3066 // LangSys Table - from first listed langsys 3067 foreach ($ffeats as $st => $scripts) { 3068 foreach ($scripts as $t => $o) { 3069 $FeatureIndex = []; 3070 $langsystable_offset = $o; 3071 $this->seek($langsystable_offset); 3072 $LookUpOrder = $this->read_ushort(); //==NULL 3073 $ReqFeatureIndex = $this->read_ushort(); 3074 if ($ReqFeatureIndex != 0xFFFF) { 3075 $FeatureIndex[] = $ReqFeatureIndex; 3076 } 3077 $FeatureCount = $this->read_ushort(); 3078 for ($i = 0; $i < $FeatureCount; $i++) { 3079 $FeatureIndex[] = $this->read_ushort(); // = index of feature 3080 } 3081 $ffeats[$st][$t] = $FeatureIndex; 3082 } 3083 } 3084//print_r($ffeats); exit; 3085 // Feauture List => LookupListIndex es 3086 $this->seek($FeatureList_offset); 3087 $FeatureCount = $this->read_ushort(); 3088 $Feature = []; 3089 for ($i = 0; $i < $FeatureCount; $i++) { 3090 $Feature[$i] = ['tag' => $this->read_tag()]; 3091 $Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort(); 3092 } 3093 for ($i = 0; $i < $FeatureCount; $i++) { 3094 $this->seek($Feature[$i]['offset']); 3095 $this->read_ushort(); // null 3096 $Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort(); 3097 $Feature[$i]['LookupListIndex'] = []; 3098 for ($c = 0; $c < $Lookupcount; $c++) { 3099 $Feature[$i]['LookupListIndex'][] = $this->read_ushort(); 3100 } 3101 } 3102 3103 foreach ($ffeats as $st => $scripts) { 3104 foreach ($scripts as $t => $o) { 3105 $FeatureIndex = $ffeats[$st][$t]; 3106 foreach ($FeatureIndex as $k => $fi) { 3107 $ffeats[$st][$t][$k] = $Feature[$fi]; 3108 } 3109 } 3110 } 3111//print_r($ffeats); exit; 3112 //===================================================================================== 3113 $gpos = []; 3114 $GPOSScriptLang = []; 3115 foreach ($ffeats as $st => $scripts) { 3116 foreach ($scripts as $t => $langsys) { 3117 $lg = []; 3118 foreach ($langsys as $ft) { 3119 $lg[$ft['LookupListIndex'][0]] = $ft; 3120 } 3121 // list of Lookups in order they need to be run i.e. order listed in Lookup table 3122 ksort($lg); 3123 foreach ($lg as $ft) { 3124 $gpos[$st][$t][$ft['tag']] = $ft['LookupListIndex']; 3125 } 3126 if (!isset($GPOSScriptLang[$st])) { 3127 $GPOSScriptLang[$st] = ''; 3128 } 3129 $GPOSScriptLang[$st] .= $t . ' '; 3130 } 3131 } 3132 if ($this->mode == 'summary') { 3133 $this->mpdf->WriteHTML('<h3>GPOS Scripts & Languages</h3>'); 3134 $html = ''; 3135 if (count($gpos)) { 3136 foreach ($gpos as $st => $g) { 3137 $html .= '<h5>' . $st . '</h5>'; 3138 foreach ($g as $l => $t) { 3139 $html .= '<div><a href="font_dump_OTL.php?script=' . $st . '&lang=' . $l . '">' . $l . '</a></b>: '; 3140 foreach ($t as $tag => $o) { 3141 $html .= $tag . ' '; 3142 } 3143 $html .= '</div>'; 3144 } 3145 } 3146 } else { 3147 $html .= '<div>No entries in GPOS table.</div>'; 3148 } 3149 $this->mpdf->WriteHTML($html); 3150 $this->mpdf->WriteHTML('</div>'); 3151 3152 return 0; 3153 } 3154 3155 //===================================================================================== 3156 // Get metadata and offsets for whole Lookup List table 3157 $this->seek($LookupList_offset); 3158 $LookupCount = $this->read_ushort(); 3159 $Lookup = []; 3160 $Offsets = []; 3161 $SubtableCount = []; 3162 for ($i = 0; $i < $LookupCount; $i++) { 3163 $Offsets[$i] = $LookupList_offset + $this->read_ushort(); 3164 } 3165 for ($i = 0; $i < $LookupCount; $i++) { 3166 $this->seek($Offsets[$i]); 3167 $Lookup[$i]['Type'] = $this->read_ushort(); 3168 $Lookup[$i]['Flag'] = $flag = $this->read_ushort(); 3169 $Lookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort(); 3170 for ($c = 0; $c < $SubtableCount[$i]; $c++) { 3171 $Lookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort(); 3172 } 3173 // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure 3174 if (($flag & 0x0010) == 0x0010) { 3175 $Lookup[$i]['MarkFilteringSet'] = $this->read_ushort(); 3176 } 3177 // else { $Lookup[$i]['MarkFilteringSet'] = ''; } 3178 // Lookup Type 9: Extension 3179 if ($Lookup[$i]['Type'] == 9) { 3180 // Overwrites new offset (32-bit) for each subtable, and a new lookup Type 3181 for ($c = 0; $c < $SubtableCount[$i]; $c++) { 3182 $this->seek($Lookup[$i]['Subtables'][$c]); 3183 $ExtensionPosFormat = $this->read_ushort(); 3184 $type = $this->read_ushort(); 3185 $Lookup[$i]['Subtables'][$c] = $Lookup[$i]['Subtables'][$c] + $this->read_ulong(); 3186 } 3187 $Lookup[$i]['Type'] = $type; 3188 } 3189 } 3190 3191 //===================================================================================== 3192 3193 $st = $this->mpdf->OTLscript; 3194 $t = $this->mpdf->OTLlang; 3195 $langsys = $gpos[$st][$t]; 3196 3197 $lul = []; // array of LookupListIndexes 3198 $tags = []; // corresponding array of feature tags e.g. 'ccmp' 3199 if (count($langsys)) { 3200 foreach ($langsys as $tag => $ft) { 3201 foreach ($ft as $ll) { 3202 $lul[$ll] = $tag; 3203 } 3204 } 3205 } 3206 ksort($lul); // Order the Lookups in the order they are in the GUSB table, regardless of Feature order 3207 $this->_getGPOSarray($Lookup, $lul, $st); 3208 3209//print_r($lul); exit; 3210 3211 return [$GPOSScriptLang, $gpos, $Lookup]; 3212 } // end if GPOS 3213 } 3214 3215 ////////////////////////////////////////////////////////////////////////////////// 3216 //===================================================================================== 3217 //===================================================================================== 3218 //===================================================================================== 3219///////////////////////////////////////////////////////////////////////////////////////// 3220 // GPOS functions 3221 function _getGPOSarray(&$Lookup, $lul, $scripttag, $level = 1, $lcoverage = '', $exB = '', $exL = '') 3222 { 3223 // Process (3) LookupList for specific Script-LangSys 3224 $html = ''; 3225 if ($level == 1) { 3226 $html .= '<bookmark level="0" content="GPOS features">'; 3227 } 3228 foreach ($lul as $luli => $tag) { 3229 $html .= '<div class="level' . $level . '">'; 3230 $html .= '<h5 class="level' . $level . '">'; 3231 if ($level == 1) { 3232 $html .= '<bookmark level="1" content="' . $tag . ' [#' . $luli . ']">'; 3233 } 3234 $html .= 'Lookup #' . $luli . ' [tag: <span style="color:#000066;">' . $tag . '</span>]</h5>'; 3235 $ignore = $this->_getGSUBignoreString($Lookup[$luli]['Flag'], $Lookup[$luli]['MarkFilteringSet']); 3236 if ($ignore) { 3237 $html .= '<div class="ignore">Ignoring: ' . $ignore . '</div> '; 3238 } 3239 3240 $Type = $Lookup[$luli]['Type']; 3241 $Flag = $Lookup[$luli]['Flag']; 3242 if (($Flag & 0x0001) == 1) { 3243 $dir = 'RTL'; 3244 } else { 3245 $dir = 'LTR'; 3246 } 3247 3248 for ($c = 0; $c < $Lookup[$luli]['SubtableCount']; $c++) { 3249 $html .= '<div class="subtable">Subtable #' . $c; 3250 if ($level == 1) { 3251 $html .= '<bookmark level="2" content="Subtable #' . $c . '">'; 3252 } 3253 $html .= '</div>'; 3254 3255 // Lets start 3256 $subtable_offset = $Lookup[$luli]['Subtables'][$c]; 3257 $this->seek($subtable_offset); 3258 $PosFormat = $this->read_ushort(); 3259 3260 //////////////////////////////////////////////////////////////////////////////// 3261 // LookupType 1: Single adjustment Adjust position of a single glyph (e.g. SmallCaps/Sups/Subs) 3262 //////////////////////////////////////////////////////////////////////////////// 3263 if ($Lookup[$luli]['Type'] == 1) { 3264 $html .= '<div class="lookuptype">LookupType 1: Single adjustment [Format ' . $PosFormat . ']</div>'; 3265 //=========== 3266 // Format 1: 3267 //=========== 3268 if ($PosFormat == 1) { 3269 $Coverage = $subtable_offset + $this->read_ushort(); 3270 $ValueFormat = $this->read_ushort(); 3271 $Value = $this->_getValueRecord($ValueFormat); 3272 3273 $this->seek($Coverage); 3274 $glyphs = $this->_getCoverage(); // Array of Hex Glyphs 3275 for ($g = 0; $g < count($glyphs); $g++) { 3276 if ($level == 2 && strpos($lcoverage, $glyphs[$g]) === false) { 3277 continue; 3278 } 3279 3280 $html .= '<div class="substitution">'; 3281 $html .= '<span class="unicode">' . $this->formatUni($glyphs[$g]) . ' </span> '; 3282 if ($level == 2 && $exB) { 3283 $html .= $exB; 3284 } 3285 $html .= '<span class="unchanged"> ' . $this->formatEntity($glyphs[$g]) . '</span>'; 3286 if ($level == 2 && $exL) { 3287 $html .= $exL; 3288 } 3289 $html .= ' » » '; 3290 if ($level == 2 && $exB) { 3291 $html .= $exB; 3292 } 3293 $html .= '<span class="changed" style="font-feature-settings:\'' . $tag . '\' 1;"> ' . $this->formatEntity($glyphs[$g]) . '</span>'; 3294 if ($level == 2 && $exL) { 3295 $html .= $exL; 3296 } 3297 $html .= ' <span class="unicode">'; 3298 if ($Value['XPlacement']) { 3299 $html .= ' Xpl: ' . $Value['XPlacement'] . ';'; 3300 } 3301 if ($Value['YPlacement']) { 3302 $html .= ' YPl: ' . $Value['YPlacement'] . ';'; 3303 } 3304 if ($Value['XAdvance']) { 3305 $html .= ' Xadv: ' . $Value['XAdvance']; 3306 } 3307 $html .= '</span>'; 3308 $html .= '</div>'; 3309 } 3310 } //=========== 3311 // Format 2: 3312 //=========== 3313 else { 3314 if ($PosFormat == 2) { 3315 $Coverage = $subtable_offset + $this->read_ushort(); 3316 $ValueFormat = $this->read_ushort(); 3317 $ValueCount = $this->read_ushort(); 3318 $Values = []; 3319 for ($v = 0; $v < $ValueCount; $v++) { 3320 $Values[] = $this->_getValueRecord($ValueFormat); 3321 } 3322 3323 $this->seek($Coverage); 3324 $glyphs = $this->_getCoverage(); // Array of Hex Glyphs 3325 3326 for ($g = 0; $g < count($glyphs); $g++) { 3327 if ($level == 2 && strpos($lcoverage, $glyphs[$g]) === false) { 3328 continue; 3329 } 3330 $Value = $Values[$g]; 3331 3332 $html .= '<div class="substitution">'; 3333 $html .= '<span class="unicode">' . $this->formatUni($glyphs[$g]) . ' </span> '; 3334 if ($level == 2 && $exB) { 3335 $html .= $exB; 3336 } 3337 $html .= '<span class="unchanged"> ' . $this->formatEntity($glyphs[$g]) . '</span>'; 3338 if ($level == 2 && $exL) { 3339 $html .= $exL; 3340 } 3341 $html .= ' » » '; 3342 if ($level == 2 && $exB) { 3343 $html .= $exB; 3344 } 3345 $html .= '<span class="changed" style="font-feature-settings:\'' . $tag . '\' 1;"> ' . $this->formatEntity($glyphs[$g]) . '</span>'; 3346 if ($level == 2 && $exL) { 3347 $html .= $exL; 3348 } 3349 $html .= ' <span class="unicode">'; 3350 if ($Value['XPlacement']) { 3351 $html .= ' Xpl: ' . $Value['XPlacement'] . ';'; 3352 } 3353 if ($Value['YPlacement']) { 3354 $html .= ' YPl: ' . $Value['YPlacement'] . ';'; 3355 } 3356 if ($Value['XAdvance']) { 3357 $html .= ' Xadv: ' . $Value['XAdvance']; 3358 } 3359 $html .= '</span>'; 3360 $html .= '</div>'; 3361 } 3362 } 3363 } 3364 } //////////////////////////////////////////////////////////////////////////////// 3365 // LookupType 2: Pair adjustment Adjust position of a pair of glyphs (Kerning) 3366 //////////////////////////////////////////////////////////////////////////////// 3367 else { 3368 if ($Lookup[$luli]['Type'] == 2) { 3369 $html .= '<div class="lookuptype">LookupType 2: Pair adjustment e.g. Kerning [Format ' . $PosFormat . ']</div>'; 3370 $Coverage = $subtable_offset + $this->read_ushort(); 3371 $ValueFormat1 = $this->read_ushort(); 3372 $ValueFormat2 = $this->read_ushort(); 3373 //=========== 3374 // Format 1: 3375 //=========== 3376 if ($PosFormat == 1) { 3377 $PairSetCount = $this->read_ushort(); 3378 $PairSetOffset = []; 3379 for ($p = 0; $p < $PairSetCount; $p++) { 3380 $PairSetOffset[] = $subtable_offset + $this->read_ushort(); 3381 } 3382 $this->seek($Coverage); 3383 $glyphs = $this->_getCoverage(); // Array of Hex Glyphs 3384 for ($p = 0; $p < $PairSetCount; $p++) { 3385 if ($level == 2 && strpos($lcoverage, $glyphs[$p]) === false) { 3386 continue; 3387 } 3388 $this->seek($PairSetOffset[$p]); 3389 // First Glyph = $glyphs[$p] 3390// Takes too long e.g. Calibri font - just list kerning pairs with this: 3391 $html .= '<div class="glyphs">'; 3392 $html .= '<span class="unchanged"> ' . $this->formatEntity($glyphs[$p]) . ' </span>'; 3393 3394 //PairSet table 3395 $PairValueCount = $this->read_ushort(); 3396 for ($pv = 0; $pv < $PairValueCount; $pv++) { 3397 //PairValueRecord 3398 $gid = $this->read_ushort(); 3399 $SecondGlyph = unicode_hex($this->glyphToChar[$gid][0]); 3400 $Value1 = $this->_getValueRecord($ValueFormat1); 3401 $Value2 = $this->_getValueRecord($ValueFormat2); 3402 3403 // If RTL pairs, GPOS declares a XPlacement e.g. -180 for an XAdvance of -180 to take 3404 // account of direction. mPDF does not need the XPlacement adjustment 3405 if ($dir == 'RTL' && $Value1['XPlacement']) { 3406 $Value1['XPlacement'] -= $Value1['XAdvance']; 3407 } 3408 3409 if ($ValueFormat2) { 3410 // If RTL pairs, GPOS declares a XPlacement e.g. -180 for an XAdvance of -180 to take 3411 // account of direction. mPDF does not need the XPlacement adjustment 3412 if ($dir == 'RTL' && $Value2['XPlacement'] && $Value2['XAdvance']) { 3413 $Value2['XPlacement'] -= $Value2['XAdvance']; 3414 } 3415 } 3416 3417 $html .= ' ' . $this->formatEntity($SecondGlyph) . ' '; 3418 3419 /* 3420 $html .= '<div class="substitution">'; 3421 $html .= '<span class="unicode">'.$this->formatUni($glyphs[$p]).' </span> '; 3422 if ($level==2 && $exB) { $html .= $exB; } 3423 $html .= '<span class="unchanged"> '.$this->formatEntity($glyphs[$p]).$this->formatEntity($SecondGlyph).'</span>'; 3424 if ($level==2 && $exL) { $html .= $exL; } 3425 $html .= ' » » '; 3426 if ($level==2 && $exB) { $html .= $exB; } 3427 $html .= '<span class="changed" style="font-feature-settings:\''.$tag.'\' 1;"> '.$this->formatEntity($glyphs[$p]).$this->formatEntity($SecondGlyph).'</span>'; 3428 if ($level==2 && $exL) { $html .= $exL; } 3429 $html .= ' <span class="unicode">'; 3430 if ($Value1['XPlacement']) { $html .= ' Xpl[1]: '.$Value1['XPlacement'].';'; } 3431 if ($Value1['YPlacement']) { $html .= ' YPl[1]: '.$Value1['YPlacement'].';'; } 3432 if ($Value1['XAdvance']) { $html .= ' Xadv[1]: '.$Value1['XAdvance']; } 3433 if ($Value2['XPlacement']) { $html .= ' Xpl[2]: '.$Value2['XPlacement'].';'; } 3434 if ($Value2['YPlacement']) { $html .= ' YPl[2]: '.$Value2['YPlacement'].';'; } 3435 if ($Value2['XAdvance']) { $html .= ' Xadv[2]: '.$Value2['XAdvance']; } 3436 $html .= '</span>'; 3437 $html .= '</div>'; 3438 */ 3439 } 3440 $html .= '</div>'; 3441 } 3442 } //=========== 3443 // Format 2: 3444 //=========== 3445 else { 3446 if ($PosFormat == 2) { 3447 $ClassDef1 = $subtable_offset + $this->read_ushort(); 3448 $ClassDef2 = $subtable_offset + $this->read_ushort(); 3449 $Class1Count = $this->read_ushort(); 3450 $Class2Count = $this->read_ushort(); 3451 3452 $sizeOfPair = (2 * $this->count_bits($ValueFormat1)) + (2 * $this->count_bits($ValueFormat2)); 3453 $sizeOfValueRecords = $Class1Count * $Class2Count * $sizeOfPair; 3454 3455 // NB Class1Count includes Class 0 even though it is not defined by $ClassDef1 3456 // i.e. Class1Count = 5; Class1 will contain array(indices 1-4); 3457 $Class1 = $this->_getClassDefinitionTable($ClassDef1); 3458 $Class2 = $this->_getClassDefinitionTable($ClassDef2); 3459 3460 $this->seek($subtable_offset + 16); 3461 3462 for ($i = 0; $i < $Class1Count; $i++) { 3463 for ($j = 0; $j < $Class2Count; $j++) { 3464 $Value1 = $this->_getValueRecord($ValueFormat1); 3465 $Value2 = $this->_getValueRecord($ValueFormat2); 3466 3467 // If RTL pairs, GPOS declares a XPlacement e.g. -180 for an XAdvance of -180 3468 // of direction. mPDF does not need the XPlacement adjustment 3469 if ($dir == 'RTL' && $Value1['XPlacement'] && $Value1['XAdvance']) { 3470 $Value1['XPlacement'] -= $Value1['XAdvance']; 3471 } 3472 if ($ValueFormat2) { 3473 if ($dir == 'RTL' && $Value2['XPlacement'] && $Value2['XAdvance']) { 3474 $Value2['XPlacement'] -= $Value2['XAdvance']; 3475 } 3476 } 3477 3478 for ($c1 = 0; $c1 < count($Class1[$i]); $c1++) { 3479 $FirstGlyph = $Class1[$i][$c1]; 3480 if ($level == 2 && strpos($lcoverage, $FirstGlyph) === false) { 3481 continue; 3482 } 3483 3484 for ($c2 = 0; $c2 < count($Class2[$j]); $c2++) { 3485 $SecondGlyph = $Class2[$j][$c2]; 3486 3487 if (!$Value1['XPlacement'] && !$Value1['YPlacement'] && !$Value1['XAdvance'] && !$Value2['XPlacement'] && !$Value2['YPlacement'] && !$Value2['XAdvance']) { 3488 continue; 3489 } 3490 3491 $html .= '<div class="substitution">'; 3492 $html .= '<span class="unicode">' . $this->formatUni($FirstGlyph) . ' </span> '; 3493 if ($level == 2 && $exB) { 3494 $html .= $exB; 3495 } 3496 $html .= '<span class="unchanged"> ' . $this->formatEntity($FirstGlyph) . $this->formatEntity($SecondGlyph) . '</span>'; 3497 if ($level == 2 && $exL) { 3498 $html .= $exL; 3499 } 3500 $html .= ' » » '; 3501 if ($level == 2 && $exB) { 3502 $html .= $exB; 3503 } 3504 $html .= '<span class="changed" style="font-feature-settings:\'' . $tag . '\' 1;"> ' . $this->formatEntity($FirstGlyph) . $this->formatEntity($SecondGlyph) . '</span>'; 3505 if ($level == 2 && $exL) { 3506 $html .= $exL; 3507 } 3508 $html .= ' <span class="unicode">'; 3509 if ($Value1['XPlacement']) { 3510 $html .= ' Xpl[1]: ' . $Value1['XPlacement'] . ';'; 3511 } 3512 if ($Value1['YPlacement']) { 3513 $html .= ' YPl[1]: ' . $Value1['YPlacement'] . ';'; 3514 } 3515 if ($Value1['XAdvance']) { 3516 $html .= ' Xadv[1]: ' . $Value1['XAdvance']; 3517 } 3518 if ($Value2['XPlacement']) { 3519 $html .= ' Xpl[2]: ' . $Value2['XPlacement'] . ';'; 3520 } 3521 if ($Value2['YPlacement']) { 3522 $html .= ' YPl[2]: ' . $Value2['YPlacement'] . ';'; 3523 } 3524 if ($Value2['XAdvance']) { 3525 $html .= ' Xadv[2]: ' . $Value2['XAdvance']; 3526 } 3527 $html .= '</span>'; 3528 $html .= '</div>'; 3529 } 3530 } 3531 } 3532 } 3533 } 3534 } 3535 } //////////////////////////////////////////////////////////////////////////////// 3536 // LookupType 3: Cursive attachment Attach cursive glyphs 3537 //////////////////////////////////////////////////////////////////////////////// 3538 else { 3539 if ($Lookup[$luli]['Type'] == 3) { 3540 $html .= '<div class="lookuptype">LookupType 3: Cursive attachment </div>'; 3541 $Coverage = $subtable_offset + $this->read_ushort(); 3542 $EntryExitCount = $this->read_ushort(); 3543 $EntryAnchors = []; 3544 $ExitAnchors = []; 3545 for ($i = 0; $i < $EntryExitCount; $i++) { 3546 $EntryAnchors[$i] = $this->read_ushort(); 3547 $ExitAnchors[$i] = $this->read_ushort(); 3548 } 3549 3550 $this->seek($Coverage); 3551 $Glyphs = $this->_getCoverage(); 3552 for ($i = 0; $i < $EntryExitCount; $i++) { 3553 // Need default XAdvance for glyph 3554 $pdfWidth = $this->mpdf->_getCharWidth($this->mpdf->fonts[$this->fontkey]['cw'], hexdec($Glyphs[$i])); 3555 $EntryAnchor = $EntryAnchors[$i]; 3556 $ExitAnchor = $ExitAnchors[$i]; 3557 $html .= '<div class="glyphs">'; 3558 $html .= '<span class="unchanged">' . $this->formatEntity($Glyphs[$i]) . ' </span> '; 3559 $html .= '<span class="unicode"> ' . $this->formatUni($Glyphs[$i]) . ' => '; 3560 3561 if ($EntryAnchor != 0) { 3562 $EntryAnchor += $subtable_offset; 3563 list($x, $y) = $this->_getAnchorTable($EntryAnchor); 3564 if ($dir == 'RTL') { 3565 if (round($pdfWidth) == round($x * 1000 / $this->mpdf->fonts[$this->fontkey]['desc']['unitsPerEm'])) { 3566 $x = 0; 3567 } else { 3568 $x = $x - ($pdfWidth * $this->mpdf->fonts[$this->fontkey]['desc']['unitsPerEm'] / 1000); 3569 } 3570 } 3571 $html .= " Entry X: " . $x . " Y: " . $y . "; "; 3572 } 3573 if ($ExitAnchor != 0) { 3574 $ExitAnchor += $subtable_offset; 3575 list($x, $y) = $this->_getAnchorTable($ExitAnchor); 3576 if ($dir == 'LTR') { 3577 if (round($pdfWidth) == round($x * 1000 / $this->mpdf->fonts[$this->fontkey]['desc']['unitsPerEm'])) { 3578 $x = 0; 3579 } else { 3580 $x = $x - ($pdfWidth * $this->mpdf->fonts[$this->fontkey]['desc']['unitsPerEm'] / 1000); 3581 } 3582 } 3583 $html .= " Exit X: " . $x . " Y: " . $y . "; "; 3584 } 3585 3586 $html .= '</span></div>'; 3587 } 3588 } //////////////////////////////////////////////////////////////////////////////// 3589 // LookupType 4: MarkToBase attachment Attach a combining mark to a base glyph 3590 //////////////////////////////////////////////////////////////////////////////// 3591 else { 3592 if ($Lookup[$luli]['Type'] == 4) { 3593 $html .= '<div class="lookuptype">LookupType 4: MarkToBase attachment </div>'; 3594 $MarkCoverage = $subtable_offset + $this->read_ushort(); 3595 $BaseCoverage = $subtable_offset + $this->read_ushort(); 3596 3597 $this->seek($MarkCoverage); 3598 $MarkGlyphs = $this->_getCoverage(); 3599 3600 $this->seek($BaseCoverage); 3601 $BaseGlyphs = $this->_getCoverage(); 3602 3603 $firstMark = ''; 3604 $html .= '<div class="glyphs">Marks: '; 3605 for ($i = 0; $i < count($MarkGlyphs); $i++) { 3606 if ($level == 2 && strpos($lcoverage, $MarkGlyphs[$i]) === false) { 3607 continue; 3608 } else { 3609 if (!$firstMark) { 3610 $firstMark = $MarkGlyphs[$i]; 3611 } 3612 } 3613 $html .= ' ' . $this->formatEntity($MarkGlyphs[$i]) . ' '; 3614 } 3615 $html .= '</div>'; 3616 if (!$firstMark) { 3617 return; 3618 } 3619 3620 $html .= '<div class="glyphs">Bases: '; 3621 for ($j = 0; $j < count($BaseGlyphs); $j++) { 3622 $html .= ' ' . $this->formatEntity($BaseGlyphs[$j]) . ' '; 3623 } 3624 $html .= '</div>'; 3625 3626 // Example 3627 $html .= '<div class="glyphs" style="font-feature-settings:\'' . $tag . '\' 1;">Example(s): '; 3628 for ($j = 0; $j < min(count($BaseGlyphs), 20); $j++) { 3629 $html .= ' ' . $this->formatEntity($BaseGlyphs[$j]) . $this->formatEntity($firstMark, true) . ' '; 3630 } 3631 $html .= '</div>'; 3632 } //////////////////////////////////////////////////////////////////////////////// 3633 // LookupType 5: MarkToLigature attachment Attach a combining mark to a ligature 3634 //////////////////////////////////////////////////////////////////////////////// 3635 else { 3636 if ($Lookup[$luli]['Type'] == 5) { 3637 $html .= '<div class="lookuptype">LookupType 5: MarkToLigature attachment </div>'; 3638 $MarkCoverage = $subtable_offset + $this->read_ushort(); 3639 //$MarkCoverage is already set in $lcoverage 00065|00073 etc 3640 $LigatureCoverage = $subtable_offset + $this->read_ushort(); 3641 $ClassCount = $this->read_ushort(); // Number of classes defined for marks = Number of mark glyphs in the MarkCoverage table 3642 $MarkArray = $subtable_offset + $this->read_ushort(); // Offset to MarkArray table 3643 $LigatureArray = $subtable_offset + $this->read_ushort(); // Offset to LigatureArray table 3644 3645 $this->seek($MarkCoverage); 3646 $MarkGlyphs = $this->_getCoverage(); 3647 $this->seek($LigatureCoverage); 3648 $LigatureGlyphs = $this->_getCoverage(); 3649 3650 $firstMark = ''; 3651 $html .= '<div class="glyphs">Marks: <span class="unchanged">'; 3652 $MarkRecord = []; 3653 for ($i = 0; $i < count($MarkGlyphs); $i++) { 3654 if ($level == 2 && strpos($lcoverage, $MarkGlyphs[$i]) === false) { 3655 continue; 3656 } else { 3657 if (!$firstMark) { 3658 $firstMark = $MarkGlyphs[$i]; 3659 } 3660 } 3661 // Get the relevant MarkRecord 3662 $MarkRecord[$i] = $this->_getMarkRecord($MarkArray, $i); 3663 //Mark Class is = $MarkRecord[$i]['Class'] 3664 $html .= ' ' . $this->formatEntity($MarkGlyphs[$i]) . ' '; 3665 } 3666 $html .= '</span></div>'; 3667 if (!$firstMark) { 3668 return; 3669 } 3670 3671 $this->seek($LigatureArray); 3672 $LigatureCount = $this->read_ushort(); 3673 $LigatureAttach = []; 3674 $html .= '<div class="glyphs">Ligatures: <span class="unchanged">'; 3675 for ($j = 0; $j < count($LigatureGlyphs); $j++) { 3676 // Get the relevant LigatureRecord 3677 $LigatureAttach[$j] = $LigatureArray + $this->read_ushort(); 3678 $html .= ' ' . $this->formatEntity($LigatureGlyphs[$j]) . ' '; 3679 } 3680 $html .= '</span></div>'; 3681 3682 /* 3683 for ($i=0;$i<count($MarkGlyphs);$i++) { 3684 $html .= '<div class="glyphs">'; 3685 $html .= '<span class="unchanged">'.$this->formatEntity($MarkGlyphs[$i]).'</span>'; 3686 3687 for ($j=0;$j<count($LigatureGlyphs);$j++) { 3688 $this->seek($LigatureAttach[$j]); 3689 $ComponentCount = $this->read_ushort(); 3690 $html .= '<span class="unchanged">'.$this->formatEntity($LigatureGlyphs[$j]).'</span>'; 3691 $offsets = array(); 3692 for ($comp=0;$comp<$ComponentCount;$comp++) { 3693 // ComponentRecords 3694 for ($class=0;$class<$ClassCount;$class++) { 3695 $offset = $this->read_ushort(); 3696 if ($offset!= 0 && $class == $MarkRecord[$i]['Class']) { 3697 3698 $html .= ' ['.$comp.'] '; 3699 3700 } 3701 } 3702 } 3703 } 3704 $html .= '</span></div>'; 3705 } 3706 */ 3707 } //////////////////////////////////////////////////////////////////////////////// 3708 // LookupType 6: MarkToMark attachment Attach a combining mark to another mark 3709 //////////////////////////////////////////////////////////////////////////////// 3710 else { 3711 if ($Lookup[$luli]['Type'] == 6) { 3712 $html .= '<div class="lookuptype">LookupType 6: MarkToMark attachment </div>'; 3713 $Mark1Coverage = $subtable_offset + $this->read_ushort(); // Combining Mark 3714 //$Mark1Coverage is already set in $LuCoverage 0065|0073 etc 3715 $Mark2Coverage = $subtable_offset + $this->read_ushort(); // Base Mark 3716 $ClassCount = $this->read_ushort(); // Number of classes defined for marks = No. of Combining mark1 glyphs in the MarkCoverage table 3717 $this->seek($Mark1Coverage); 3718 $Mark1Glyphs = $this->_getCoverage(); 3719 $this->seek($Mark2Coverage); 3720 $Mark2Glyphs = $this->_getCoverage(); 3721 3722 $firstMark = ''; 3723 $html .= '<div class="glyphs">Marks: <span class="unchanged">'; 3724 for ($i = 0; $i < count($Mark1Glyphs); $i++) { 3725 if ($level == 2 && strpos($lcoverage, $Mark1Glyphs[$i]) === false) { 3726 continue; 3727 } else { 3728 if (!$firstMark) { 3729 $firstMark = $Mark1Glyphs[$i]; 3730 } 3731 } 3732 $html .= ' ' . $this->formatEntity($Mark1Glyphs[$i]) . ' '; 3733 } 3734 $html .= '</span></div>'; 3735 3736 if ($firstMark) { 3737 $html .= '<div class="glyphs">Bases: <span class="unchanged">'; 3738 for ($j = 0; $j < count($Mark2Glyphs); $j++) { 3739 $html .= ' ' . $this->formatEntity($Mark2Glyphs[$j]) . ' '; 3740 } 3741 $html .= '</span></div>'; 3742 3743 // Example 3744 $html .= '<div class="glyphs" style="font-feature-settings:\'' . $tag . '\' 1;">Example(s): <span class="changed">'; 3745 for ($j = 0; $j < min(count($Mark2Glyphs), 20); $j++) { 3746 $html .= ' ' . $this->formatEntity($Mark2Glyphs[$j]) . $this->formatEntity($firstMark, true) . ' '; 3747 } 3748 $html .= '</span></div>'; 3749 } 3750 } //////////////////////////////////////////////////////////////////////////////// 3751 // LookupType 7: Context positioning Position one or more glyphs in context 3752 //////////////////////////////////////////////////////////////////////////////// 3753 else { 3754 if ($Lookup[$luli]['Type'] == 7) { 3755 $html .= '<div class="lookuptype">LookupType 7: Context positioning [Format ' . $PosFormat . ']</div>'; 3756 //=========== 3757 // Format 1: 3758 //=========== 3759 if ($PosFormat == 1) { 3760 throw new \Mpdf\Exception\FontException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not YET TESTED."); 3761 } //=========== 3762 // Format 2: 3763 //=========== 3764 else { 3765 if ($PosFormat == 2) { 3766 throw new \Mpdf\Exception\FontException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not YET TESTED."); 3767 } //=========== 3768 // Format 3: 3769 //=========== 3770 else { 3771 if ($PosFormat == 3) { 3772 throw new \Mpdf\Exception\FontException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not YET TESTED."); 3773 } else { 3774 throw new \Mpdf\Exception\FontException("GPOS Lookup Type " . $Type . ", Format " . $PosFormat . " not supported."); 3775 } 3776 } 3777 } 3778 } //////////////////////////////////////////////////////////////////////////////// 3779 // LookupType 8: Chained Context positioning Position one or more glyphs in chained context 3780 //////////////////////////////////////////////////////////////////////////////// 3781 else { 3782 if ($Lookup[$luli]['Type'] == 8) { 3783 $html .= '<div class="lookuptype">LookupType 8: Chained Context positioning [Format ' . $PosFormat . ']</div>'; 3784 //=========== 3785 // Format 1: 3786 //=========== 3787 if ($PosFormat == 1) { 3788 throw new \Mpdf\Exception\FontException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not TESTED YET."); 3789 } //=========== 3790 // Format 2: 3791 //=========== 3792 else { 3793 if ($PosFormat == 2) { 3794 $html .= '<div>GPOS Lookup Type 8: Format 2 not yet supported in OTL dump</div>'; 3795 continue; 3796 /* NB When developing - cf. GSUB 6.2 */ 3797 throw new \Mpdf\Exception\FontException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not TESTED YET."); 3798 } //=========== 3799 // Format 3: 3800 //=========== 3801 else { 3802 if ($PosFormat == 3) { 3803 $BacktrackGlyphCount = $this->read_ushort(); 3804 $CoverageBacktrackOffset = []; 3805 for ($b = 0; $b < $BacktrackGlyphCount; $b++) { 3806 $CoverageBacktrackOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order 3807 } 3808 $InputGlyphCount = $this->read_ushort(); 3809 $CoverageInputOffset = []; 3810 for ($b = 0; $b < $InputGlyphCount; $b++) { 3811 $CoverageInputOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order 3812 } 3813 $LookaheadGlyphCount = $this->read_ushort(); 3814 $CoverageLookaheadOffset = []; 3815 for ($b = 0; $b < $LookaheadGlyphCount; $b++) { 3816 $CoverageLookaheadOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order 3817 } 3818 $PosCount = $this->read_ushort(); 3819 3820 $PosLookupRecord = []; 3821 for ($p = 0; $p < $PosCount; $p++) { 3822 // PosLookupRecord 3823 $PosLookupRecord[$p]['SequenceIndex'] = $this->read_ushort(); 3824 $PosLookupRecord[$p]['LookupListIndex'] = $this->read_ushort(); 3825 } 3826 3827 $backtrackGlyphs = []; 3828 for ($b = 0; $b < $BacktrackGlyphCount; $b++) { 3829 $this->seek($CoverageBacktrackOffset[$b]); 3830 $backtrackGlyphs[$b] = implode('|', $this->_getCoverage()); 3831 } 3832 $inputGlyphs = []; 3833 for ($b = 0; $b < $InputGlyphCount; $b++) { 3834 $this->seek($CoverageInputOffset[$b]); 3835 $inputGlyphs[$b] = implode('|', $this->_getCoverage()); 3836 } 3837 $lookaheadGlyphs = []; 3838 for ($b = 0; $b < $LookaheadGlyphCount; $b++) { 3839 $this->seek($CoverageLookaheadOffset[$b]); 3840 $lookaheadGlyphs[$b] = implode('|', $this->_getCoverage()); 3841 } 3842 3843 $exampleB = []; 3844 $exampleI = []; 3845 $exampleL = []; 3846 $html .= '<div class="context">CONTEXT: '; 3847 for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) { 3848 $html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>'; 3849 $exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]); 3850 } 3851 for ($ff = 0; $ff < count($inputGlyphs); $ff++) { 3852 $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; 3853 $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); 3854 } 3855 for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) { 3856 $html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>'; 3857 $exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]); 3858 } 3859 $html .= '</div>'; 3860 3861 for ($p = 0; $p < $PosCount; $p++) { 3862 $lup = $PosLookupRecord[$p]['LookupListIndex']; 3863 $seqIndex = $PosLookupRecord[$p]['SequenceIndex']; 3864 3865 // GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n] 3866 $exB = ''; 3867 $exL = ''; 3868 if (count($exampleB)) { 3869 $exB .= '<span class="backtrack">' . implode('‍', $exampleB) . '</span>'; 3870 } 3871 3872 if ($seqIndex > 0) { 3873 $exB .= '<span class="inputother">'; 3874 for ($ip = 0; $ip < $seqIndex; $ip++) { 3875 $exB .= $exampleI[$ip] . '‍'; 3876 } 3877 $exB .= '</span>'; 3878 } 3879 3880 if (count($inputGlyphs) > ($seqIndex + 1)) { 3881 $exL .= '<span class="inputother">'; 3882 for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { 3883 $exL .= '‍' . $exampleI[$ip]; 3884 } 3885 $exL .= '</span>'; 3886 } 3887 3888 if (count($exampleL)) { 3889 $exL .= '<span class="lookahead">' . implode('‍', $exampleL) . '</span>'; 3890 } 3891 3892 $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; 3893 3894 $lul2 = [$lup => $tag]; 3895 3896 // Only apply if the (first) 'Replace' glyph from the 3897 // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] 3898 // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 3899 // to level 2 and only apply if first Replace glyph is in this list 3900 $html .= $this->_getGPOSarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); 3901 } 3902 } 3903 } 3904 } 3905 } 3906 } 3907 } 3908 } 3909 } 3910 } 3911 } 3912 } 3913 } 3914 $html .= '</div>'; 3915 } 3916 if ($level == 1) { 3917 $this->mpdf->WriteHTML($html); 3918 } else { 3919 return $html; 3920 } 3921//print_r($Lookup); exit; 3922 } 3923 3924 //===================================================================================== 3925 //===================================================================================== 3926 // GPOS FUNCTIONS 3927 //===================================================================================== 3928 3929 function count_bits($n) 3930 { 3931 for ($c = 0; $n; $c++) { 3932 $n &= $n - 1; // clear the least significant bit set 3933 } 3934 3935 return $c; 3936 } 3937 3938 function _getValueRecord($ValueFormat) 3939 { 3940 // Common ValueRecord for GPOS 3941 // Only returns 3 possible: $vra['XPlacement'] $vra['YPlacement'] $vra['XAdvance'] 3942 $vra = []; 3943 // Horizontal adjustment for placement-in design units 3944 if (($ValueFormat & 0x0001) == 0x0001) { 3945 $vra['XPlacement'] = $this->read_short(); 3946 } 3947 // Vertical adjustment for placement-in design units 3948 if (($ValueFormat & 0x0002) == 0x0002) { 3949 $vra['YPlacement'] = $this->read_short(); 3950 } 3951 // Horizontal adjustment for advance-in design units (only used for horizontal writing) 3952 if (($ValueFormat & 0x0004) == 0x0004) { 3953 $vra['XAdvance'] = $this->read_short(); 3954 } 3955 // Vertical adjustment for advance-in design units (only used for vertical writing) 3956 if (($ValueFormat & 0x0008) == 0x0008) { 3957 $this->read_short(); 3958 } 3959 // Offset to Device table for horizontal placement-measured from beginning of PosTable (may be NULL) 3960 if (($ValueFormat & 0x0010) == 0x0010) { 3961 $this->read_ushort(); 3962 } 3963 // Offset to Device table for vertical placement-measured from beginning of PosTable (may be NULL) 3964 if (($ValueFormat & 0x0020) == 0x0020) { 3965 $this->read_ushort(); 3966 } 3967 // Offset to Device table for horizontal advance-measured from beginning of PosTable (may be NULL) 3968 if (($ValueFormat & 0x0040) == 0x0040) { 3969 $this->read_ushort(); 3970 } 3971 // Offset to Device table for vertical advance-measured from beginning of PosTable (may be NULL) 3972 if (($ValueFormat & 0x0080) == 0x0080) { 3973 $this->read_ushort(); 3974 } 3975 3976 return $vra; 3977 } 3978 3979 function _getAnchorTable($offset = 0) 3980 { 3981 if ($offset) { 3982 $this->seek($offset); 3983 } 3984 $AnchorFormat = $this->read_ushort(); 3985 $XCoordinate = $this->read_short(); 3986 $YCoordinate = $this->read_short(); 3987 3988 // Format 2 specifies additional link to contour point; Format 3 additional Device table 3989 return [$XCoordinate, $YCoordinate]; 3990 } 3991 3992 function _getMarkRecord($offset, $MarkPos) 3993 { 3994 $this->seek($offset); 3995 $MarkCount = $this->read_ushort(); 3996 $this->skip($MarkPos * 4); 3997 $Class = $this->read_ushort(); 3998 $MarkAnchor = $offset + $this->read_ushort(); // = Offset to anchor table 3999 list($x, $y) = $this->_getAnchorTable($MarkAnchor); 4000 $MarkRecord = ['Class' => $Class, 'AnchorX' => $x, 'AnchorY' => $y]; 4001 4002 return $MarkRecord; 4003 } 4004 4005 ////////////////////////////////////////////////////////////////////////////////// 4006 // Recursively get composite glyph data 4007 function getGlyphData($originalGlyphIdx, &$maxdepth, &$depth, &$points, &$contours) 4008 { 4009 $depth++; 4010 $maxdepth = max($maxdepth, $depth); 4011 if (count($this->glyphdata[$originalGlyphIdx]['compGlyphs'])) { 4012 foreach ($this->glyphdata[$originalGlyphIdx]['compGlyphs'] as $glyphIdx) { 4013 $this->getGlyphData($glyphIdx, $maxdepth, $depth, $points, $contours); 4014 } 4015 } else { 4016 if (($this->glyphdata[$originalGlyphIdx]['nContours'] > 0) && $depth > 0) { // simple 4017 $contours += $this->glyphdata[$originalGlyphIdx]['nContours']; 4018 $points += $this->glyphdata[$originalGlyphIdx]['nPoints']; 4019 } 4020 } 4021 $depth--; 4022 } 4023 4024 ////////////////////////////////////////////////////////////////////////////////// 4025 // Recursively get composite glyphs 4026 function getGlyphs($originalGlyphIdx, &$start, &$glyphSet, &$subsetglyphs) 4027 { 4028 $glyphPos = $this->glyphPos[$originalGlyphIdx]; 4029 $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos; 4030 if (!$glyphLen) { 4031 return; 4032 } 4033 $this->seek($start + $glyphPos); 4034 $numberOfContours = $this->read_short(); 4035 if ($numberOfContours < 0) { 4036 $this->skip(8); 4037 $flags = GlyphOperator::MORE; 4038 while ($flags & GlyphOperator::MORE) { 4039 $flags = $this->read_ushort(); 4040 } 4041 $glyphIdx = $this->read_ushort(); 4042 if (!isset($glyphSet[$glyphIdx])) { 4043 $glyphSet[$glyphIdx] = count($subsetglyphs); // old glyphID to new glyphID 4044 $subsetglyphs[$glyphIdx] = true; 4045 } 4046 $savepos = ftell($this->fh); 4047 $this->getGlyphs($glyphIdx, $start, $glyphSet, $subsetglyphs); 4048 $this->seek($savepos); 4049 if ($flags & GlyphOperator::WORDS) { 4050 $this->skip(4); 4051 } else { 4052 $this->skip(2); 4053 } 4054 if ($flags & GlyphOperator::SCALE) { 4055 $this->skip(2); 4056 } else { 4057 if ($flags & GlyphOperator::XYSCALE) { 4058 $this->skip(4); 4059 } else { 4060 if ($flags & GlyphOperator::TWOBYTWO) { 4061 $this->skip(8); 4062 } 4063 } 4064 } 4065 } 4066 } 4067 4068 4069 ////////////////////////////////////////////////////////////////////////////////// 4070 4071 function getHMTX($numberOfHMetrics, $numGlyphs, &$glyphToChar, $scale) 4072 { 4073 $start = $this->seek_table("hmtx"); 4074 $aw = 0; 4075 $this->charWidths = str_pad('', 256 * 256 * 2, "\x00"); 4076 if ($this->maxUniChar > 65536) { 4077 $this->charWidths .= str_pad('', 256 * 256 * 2, "\x00"); 4078 } // Plane 1 SMP 4079 if ($this->maxUniChar > 131072) { 4080 $this->charWidths .= str_pad('', 256 * 256 * 2, "\x00"); 4081 } // Plane 2 SMP 4082 $nCharWidths = 0; 4083 if (($numberOfHMetrics * 4) < $this->maxStrLenRead) { 4084 $data = $this->get_chunk($start, ($numberOfHMetrics * 4)); 4085 $arr = unpack("n*", $data); 4086 } else { 4087 $this->seek($start); 4088 } 4089 for ($glyph = 0; $glyph < $numberOfHMetrics; $glyph++) { 4090 if (($numberOfHMetrics * 4) < $this->maxStrLenRead) { 4091 $aw = $arr[($glyph * 2) + 1]; 4092 } else { 4093 $aw = $this->read_ushort(); 4094 $lsb = $this->read_ushort(); 4095 } 4096 if (isset($glyphToChar[$glyph]) || $glyph == 0) { 4097 if ($aw >= (1 << 15)) { 4098 $aw = 0; 4099 } // 1.03 Some (arabic) fonts have -ve values for width 4100 // although should be unsigned value - comes out as e.g. 65108 (intended -50) 4101 if ($glyph == 0) { 4102 $this->defaultWidth = $scale * $aw; 4103 continue; 4104 } 4105 foreach ($glyphToChar[$glyph] as $char) { 4106 //$this->charWidths[$char] = intval(round($scale*$aw)); 4107 if ($char != 0 && $char != 65535) { 4108 $w = intval(round($scale * $aw)); 4109 if ($w == 0) { 4110 $w = 65535; 4111 } 4112 if ($char < 196608) { 4113 $this->charWidths[$char * 2] = chr($w >> 8); 4114 $this->charWidths[$char * 2 + 1] = chr($w & 0xFF); 4115 $nCharWidths++; 4116 } 4117 } 4118 } 4119 } 4120 } 4121 $data = $this->get_chunk(($start + $numberOfHMetrics * 4), ($numGlyphs * 2)); 4122 $arr = unpack("n*", $data); 4123 $diff = $numGlyphs - $numberOfHMetrics; 4124 $w = intval(round($scale * $aw)); 4125 if ($w == 0) { 4126 $w = 65535; 4127 } 4128 for ($pos = 0; $pos < $diff; $pos++) { 4129 $glyph = $pos + $numberOfHMetrics; 4130 if (isset($glyphToChar[$glyph])) { 4131 foreach ($glyphToChar[$glyph] as $char) { 4132 if ($char != 0 && $char != 65535) { 4133 if ($char < 196608) { 4134 $this->charWidths[$char * 2] = chr($w >> 8); 4135 $this->charWidths[$char * 2 + 1] = chr($w & 0xFF); 4136 $nCharWidths++; 4137 } 4138 } 4139 } 4140 } 4141 } 4142 // NB 65535 is a set width of 0 4143 // First bytes define number of chars in font 4144 $this->charWidths[0] = chr($nCharWidths >> 8); 4145 $this->charWidths[1] = chr($nCharWidths & 0xFF); 4146 } 4147 4148 function getHMetric($numberOfHMetrics, $gid) 4149 { 4150 $start = $this->seek_table("hmtx"); 4151 if ($gid < $numberOfHMetrics) { 4152 $this->seek($start + ($gid * 4)); 4153 $hm = fread($this->fh, 4); 4154 } else { 4155 $this->seek($start + (($numberOfHMetrics - 1) * 4)); 4156 $hm = fread($this->fh, 2); 4157 $this->seek($start + ($numberOfHMetrics * 2) + ($gid * 2)); 4158 $hm .= fread($this->fh, 2); 4159 } 4160 4161 return $hm; 4162 } 4163 4164 function getLOCA($indexToLocFormat, $numGlyphs) 4165 { 4166 $start = $this->seek_table('loca'); 4167 $this->glyphPos = []; 4168 if ($indexToLocFormat == 0) { 4169 $data = $this->get_chunk($start, ($numGlyphs * 2) + 2); 4170 $arr = unpack("n*", $data); 4171 for ($n = 0; $n <= $numGlyphs; $n++) { 4172 $this->glyphPos[] = ($arr[$n + 1] * 2); 4173 } 4174 } else { 4175 if ($indexToLocFormat == 1) { 4176 $data = $this->get_chunk($start, ($numGlyphs * 4) + 4); 4177 $arr = unpack("N*", $data); 4178 for ($n = 0; $n <= $numGlyphs; $n++) { 4179 $this->glyphPos[] = ($arr[$n + 1]); 4180 } 4181 } else { 4182 throw new \Mpdf\Exception\FontException('Unknown location table format ' . $indexToLocFormat); 4183 } 4184 } 4185 } 4186 4187 // CMAP Format 4 4188 function getCMAP4($unicode_cmap_offset, &$glyphToChar, &$charToGlyph) 4189 { 4190 $this->maxUniChar = 0; 4191 $this->seek($unicode_cmap_offset + 2); 4192 $length = $this->read_ushort(); 4193 $limit = $unicode_cmap_offset + $length; 4194 $this->skip(2); 4195 4196 $segCount = $this->read_ushort() / 2; 4197 $this->skip(6); 4198 $endCount = []; 4199 for ($i = 0; $i < $segCount; $i++) { 4200 $endCount[] = $this->read_ushort(); 4201 } 4202 $this->skip(2); 4203 $startCount = []; 4204 for ($i = 0; $i < $segCount; $i++) { 4205 $startCount[] = $this->read_ushort(); 4206 } 4207 $idDelta = []; 4208 for ($i = 0; $i < $segCount; $i++) { 4209 $idDelta[] = $this->read_short(); 4210 } // ???? was unsigned short 4211 $idRangeOffset_start = $this->_pos; 4212 $idRangeOffset = []; 4213 for ($i = 0; $i < $segCount; $i++) { 4214 $idRangeOffset[] = $this->read_ushort(); 4215 } 4216 4217 for ($n = 0; $n < $segCount; $n++) { 4218 $endpoint = ($endCount[$n] + 1); 4219 for ($unichar = $startCount[$n]; $unichar < $endpoint; $unichar++) { 4220 if ($idRangeOffset[$n] == 0) { 4221 $glyph = ($unichar + $idDelta[$n]) & 0xFFFF; 4222 } else { 4223 $offset = ($unichar - $startCount[$n]) * 2 + $idRangeOffset[$n]; 4224 $offset = $idRangeOffset_start + 2 * $n + $offset; 4225 if ($offset >= $limit) { 4226 $glyph = 0; 4227 } else { 4228 $glyph = $this->get_ushort($offset); 4229 if ($glyph != 0) { 4230 $glyph = ($glyph + $idDelta[$n]) & 0xFFFF; 4231 } 4232 } 4233 } 4234 $charToGlyph[$unichar] = $glyph; 4235 if ($unichar < 196608) { 4236 $this->maxUniChar = max($unichar, $this->maxUniChar); 4237 } 4238 $glyphToChar[$glyph][] = $unichar; 4239 } 4240 } 4241 } 4242 4243 function formatUni($char) 4244 { 4245 $x = preg_replace('/^[0]*/', '', $char); 4246 $x = str_pad($x, 4, '0', STR_PAD_LEFT); 4247 $d = hexdec($x); 4248 if (($d > 57343 && $d < 63744) || ($d > 122879 && $d < 126977)) { 4249 $id = 'M'; 4250 } // E000 - F8FF, 1E000-1F000 4251 else { 4252 $id = 'U'; 4253 } 4254 4255 return $id . '+' . $x; 4256 } 4257 4258 function formatEntity($char, $allowjoining = false) 4259 { 4260 $char = preg_replace('/^[0]/', '', $char); 4261 $x = '&#x' . $char . ';'; 4262 if (strpos($this->GlyphClassMarks, $char) !== false) { 4263 if (!$allowjoining) { 4264 $x = '◌' . $x; 4265 } 4266 } 4267 4268 return $x; 4269 } 4270 4271 function formatUniArr($arr) 4272 { 4273 $s = []; 4274 foreach ($arr as $c) { 4275 $x = preg_replace('/^[0]*/', '', $c); 4276 $d = hexdec($x); 4277 if (($d > 57343 && $d < 63744) || ($d > 122879 && $d < 126977)) { 4278 $id = 'M'; 4279 } // E000 - F8FF, 1E000-1F000 4280 else { 4281 $id = 'U'; 4282 } 4283 $s[] = $id . '+' . str_pad($x, 4, '0', STR_PAD_LEFT); 4284 } 4285 4286 return implode(', ', $s); 4287 } 4288 4289 function formatEntityArr($arr) 4290 { 4291 $s = []; 4292 foreach ($arr as $c) { 4293 $c = preg_replace('/^[0]/', '', $c); 4294 $x = '&#x' . $c . ';'; 4295 if (strpos($this->GlyphClassMarks, $c) !== false) { 4296 $x = '◌' . $x; 4297 } 4298 $s[] = $x; 4299 } 4300 4301 return implode(' ', $s); // ZWNJ? ‍ 4302 } 4303 4304 function formatClassArr($arr) 4305 { 4306 $s = []; 4307 foreach ($arr as $c) { 4308 $x = preg_replace('/^[0]*/', '', $c); 4309 $d = hexdec($x); 4310 if (($d > 57343 && $d < 63744) || ($d > 122879 && $d < 126977)) { 4311 $id = 'M'; 4312 } // E000 - F8FF, 1E000-1F000 4313 else { 4314 $id = 'U'; 4315 } 4316 $s[] = $id . '+' . str_pad($x, 4, '0', STR_PAD_LEFT); 4317 } 4318 4319 return implode(', ', $s); 4320 } 4321 4322 function formatUniStr($str) 4323 { 4324 $s = []; 4325 $arr = explode('|', $str); 4326 foreach ($arr as $c) { 4327 $x = preg_replace('/^[0]*/', '', $c); 4328 $d = hexdec($x); 4329 if (($d > 57343 && $d < 63744) || ($d > 122879 && $d < 126977)) { 4330 $id = 'M'; 4331 } // E000 - F8FF, 1E000-1F000 4332 else { 4333 $id = 'U'; 4334 } 4335 $s[] = $id . '+' . str_pad($x, 4, '0', STR_PAD_LEFT); 4336 } 4337 4338 return implode(', ', $s); 4339 } 4340 4341 function formatEntityStr($str) 4342 { 4343 $s = []; 4344 $arr = explode('|', $str); 4345 foreach ($arr as $c) { 4346 $c = preg_replace('/^[0]/', '', $c); 4347 $x = '&#x' . $c . ';'; 4348 if (strpos($this->GlyphClassMarks, $c) !== false) { 4349 $x = '◌' . $x; 4350 } 4351 $s[] = $x; 4352 } 4353 4354 return implode(' ', $s); // ZWNJ? ‍ 4355 } 4356 4357 function formatEntityFirst($str) 4358 { 4359 $arr = explode('|', $str); 4360 $char = preg_replace('/^[0]/', '', $arr[0]); 4361 $x = '&#x' . $char . ';'; 4362 if (strpos($this->GlyphClassMarks, $char) !== false) { 4363 $x = '◌' . $x; 4364 } 4365 4366 return $x; 4367 } 4368 4369} 4370