PluginProbe
PDF & Print by BestWebSoft – WordPress Posts and Pages PDF Generator Plugin / trunk
PDF & Print by BestWebSoft – WordPress Posts and Pages PDF Generator Plugin vtrunk
trunk 1.5 1.6 1.7 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 All 71 releases
pdf-print / mpdf / classes / otl_dump.php

otl_dump.php in PDF & Print by BestWebSoft – WordPress Posts and Pages PDF Generator Plugin trunk, at mpdf/classes/otl_dump.php

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