1<?php
2
3namespace Mpdf;
4
5// Define the value used in the "head" table of a created TTF file
6// 0x74727565 "true" for Mac
7// 0x00010000 for Windows
8// Either seems to work for a font embedded in a PDF file
9// when read by Adobe Reader on a Windows PC(!)
10use Mpdf\Fonts\GlyphOperator;
11
12if (!defined('_TTF_MAC_HEADER')) {
13	define("_TTF_MAC_HEADER", false);
14}
15
16// Recalculate correct metadata/profiles when making subset fonts (not SIP/SMP)
17// e.g. xMin, xMax, maxNContours
18if (!defined('_RECALC_PROFILE')) {
19	define("_RECALC_PROFILE", false);
20}
21
22// mPDF 5.7.1
23if (!function_exists('Mpdf\unicode_hex')) {
24	function unicode_hex($unicode_dec)
25	{
26		return (sprintf("%05s", strtoupper(dechex($unicode_dec))));
27	}
28}
29
30class OtlDump
31{
32
33	var $GPOSFeatures; // mPDF 5.7.1
34
35	var $GPOSLookups;  // mPDF 5.7.1
36
37	var $GPOSScriptLang; // mPDF 5.7.1
38
39	var $ignoreStrings; // mPDF 5.7.1
40
41	var $MarkAttachmentType; // mPDF 5.7.1
42
43	var $MarkGlyphSets; // mPDF 7.5.1
44
45	var $GlyphClassMarks; // mPDF 5.7.1
46
47	var $GlyphClassLigatures; // mPDF 5.7.1
48
49	var $GlyphClassBases; // mPDF 5.7.1
50
51	var $GlyphClassComponents; // mPDF 5.7.1
52
53	var $GSUBScriptLang; // mPDF 5.7.1
54
55	var $rtlPUAstr; // mPDF 5.7.1
56
57	var $rtlPUAarr; // mPDF 5.7.1
58
59	var $fontkey; // mPDF 5.7.1
60
61	var $useOTL; // mPDF 5.7.1
62
63	var $panose;
64
65	var $maxUni;
66
67	var $sFamilyClass;
68
69	var $sFamilySubClass;
70
71	var $sipset;
72
73	var $smpset;
74
75	var $_pos;
76
77	var $numTables;
78
79	var $searchRange;
80
81	var $entrySelector;
82
83	var $rangeShift;
84
85	var $tables;
86
87	var $otables;
88
89	var $filename;
90
91	var $fh;
92
93	var $glyphPos;
94
95	var $charToGlyph;
96
97	var $ascent;
98
99	var $descent;
100
101	var $name;
102
103	var $familyName;
104
105	var $styleName;
106
107	var $fullName;
108
109	var $uniqueFontID;
110
111	var $unitsPerEm;
112
113	var $bbox;
114
115	var $capHeight;
116
117	var $stemV;
118
119	var $italicAngle;
120
121	var $flags;
122
123	var $underlinePosition;
124
125	var $underlineThickness;
126
127	var $charWidths;
128
129	var $defaultWidth;
130
131	var $maxStrLenRead;
132
133	var $numTTCFonts;
134
135	var $TTCFonts;
136
137	var $maxUniChar;
138
139	var $kerninfo;
140
141	var $mode;
142
143	var $glyphToChar;
144
145	var $fontRevision;
146
147	var $glyphdata;
148
149	var $glyphIDtoUn;
150
151	var $restrictedUse;
152
153	var $GSUBFeatures;
154
155	var $GSUBLookups;
156
157	var $glyphIDtoUni;
158
159	var $GSLuCoverage;
160
161	var $version;
162
163	private $mpdf;
164
165	public function __construct(Mpdf $mpdf)
166	{
167		$this->mpdf = $mpdf;
168		$this->maxStrLenRead = 200000; // Maximum size of glyf table to read in as string (otherwise reads each glyph from file)
169	}
170
171	function getMetrics($file, $fontkey, $TTCfontID = 0, $debug = false, $BMPonly = false, $kerninfo = false, $useOTL = 0, $mode = null)
172	{
173		// mPDF 5.7.1
174		$this->mode = $mode;
175		$this->useOTL = $useOTL; // mPDF 5.7.1
176		$this->fontkey = $fontkey; // mPDF 5.7.1
177		$this->filename = $file;
178		$this->fh = fopen($file, 'rb');
179
180		if (!$this->fh) {
181			throw new \Mpdf\Exception\FontException(sprintf('Unable to open file "%s"', $file));
182		}
183
184		$this->_pos = 0;
185		$this->charWidths = '';
186		$this->glyphPos = [];
187		$this->charToGlyph = [];
188		$this->tables = [];
189		$this->otables = [];
190		$this->kerninfo = [];
191		$this->ascent = 0;
192		$this->descent = 0;
193		$this->numTTCFonts = 0;
194		$this->TTCFonts = [];
195		$this->version = $version = $this->read_ulong();
196		$this->panose = [];
197
198		if ($version == 0x4F54544F) {
199			throw new \Mpdf\Exception\FontException("Postscript outlines are not supported");
200		}
201
202		if ($version == 0x74746366 && !$TTCfontID) {
203			throw new \Mpdf\Exception\FontException("TTCfontID for a TrueType Collection has to be defined in ttfontdata configuration key (" . $file . ")");
204		}
205
206		if (!in_array($version, [0x00010000, 0x74727565]) && !$TTCfontID) {
207			throw new \Mpdf\Exception\FontException("Not a TrueType font: version=" . $version);
208		}
209
210		if ($TTCfontID > 0) {
211			$this->version = $version = $this->read_ulong(); // TTC Header version now
212			if (!in_array($version, [0x00010000, 0x00020000])) {
213				throw new \Mpdf\Exception\FontException("Error parsing TrueType Collection: version=" . $version . " - " . $file);
214			}
215			$this->numTTCFonts = $this->read_ulong();
216			for ($i = 1; $i <= $this->numTTCFonts; $i++) {
217				$this->TTCFonts[$i]['offset'] = $this->read_ulong();
218			}
219			$this->seek($this->TTCFonts[$TTCfontID]['offset']);
220			$this->version = $version = $this->read_ulong(); // TTFont version again now
221		}
222		$this->readTableDirectory($debug);
223		$this->extractInfo($debug, $BMPonly, $kerninfo, $useOTL);
224		fclose($this->fh);
225	}
226
227	function readTableDirectory($debug = false)
228	{
229		$this->numTables = $this->read_ushort();
230		$this->searchRange = $this->read_ushort();
231		$this->entrySelector = $this->read_ushort();
232		$this->rangeShift = $this->read_ushort();
233		$this->tables = [];
234		for ($i = 0; $i < $this->numTables; $i++) {
235			$record = [];
236			$record['tag'] = $this->read_tag();
237			$record['checksum'] = [$this->read_ushort(), $this->read_ushort()];
238			$record['offset'] = $this->read_ulong();
239			$record['length'] = $this->read_ulong();
240			$this->tables[$record['tag']] = $record;
241		}
242		if ($debug) {
243			$this->checksumTables();
244		}
245	}
246
247	function checksumTables()
248	{
249		// Check the checksums for all tables
250		foreach ($this->tables as $t) {
251			if ($t['length'] > 0 && $t['length'] < $this->maxStrLenRead) { // 1.02
252				$table = $this->get_chunk($t['offset'], $t['length']);
253				$checksum = $this->calcChecksum($table);
254				if ($t['tag'] == 'head') {
255					$up = unpack('n*', substr($table, 8, 4));
256					$adjustment[0] = $up[1];
257					$adjustment[1] = $up[2];
258					$checksum = $this->sub32($checksum, $adjustment);
259				}
260				$xchecksum = $t['checksum'];
261				if ($xchecksum != $checksum) {
262					throw new \Mpdf\Exception\FontException(sprintf('TTF file "%s": invalid checksum %s table: %s (expected %s)', $this->filename, dechex($checksum[0]) . dechex($checksum[1]), $t['tag'], dechex($xchecksum[0]) . dechex($xchecksum[1])));
263				}
264			}
265		}
266	}
267
268	function sub32($x, $y)
269	{
270		$xlo = $x[1];
271		$xhi = $x[0];
272		$ylo = $y[1];
273		$yhi = $y[0];
274		if ($ylo > $xlo) {
275			$xlo += 1 << 16;
276			$yhi += 1;
277		}
278		$reslo = $xlo - $ylo;
279		if ($yhi > $xhi) {
280			$xhi += 1 << 16;
281		}
282		$reshi = $xhi - $yhi;
283		$reshi = $reshi & 0xFFFF;
284
285		return [$reshi, $reslo];
286	}
287
288	function calcChecksum($data)
289	{
290		if (strlen($data) % 4) {
291			$data .= str_repeat("\0", (4 - (strlen($data) % 4)));
292		}
293		$len = strlen($data);
294		$hi = 0x0000;
295		$lo = 0x0000;
296		for ($i = 0; $i < $len; $i += 4) {
297			$hi += (ord($data[$i]) << 8) + ord($data[$i + 1]);
298			$lo += (ord($data[$i + 2]) << 8) + ord($data[$i + 3]);
299			$hi += ($lo >> 16) & 0xFFFF;
300			$lo = $lo & 0xFFFF;
301		}
302
303		return [$hi, $lo];
304	}
305
306	function get_table_pos($tag)
307	{
308		$offset = isset($this->tables[$tag]['offset']) ? $this->tables[$tag]['offset'] : null;
309		$length = isset($this->tables[$tag]['length']) ? $this->tables[$tag]['length'] : null;
310
311		return [$offset, $length];
312	}
313
314	function seek($pos)
315	{
316		$this->_pos = $pos;
317		fseek($this->fh, $this->_pos);
318	}
319
320	function skip($delta)
321	{
322		$this->_pos = $this->_pos + $delta;
323		fseek($this->fh, $delta, SEEK_CUR);
324	}
325
326	function seek_table($tag, $offset_in_table = 0)
327	{
328		$tpos = $this->get_table_pos($tag);
329		$this->_pos = $tpos[0] + $offset_in_table;
330		fseek($this->fh, $this->_pos);
331
332		return $this->_pos;
333	}
334
335	function read_tag()
336	{
337		$this->_pos += 4;
338
339		return fread($this->fh, 4);
340	}
341
342	function read_short()
343	{
344		$this->_pos += 2;
345		$s = fread($this->fh, 2);
346		$a = (ord($s[0]) << 8) + ord($s[1]);
347		if ($a & (1 << 15)) {
348			$a = ($a - (1 << 16));
349		}
350
351		return $a;
352	}
353
354	function unpack_short($s)
355	{
356		$a = (ord($s[0]) << 8) + ord($s[1]);
357		if ($a & (1 << 15)) {
358			$a = ($a - (1 << 16));
359		}
360
361		return $a;
362	}
363
364	function read_ushort()
365	{
366		$this->_pos += 2;
367		$s = fread($this->fh, 2);
368
369		return (ord($s[0]) << 8) + ord($s[1]);
370	}
371
372	function read_ulong()
373	{
374		$this->_pos += 4;
375		$s = fread($this->fh, 4);
376
377		// if large uInt32 as an integer, PHP converts it to -ve
378		return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 	16777216  = 1<<24
379	}
380
381	function get_ushort($pos)
382	{
383		fseek($this->fh, $pos);
384		$s = fread($this->fh, 2);
385
386		return (ord($s[0]) << 8) + ord($s[1]);
387	}
388
389	function get_ulong($pos)
390	{
391		fseek($this->fh, $pos);
392		$s = fread($this->fh, 4);
393
394		// iF large uInt32 as an integer, PHP converts it to -ve
395		return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 	16777216  = 1<<24
396	}
397
398	function pack_short($val)
399	{
400		if ($val < 0) {
401			$val = abs($val);
402			$val = ~$val;
403			$val += 1;
404		}
405
406		return pack("n", $val);
407	}
408
409	function splice($stream, $offset, $value)
410	{
411		return substr($stream, 0, $offset) . $value . substr($stream, $offset + strlen($value));
412	}
413
414	function _set_ushort($stream, $offset, $value)
415	{
416		$up = pack("n", $value);
417
418		return $this->splice($stream, $offset, $up);
419	}
420
421	function _set_short($stream, $offset, $val)
422	{
423		if ($val < 0) {
424			$val = abs($val);
425			$val = ~$val;
426			$val += 1;
427		}
428		$up = pack("n", $val);
429
430		return $this->splice($stream, $offset, $up);
431	}
432
433	function get_chunk($pos, $length)
434	{
435		fseek($this->fh, $pos);
436		if ($length < 1) {
437			return '';
438		}
439
440		return (fread($this->fh, $length));
441	}
442
443	function get_table($tag)
444	{
445		list($pos, $length) = $this->get_table_pos($tag);
446		if ($length == 0) {
447			return '';
448		}
449		fseek($this->fh, $pos);
450
451		return (fread($this->fh, $length));
452	}
453
454	function add($tag, $data)
455	{
456		if ($tag == 'head') {
457			$data = $this->splice($data, 8, "\0\0\0\0");
458		}
459		$this->otables[$tag] = $data;
460	}
461
462	/////////////////////////////////////////////////////////////////////////////////////////
463	/////////////////////////////////////////////////////////////////////////////////////////
464
465	function extractInfo($debug = false, $BMPonly = false, $kerninfo = false, $useOTL = 0)
466	{
467		$this->panose = [];
468		$this->sFamilyClass = 0;
469		$this->sFamilySubClass = 0;
470		///////////////////////////////////
471		// name - Naming table
472		///////////////////////////////////
473		$name_offset = $this->seek_table("name");
474		$format = $this->read_ushort();
475		if ($format != 0 && $format != 1) {
476			throw new \Mpdf\Exception\FontException("Error loading font: Unknown name table format " . $format);
477		}
478		$numRecords = $this->read_ushort();
479		$string_data_offset = $name_offset + $this->read_ushort();
480		$names = [1 => '', 2 => '', 3 => '', 4 => '', 6 => ''];
481		$K = array_keys($names);
482		$nameCount = count($names);
483		for ($i = 0; $i < $numRecords; $i++) {
484			$platformId = $this->read_ushort();
485			$encodingId = $this->read_ushort();
486			$languageId = $this->read_ushort();
487			$nameId = $this->read_ushort();
488			$length = $this->read_ushort();
489			$offset = $this->read_ushort();
490			if (!in_array($nameId, $K)) {
491				continue;
492			}
493			$N = '';
494			if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name
495				$opos = $this->_pos;
496				$this->seek($string_data_offset + $offset);
497				if ($length % 2 != 0) {
498					throw new \Mpdf\Exception\FontException("Error loading font: PostScript name is UTF-16BE string of odd length");
499				}
500				$length /= 2;
501				$N = '';
502				while ($length > 0) {
503					$char = $this->read_ushort();
504					$N .= (chr($char));
505					$length -= 1;
506				}
507				$this->_pos = $opos;
508				$this->seek($opos);
509			} else {
510				if ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name
511					$opos = $this->_pos;
512					$N = $this->get_chunk($string_data_offset + $offset, $length);
513					$this->_pos = $opos;
514					$this->seek($opos);
515				}
516			}
517			if ($N && $names[$nameId] == '') {
518				$names[$nameId] = $N;
519				$nameCount -= 1;
520				if ($nameCount == 0) {
521					break;
522				}
523			}
524		}
525		if ($names[6]) {
526			$psName = $names[6];
527		} else {
528			if ($names[4]) {
529				$psName = preg_replace('/ /', '-', $names[4]);
530			} else {
531				if ($names[1]) {
532					$psName = preg_replace('/ /', '-', $names[1]);
533				} else {
534					$psName = '';
535				}
536			}
537		}
538		if (!$psName) {
539			throw new \Mpdf\Exception\FontException("Error loading font: Could not find PostScript font name: " . $this->filename);
540		}
541		if ($debug) {
542			for ($i = 0; $i < count($psName); $i++) {
543				$c = $psName[$i];
544				$oc = ord($c);
545				if ($oc > 126 || strpos(' [](){}<>/%', $c) !== false) {
546					throw new \Mpdf\Exception\FontException("psName=" . $psName . " contains invalid character " . $c . " ie U+" . ord(c));
547				}
548			}
549		}
550		$this->name = $psName;
551		if ($names[1]) {
552			$this->familyName = $names[1];
553		} else {
554			$this->familyName = $psName;
555		}
556		if ($names[2]) {
557			$this->styleName = $names[2];
558		} else {
559			$this->styleName = 'Regular';
560		}
561		if ($names[4]) {
562			$this->fullName = $names[4];
563		} else {
564			$this->fullName = $psName;
565		}
566		if ($names[3]) {
567			$this->uniqueFontID = $names[3];
568		} else {
569			$this->uniqueFontID = $psName;
570		}
571
572		if ($names[6]) {
573			$this->fullName = $names[6];
574		}
575
576		///////////////////////////////////
577		// head - Font header table
578		///////////////////////////////////
579		$this->seek_table("head");
580		if ($debug) {
581			$ver_maj = $this->read_ushort();
582			$ver_min = $this->read_ushort();
583			if ($ver_maj != 1) {
584				throw new \Mpdf\Exception\FontException('Error loading font: Unknown head table version ' . $ver_maj . '.' . $ver_min);
585			}
586			$this->fontRevision = $this->read_ushort() . $this->read_ushort();
587
588			$this->skip(4);
589			$magic = $this->read_ulong();
590			if ($magic != 0x5F0F3CF5) {
591				throw new \Mpdf\Exception\FontException('Error loading font: Invalid head table magic ' . $magic);
592			}
593			$this->skip(2);
594		} else {
595			$this->skip(18);
596		}
597		$this->unitsPerEm = $unitsPerEm = $this->read_ushort();
598		$scale = 1000 / $unitsPerEm;
599		$this->skip(16);
600		$xMin = $this->read_short();
601		$yMin = $this->read_short();
602		$xMax = $this->read_short();
603		$yMax = $this->read_short();
604		$this->bbox = [($xMin * $scale), ($yMin * $scale), ($xMax * $scale), ($yMax * $scale)];
605		$this->skip(3 * 2);
606		$indexToLocFormat = $this->read_ushort();
607		$glyphDataFormat = $this->read_ushort();
608		if ($glyphDataFormat != 0) {
609			throw new \Mpdf\Exception\FontException('Error loading font: Unknown glyph data format ' . $glyphDataFormat);
610		}
611
612		///////////////////////////////////
613		// hhea metrics table
614		///////////////////////////////////
615		// ttf2t1 seems to use this value rather than the one in OS/2 - so put in for compatibility
616		if (isset($this->tables["hhea"])) {
617			$this->seek_table("hhea");
618			$this->skip(4);
619			$hheaAscender = $this->read_short();
620			$hheaDescender = $this->read_short();
621			$this->ascent = ($hheaAscender * $scale);
622			$this->descent = ($hheaDescender * $scale);
623		}
624
625		///////////////////////////////////
626		// OS/2 - OS/2 and Windows metrics table
627		///////////////////////////////////
628		if (isset($this->tables["OS/2"])) {
629			$this->seek_table("OS/2");
630			$version = $this->read_ushort();
631			$this->skip(2);
632			$usWeightClass = $this->read_ushort();
633			$this->skip(2);
634			$fsType = $this->read_ushort();
635			if ($fsType == 0x0002 || ($fsType & 0x0300) != 0) {
636				global $overrideTTFFontRestriction;
637				if (!$overrideTTFFontRestriction) {
638					throw new \Mpdf\Exception\FontException('Font file ' . $this->filename . ' cannot be embedded due to copyright restrictions.');
639				}
640				$this->restrictedUse = true;
641			}
642			$this->skip(20);
643			$sF = $this->read_short();
644			$this->sFamilyClass = ($sF >> 8);
645			$this->sFamilySubClass = ($sF & 0xFF);
646			$this->_pos += 10;  //PANOSE = 10 byte length
647			$panose = fread($this->fh, 10);
648			$this->panose = [];
649			for ($p = 0; $p < strlen($panose); $p++) {
650				$this->panose[] = ord($panose[$p]);
651			}
652			$this->skip(26);
653			$sTypoAscender = $this->read_short();
654			$sTypoDescender = $this->read_short();
655			if (!$this->ascent) {
656				$this->ascent = ($sTypoAscender * $scale);
657			}
658			if (!$this->descent) {
659				$this->descent = ($sTypoDescender * $scale);
660			}
661			if ($version > 1) {
662				$this->skip(16);
663				$sCapHeight = $this->read_short();
664				$this->capHeight = ($sCapHeight * $scale);
665			} else {
666				$this->capHeight = $this->ascent;
667			}
668		} else {
669			$usWeightClass = 500;
670			if (!$this->ascent) {
671				$this->ascent = ($yMax * $scale);
672			}
673			if (!$this->descent) {
674				$this->descent = ($yMin * $scale);
675			}
676			$this->capHeight = $this->ascent;
677		}
678		$this->stemV = 50 + intval(pow(($usWeightClass / 65.0), 2));
679
680		///////////////////////////////////
681		// post - PostScript table
682		///////////////////////////////////
683		$this->seek_table("post");
684		if ($debug) {
685			$ver_maj = $this->read_ushort();
686			$ver_min = $this->read_ushort();
687			if ($ver_maj < 1 || $ver_maj > 4) {
688				throw new \Mpdf\Exception\FontException('Error loading font: Unknown post table version ' . $ver_maj);
689			}
690		} else {
691			$this->skip(4);
692		}
693		$this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0;
694		$this->underlinePosition = $this->read_short() * $scale;
695		$this->underlineThickness = $this->read_short() * $scale;
696		$isFixedPitch = $this->read_ulong();
697
698		$this->flags = 4;
699
700		if ($this->italicAngle != 0) {
701			$this->flags = $this->flags | 64;
702		}
703		if ($usWeightClass >= 600) {
704			$this->flags = $this->flags | 262144;
705		}
706		if ($isFixedPitch) {
707			$this->flags = $this->flags | 1;
708		}
709
710		///////////////////////////////////
711		// hhea - Horizontal header table
712		///////////////////////////////////
713		$this->seek_table("hhea");
714		if ($debug) {
715			$ver_maj = $this->read_ushort();
716			$ver_min = $this->read_ushort();
717			if ($ver_maj != 1) {
718				throw new \Mpdf\Exception\FontException(sprintf('Error loading font: Unknown hhea table version %s', $ver_maj));
719			}
720			$this->skip(28);
721		} else {
722			$this->skip(32);
723		}
724		$metricDataFormat = $this->read_ushort();
725		if ($metricDataFormat != 0) {
726			throw new \Mpdf\Exception\FontException('Error loading font: Unknown horizontal metric data format ' . $metricDataFormat);
727		}
728		$numberOfHMetrics = $this->read_ushort();
729		if ($numberOfHMetrics == 0) {
730			throw new \Mpdf\Exception\FontException('Error loading font: Number of horizontal metrics is 0');
731		}
732
733		///////////////////////////////////
734		// maxp - Maximum profile table
735		///////////////////////////////////
736		$this->seek_table("maxp");
737		if ($debug) {
738			$ver_maj = $this->read_ushort();
739			$ver_min = $this->read_ushort();
740			if ($ver_maj != 1) {
741				throw new \Mpdf\Exception\FontException('Error loading font: Unknown maxp table version ' . $ver_maj);
742			}
743		} else {
744			$this->skip(4);
745		}
746		$numGlyphs = $this->read_ushort();
747
748		///////////////////////////////////
749		// cmap - Character to glyph index mapping table
750		///////////////////////////////////
751		$cmap_offset = $this->seek_table("cmap");
752		$this->skip(2);
753		$cmapTableCount = $this->read_ushort();
754		$unicode_cmap_offset = 0;
755		for ($i = 0; $i < $cmapTableCount; $i++) {
756			$platformID = $this->read_ushort();
757			$encodingID = $this->read_ushort();
758			$offset = $this->read_ulong();
759			$save_pos = $this->_pos;
760			if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
761				$format = $this->get_ushort($cmap_offset + $offset);
762				if ($format == 4) {
763					if (!$unicode_cmap_offset) {
764						$unicode_cmap_offset = $cmap_offset + $offset;
765					}
766					if ($BMPonly) {
767						break;
768					}
769				}
770			} // Microsoft, Unicode Format 12 table HKCS
771			else {
772				if ((($platformID == 3 && $encodingID == 10) || $platformID == 0) && !$BMPonly) {
773					$format = $this->get_ushort($cmap_offset + $offset);
774					if ($format == 12) {
775						$unicode_cmap_offset = $cmap_offset + $offset;
776						break;
777					}
778				}
779			}
780			$this->seek($save_pos);
781		}
782
783		if (!$unicode_cmap_offset) {
784			throw new \Mpdf\Exception\FontException('Font (' . $this->filename . ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
785		}
786
787		$sipset = false;
788		$smpset = false;
789
790		// mPDF 5.7.1
791		$this->GSUBScriptLang = [];
792		$this->rtlPUAstr = '';
793		$this->rtlPUAarr = [];
794		$this->GSUBFeatures = [];
795		$this->GSUBLookups = [];
796		$this->GPOSScriptLang = [];
797		$this->GPOSFeatures = [];
798		$this->GPOSLookups = [];
799		$this->glyphIDtoUni = '';
800
801		// Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
802		if ($format == 12 && !$BMPonly) {
803			$this->maxUniChar = 0;
804			$this->seek($unicode_cmap_offset + 4);
805			$length = $this->read_ulong();
806			$limit = $unicode_cmap_offset + $length;
807			$this->skip(4);
808
809			$nGroups = $this->read_ulong();
810
811			$glyphToChar = [];
812			$charToGlyph = [];
813			for ($i = 0; $i < $nGroups; $i++) {
814				$startCharCode = $this->read_ulong();
815				$endCharCode = $this->read_ulong();
816				$startGlyphCode = $this->read_ulong();
817				if ($endCharCode > 0x20000 && $endCharCode < 0x2FFFF) {
818					$sipset = true;
819				} else {
820					if ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) {
821						$smpset = true;
822					}
823				}
824				$offset = 0;
825				for ($unichar = $startCharCode; $unichar <= $endCharCode; $unichar++) {
826					$glyph = $startGlyphCode + $offset;
827					$offset++;
828					if ($unichar < 0x30000) {
829						$charToGlyph[$unichar] = $glyph;
830						$this->maxUniChar = max($unichar, $this->maxUniChar);
831						$glyphToChar[$glyph][] = $unichar;
832					}
833				}
834			}
835		} else {
836			$glyphToChar = [];
837			$charToGlyph = [];
838			$this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph);
839		}
840		$this->sipset = $sipset;
841		$this->smpset = $smpset;
842
843		///////////////////////////////////
844		// mPDF 5.7.1
845		// Map Unmapped glyphs - from $numGlyphs
846		if ($this->useOTL) {
847			$bctr = 0xE000;
848			for ($gid = 1; $gid < $numGlyphs; $gid++) {
849				if (!isset($glyphToChar[$gid])) {
850					while (isset($charToGlyph[$bctr])) {
851						$bctr++;
852					} // Avoid overwriting a glyph already mapped in PUA
853					if (($bctr > 0xF8FF) && ($bctr < 0x2CEB0)) {
854						if (!$BMPonly) {
855							$bctr = 0x2CEB0;  // Use unassigned area 0x2CEB0 to 0x2F7FF (space for 10,000 characters)
856							$this->sipset = $sipset = true; // forces subsetting; also ensure charwidths are saved
857							while (isset($charToGlyph[$bctr])) {
858								$bctr++;
859							}
860						} else {
861							throw new \Mpdf\Exception\FontException(sprintf('Font "%s" does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)', $this->filename));
862						}
863					}
864					$glyphToChar[$gid][] = $bctr;
865					$charToGlyph[$bctr] = $gid;
866					$this->maxUniChar = max($bctr, $this->maxUniChar);
867					$bctr++;
868				}
869			}
870		}
871		$this->glyphToChar = $glyphToChar;
872		$this->charToGlyph = $charToGlyph;
873		///////////////////////////////////
874		// mPDF 5.7.1	OpenType Layout tables
875		$this->GSUBScriptLang = [];
876		$this->rtlPUAstr = '';
877		$this->rtlPUAarr = [];
878		if ($useOTL) {
879			$this->_getGDEFtables();
880			list($this->GSUBScriptLang, $this->GSUBFeatures, $this->GSUBLookups, $this->rtlPUAstr, $this->rtlPUAarr) = $this->_getGSUBtables();
881			list($this->GPOSScriptLang, $this->GPOSFeatures, $this->GPOSLookups) = $this->_getGPOStables();
882			$this->glyphIDtoUni = str_pad('', 256 * 256 * 3, "\x00");
883			foreach ($glyphToChar as $gid => $arr) {
884				if (isset($glyphToChar[$gid][0])) {
885					$char = $glyphToChar[$gid][0];
886					if ($char != 0 && $char != 65535) {
887						$this->glyphIDtoUni[$gid * 3] = chr($char >> 16);
888						$this->glyphIDtoUni[$gid * 3 + 1] = chr(($char >> 8) & 0xFF);
889						$this->glyphIDtoUni[$gid * 3 + 2] = chr($char & 0xFF);
890					}
891				}
892			}
893		}
894		///////////////////////////////////
895		///////////////////////////////////
896		// hmtx - Horizontal metrics table
897		///////////////////////////////////
898		$this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
899
900		///////////////////////////////////
901		// kern - Kerning pair table
902		///////////////////////////////////
903		if ($kerninfo) {
904			// Recognises old form of Kerning table - as required by Windows - Format 0 only
905			$kern_offset = $this->seek_table("kern");
906			$version = $this->read_ushort();
907			$nTables = $this->read_ushort();
908			// subtable header
909			$sversion = $this->read_ushort();
910			$slength = $this->read_ushort();
911			$scoverage = $this->read_ushort();
912			$format = $scoverage >> 8;
913			if ($kern_offset && $version == 0 && $format == 0) {
914				// Format 0
915				$nPairs = $this->read_ushort();
916				$this->skip(6);
917				for ($i = 0; $i < $nPairs; $i++) {
918					$left = $this->read_ushort();
919					$right = $this->read_ushort();
920					$val = $this->read_short();
921					if (count($glyphToChar[$left]) == 1 && count($glyphToChar[$right]) == 1) {
922						if ($left != 32 && $right != 32) {
923							$this->kerninfo[$glyphToChar[$left][0]][$glyphToChar[$right][0]] = intval($val * $scale);
924						}
925					}
926				}
927			}
928		}
929	}
930
931	/////////////////////////////////////////////////////////////////////////////////////////
932	function _getGDEFtables()
933	{
934		///////////////////////////////////
935		// GDEF - Glyph Definition
936		///////////////////////////////////
937		// http://www.microsoft.com/typography/otspec/gdef.htm
938		if (isset($this->tables["GDEF"])) {
939			if ($this->mode == 'summary') {
940				$this->mpdf->WriteHTML('<h1>GDEF table</h1>');
941			}
942			$gdef_offset = $this->seek_table("GDEF");
943			// ULONG Version of the GDEF table-currently 0x00010000
944			$ver_maj = $this->read_ushort();
945			$ver_min = $this->read_ushort();
946			// Version 0x00010002 of GDEF header contains additional Offset to a list defining mark glyph set definitions (MarkGlyphSetDef)
947			$GlyphClassDef_offset = $this->read_ushort();
948			$AttachList_offset = $this->read_ushort();
949			$LigCaretList_offset = $this->read_ushort();
950			$MarkAttachClassDef_offset = $this->read_ushort();
951			if ($ver_min == 2) {
952				$MarkGlyphSetsDef_offset = $this->read_ushort();
953			}
954
955			// GlyphClassDef
956			$this->seek($gdef_offset + $GlyphClassDef_offset);
957			/*
958			  1	Base glyph (single character, spacing glyph)
959			  2	Ligature glyph (multiple character, spacing glyph)
960			  3	Mark glyph (non-spacing combining glyph)
961			  4	Component glyph (part of single character, spacing glyph)
962			 */
963			$GlyphByClass = $this->_getClassDefinitionTable();
964
965			if ($this->mode == 'summary') {
966				$this->mpdf->WriteHTML('<h2>Glyph classes</h2>');
967			}
968
969			if (isset($GlyphByClass[1]) && count($GlyphByClass[1]) > 0) {
970				$this->GlyphClassBases = $this->formatClassArr($GlyphByClass[1]);
971				if ($this->mode == 'summary') {
972					$this->mpdf->WriteHTML('<h3>Glyph class 1</h3>');
973					$this->mpdf->WriteHTML('<h5>Base glyph (single character, spacing glyph)</h5>');
974					$html = '';
975					$html .= '<div class="glyphs">';
976					foreach ($GlyphByClass[1] as $g) {
977						$html .= '&#x' . $g . '; ';
978					}
979					$html .= '</div>';
980					$this->mpdf->WriteHTML($html);
981				}
982			} else {
983				$this->GlyphClassBases = '';
984			}
985			if (isset($GlyphByClass[2]) && count($GlyphByClass[2]) > 0) {
986				$this->GlyphClassLigatures = $this->formatClassArr($GlyphByClass[2]);
987				if ($this->mode == 'summary') {
988					$this->mpdf->WriteHTML('<h3>Glyph class 2</h3>');
989					$this->mpdf->WriteHTML('<h5>Ligature glyph (multiple character, spacing glyph)</h5>');
990					$html = '';
991					$html .= '<div class="glyphs">';
992					foreach ($GlyphByClass[2] as $g) {
993						$html .= '&#x' . $g . '; ';
994					}
995					$html .= '</div>';
996					$this->mpdf->WriteHTML($html);
997				}
998			} else {
999				$this->GlyphClassLigatures = '';
1000			}
1001			if (isset($GlyphByClass[3]) && count($GlyphByClass[3]) > 0) {
1002				$this->GlyphClassMarks = $this->formatClassArr($GlyphByClass[3]);
1003				if ($this->mode == 'summary') {
1004					$this->mpdf->WriteHTML('<h3>Glyph class 3</h3>');
1005					$this->mpdf->WriteHTML('<h5>Mark glyph (non-spacing combining glyph)</h5>');
1006					$html = '';
1007					$html .= '<div class="glyphs">';
1008					foreach ($GlyphByClass[3] as $g) {
1009						$html .= '&#x25cc;&#x' . $g . '; ';
1010					}
1011					$html .= '</div>';
1012					$this->mpdf->WriteHTML($html);
1013				}
1014			} else {
1015				$this->GlyphClassMarks = '';
1016			}
1017			if (isset($GlyphByClass[4]) && count($GlyphByClass[4]) > 0) {
1018				$this->GlyphClassComponents = $this->formatClassArr($GlyphByClass[4]);
1019				if ($this->mode == 'summary') {
1020					$this->mpdf->WriteHTML('<h3>Glyph class 4</h3>');
1021					$this->mpdf->WriteHTML('<h5>Component glyph (part of single character, spacing glyph)</h5>');
1022					$html = '';
1023					$html .= '<div class="glyphs">';
1024					foreach ($GlyphByClass[4] as $g) {
1025						$html .= '&#x' . $g . '; ';
1026					}
1027					$html .= '</div>';
1028					$this->mpdf->WriteHTML($html);
1029				}
1030			} else {
1031				$this->GlyphClassComponents = '';
1032			}
1033
1034			$Marks = $GlyphByClass[3]; // to use for MarkAttachmentType
1035
1036			/* Required for GPOS
1037			  // Attachment List
1038			  if ($AttachList_offset) {
1039			  $this->seek($gdef_offset+$AttachList_offset );
1040			  }
1041			  The Attachment Point List table (AttachmentList) identifies all the attachment points defined in the GPOS table and their associated glyphs so a client can quickly access coordinates for each glyph's attachment points. As a result, the client can cache coordinates for attachment points along with glyph bitmaps and avoid recalculating the attachment points each time it displays a glyph. Without this table, processing speed would be slower because the client would have to decode the GPOS lookups that define attachment points and compile the points in a list.
1042
1043			  The Attachment List table (AttachList) may be used to cache attachment point coordinates along with glyph bitmaps.
1044
1045			  The table consists of an offset to a Coverage table (Coverage) listing all glyphs that define attachment points in the GPOS table, a count of the glyphs with attachment points (GlyphCount), and an array of offsets to AttachPoint tables (AttachPoint). The array lists the AttachPoint tables, one for each glyph in the Coverage table, in the same order as the Coverage Index.
1046			  AttachList table
1047			  Type 	Name 	Description
1048			  Offset 	Coverage 	Offset to Coverage table - from beginning of AttachList table
1049			  uint16 	GlyphCount 	Number of glyphs with attachment points
1050			  Offset 	AttachPoint[GlyphCount] 	Array of offsets to AttachPoint tables-from beginning of AttachList table-in Coverage Index order
1051
1052			  An AttachPoint table consists of a count of the attachment points on a single glyph (PointCount) and an array of contour indices of those points (PointIndex), listed in increasing numerical order.
1053
1054			  AttachPoint table
1055			  Type 	Name 	Description
1056			  uint16 	PointCount 	Number of attachment points on this glyph
1057			  uint16 	PointIndex[PointCount] 	Array of contour point indices -in increasing numerical order
1058
1059			  See Example 3 - http://www.microsoft.com/typography/otspec/gdef.htm
1060			 */
1061
1062			// Ligature Caret List
1063			// The Ligature Caret List table (LigCaretList) defines caret positions for all the ligatures in a font.
1064			// Not required for mDPF
1065			// MarkAttachmentType
1066			if ($MarkAttachClassDef_offset) {
1067				if ($this->mode == 'summary') {
1068					$this->mpdf->WriteHTML('<h1>Mark Attachment Types</h1>');
1069				}
1070				$this->seek($gdef_offset + $MarkAttachClassDef_offset);
1071				$MarkAttachmentTypes = $this->_getClassDefinitionTable();
1072				foreach ($MarkAttachmentTypes as $class => $glyphs) {
1073					if (is_array($Marks) && count($Marks)) {
1074						$mat = array_diff($Marks, $MarkAttachmentTypes[$class]);
1075						sort($mat, SORT_STRING);
1076					} else {
1077						$mat = [];
1078					}
1079
1080					$this->MarkAttachmentType[$class] = $this->formatClassArr($mat);
1081
1082					if ($this->mode == 'summary') {
1083						$this->mpdf->WriteHTML('<h3>Mark Attachment Type: ' . $class . '</h3>');
1084						$html = '';
1085						$html .= '<div class="glyphs">';
1086						foreach ($glyphs as $g) {
1087							$html .= '&#x25cc;&#x' . $g . '; ';
1088						}
1089						$html .= '</div>';
1090						$this->mpdf->WriteHTML($html);
1091					}
1092				}
1093			} else {
1094				$this->MarkAttachmentType = [];
1095			}
1096
1097			// MarkGlyphSets only in Version 0x00010002 of GDEF
1098			if ($ver_min == 2 && $MarkGlyphSetsDef_offset) {
1099				if ($this->mode == 'summary') {
1100					$this->mpdf->WriteHTML('<h1>Mark Glyph Sets</h1>');
1101				}
1102				$this->seek($gdef_offset + $MarkGlyphSetsDef_offset);
1103				$MarkSetTableFormat = $this->read_ushort();
1104				$MarkSetCount = $this->read_ushort();
1105				$MarkSetOffset = [];
1106				for ($i = 0; $i < $MarkSetCount; $i++) {
1107					$MarkSetOffset[] = $this->read_ulong();
1108				}
1109				for ($i = 0; $i < $MarkSetCount; $i++) {
1110					$this->seek($MarkSetOffset[$i]);
1111					$glyphs = $this->_getCoverage();
1112					$this->MarkGlyphSets[$i] = $this->formatClassArr($glyphs);
1113					if ($this->mode == 'summary') {
1114						$this->mpdf->WriteHTML('<h3>Mark Glyph Set class: ' . $i . '</h3>');
1115						$html = '';
1116						$html .= '<div class="glyphs">';
1117						foreach ($glyphs as $g) {
1118							$html .= '&#x25cc;&#x' . $g . '; ';
1119						}
1120						$html .= '</div>';
1121						$this->mpdf->WriteHTML($html);
1122					}
1123				}
1124			} else {
1125				$this->MarkGlyphSets = [];
1126			}
1127		} else {
1128			$this->mpdf->WriteHTML('<div>GDEF table not defined</div>');
1129		}
1130
1131//echo $this->GlyphClassMarks ; exit;
1132//print_r($GlyphClass); exit;
1133//print_r($GlyphByClass); exit;
1134	}
1135
1136	function _getClassDefinitionTable($offset = 0)
1137	{
1138
1139		if ($offset > 0) {
1140			$this->seek($offset);
1141		}
1142
1143		// NB Any glyph not included in the range of covered GlyphIDs automatically belongs to Class 0. This is not returned by this function
1144		$ClassFormat = $this->read_ushort();
1145		$GlyphByClass = [];
1146		if ($ClassFormat == 1) {
1147			$StartGlyph = $this->read_ushort();
1148			$GlyphCount = $this->read_ushort();
1149			for ($i = 0; $i < $GlyphCount; $i++) {
1150				$gid = $StartGlyph + $i;
1151				$class = $this->read_ushort();
1152				$GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]);
1153			}
1154		} else {
1155			if ($ClassFormat == 2) {
1156				$tableCount = $this->read_ushort();
1157				for ($i = 0; $i < $tableCount; $i++) {
1158					$startGlyphID = $this->read_ushort();
1159					$endGlyphID = $this->read_ushort();
1160					$class = $this->read_ushort();
1161					for ($gid = $startGlyphID; $gid <= $endGlyphID; $gid++) {
1162						$GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]);
1163					}
1164				}
1165			}
1166		}
1167		ksort($GlyphByClass);
1168
1169		return $GlyphByClass;
1170	}
1171
1172	function _getGSUBtables()
1173	{
1174		///////////////////////////////////
1175		// GSUB - Glyph Substitution
1176		///////////////////////////////////
1177		if (isset($this->tables["GSUB"])) {
1178			$this->mpdf->WriteHTML('<h1>GSUB Tables</h1>');
1179			$ffeats = [];
1180			$gsub_offset = $this->seek_table("GSUB");
1181			$this->skip(4);
1182			$ScriptList_offset = $gsub_offset + $this->read_ushort();
1183			$FeatureList_offset = $gsub_offset + $this->read_ushort();
1184			$LookupList_offset = $gsub_offset + $this->read_ushort();
1185
1186			// ScriptList
1187			$this->seek($ScriptList_offset);
1188			$ScriptCount = $this->read_ushort();
1189			for ($i = 0; $i < $ScriptCount; $i++) {
1190				$ScriptTag = $this->read_tag(); // = "beng", "deva" etc.
1191				$ScriptTableOffset = $this->read_ushort();
1192				$ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset;
1193			}
1194
1195			// Script Table
1196			foreach ($ffeats as $t => $o) {
1197				$ls = [];
1198				$this->seek($o);
1199				$DefLangSys_offset = $this->read_ushort();
1200				if ($DefLangSys_offset > 0) {
1201					$ls['DFLT'] = $DefLangSys_offset + $o;
1202				}
1203				$LangSysCount = $this->read_ushort();
1204				for ($i = 0; $i < $LangSysCount; $i++) {
1205					$LangTag = $this->read_tag(); // =
1206					$LangTableOffset = $this->read_ushort();
1207					$ls[$LangTag] = $o + $LangTableOffset;
1208				}
1209				$ffeats[$t] = $ls;
1210			}
1211//print_r($ffeats); exit;
1212			// Get FeatureIndexList
1213			// LangSys Table - from first listed langsys
1214			foreach ($ffeats as $st => $scripts) {
1215				foreach ($scripts as $t => $o) {
1216					$FeatureIndex = [];
1217					$langsystable_offset = $o;
1218					$this->seek($langsystable_offset);
1219					$LookUpOrder = $this->read_ushort(); //==NULL
1220					$ReqFeatureIndex = $this->read_ushort();
1221					if ($ReqFeatureIndex != 0xFFFF) {
1222						$FeatureIndex[] = $ReqFeatureIndex;
1223					}
1224					$FeatureCount = $this->read_ushort();
1225					for ($i = 0; $i < $FeatureCount; $i++) {
1226						$FeatureIndex[] = $this->read_ushort(); // = index of feature
1227					}
1228					$ffeats[$st][$t] = $FeatureIndex;
1229				}
1230			}
1231//print_r($ffeats); exit;
1232			// Feauture List => LookupListIndex es
1233			$this->seek($FeatureList_offset);
1234			$FeatureCount = $this->read_ushort();
1235			$Feature = [];
1236			for ($i = 0; $i < $FeatureCount; $i++) {
1237				$Feature[$i] = ['tag' => $this->read_tag()];
1238				$Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort();
1239			}
1240			for ($i = 0; $i < $FeatureCount; $i++) {
1241				$this->seek($Feature[$i]['offset']);
1242				$this->read_ushort(); // null
1243				$Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort();
1244				$Feature[$i]['LookupListIndex'] = [];
1245				for ($c = 0; $c < $Lookupcount; $c++) {
1246					$Feature[$i]['LookupListIndex'][] = $this->read_ushort();
1247				}
1248			}
1249
1250			foreach ($ffeats as $st => $scripts) {
1251				foreach ($scripts as $t => $o) {
1252					$FeatureIndex = $ffeats[$st][$t];
1253					foreach ($FeatureIndex as $k => $fi) {
1254						$ffeats[$st][$t][$k] = $Feature[$fi];
1255					}
1256				}
1257			}
1258			//=====================================================================================
1259			$gsub = [];
1260			$GSUBScriptLang = [];
1261			foreach ($ffeats as $st => $scripts) {
1262				foreach ($scripts as $t => $langsys) {
1263					$lg = [];
1264					foreach ($langsys as $ft) {
1265						$lg[$ft['LookupListIndex'][0]] = $ft;
1266					}
1267					// list of Lookups in order they need to be run i.e. order listed in Lookup table
1268					ksort($lg);
1269					foreach ($lg as $ft) {
1270						$gsub[$st][$t][$ft['tag']] = $ft['LookupListIndex'];
1271					}
1272					if (!isset($GSUBScriptLang[$st])) {
1273						$GSUBScriptLang[$st] = '';
1274					}
1275					$GSUBScriptLang[$st] .= $t . ' ';
1276				}
1277			}
1278
1279//print_r($gsub); exit;
1280
1281			if ($this->mode == 'summary') {
1282				$this->mpdf->WriteHTML('<h3>GSUB Scripts &amp; Languages</h3>');
1283				$this->mpdf->WriteHTML('<div class="glyphs">');
1284				$html = '';
1285				if (count($gsub)) {
1286					foreach ($gsub as $st => $g) {
1287						$html .= '<h5>' . $st . '</h5>';
1288						foreach ($g as $l => $t) {
1289							$html .= '<div><a href="font_dump_OTL.php?script=' . $st . '&lang=' . $l . '">' . $l . '</a></b>: ';
1290							foreach ($t as $tag => $o) {
1291								$html .= $tag . ' ';
1292							}
1293							$html .= '</div>';
1294						}
1295					}
1296				} else {
1297					$html .= '<div>No entries in GSUB table.</div>';
1298				}
1299				$this->mpdf->WriteHTML($html);
1300				$this->mpdf->WriteHTML('</div>');
1301
1302				return 0;
1303			}
1304
1305			//=====================================================================================
1306			// Get metadata and offsets for whole Lookup List table
1307			$this->seek($LookupList_offset);
1308			$LookupCount = $this->read_ushort();
1309			$GSLookup = [];
1310			$Offsets = [];
1311			$SubtableCount = [];
1312			for ($i = 0; $i < $LookupCount; $i++) {
1313				$Offsets[$i] = $LookupList_offset + $this->read_ushort();
1314			}
1315			for ($i = 0; $i < $LookupCount; $i++) {
1316				$this->seek($Offsets[$i]);
1317				$GSLookup[$i]['Type'] = $this->read_ushort();
1318				$GSLookup[$i]['Flag'] = $flag = $this->read_ushort();
1319				$GSLookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort();
1320				for ($c = 0; $c < $SubtableCount[$i]; $c++) {
1321					$GSLookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort();
1322				}
1323				// MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure
1324				if (($flag & 0x0010) == 0x0010) {
1325					$GSLookup[$i]['MarkFilteringSet'] = $this->read_ushort();
1326				}
1327				// else { $GSLookup[$i]['MarkFilteringSet'] = ''; }
1328				// Lookup Type 7: Extension
1329				if ($GSLookup[$i]['Type'] == 7) {
1330					// Overwrites new offset (32-bit) for each subtable, and a new lookup Type
1331					for ($c = 0; $c < $SubtableCount[$i]; $c++) {
1332						$this->seek($GSLookup[$i]['Subtables'][$c]);
1333						$ExtensionPosFormat = $this->read_ushort();
1334						$type = $this->read_ushort();
1335						$GSLookup[$i]['Subtables'][$c] = $GSLookup[$i]['Subtables'][$c] + $this->read_ulong();
1336					}
1337					$GSLookup[$i]['Type'] = $type;
1338				}
1339			}
1340
1341//print_r($GSLookup); exit;
1342			//=====================================================================================
1343			// Process Whole LookupList - Get LuCoverage = Lookup coverage just for first glyph
1344			$this->GSLuCoverage = [];
1345			for ($i = 0; $i < $LookupCount; $i++) {
1346				for ($c = 0; $c < $GSLookup[$i]['SubtableCount']; $c++) {
1347					$this->seek($GSLookup[$i]['Subtables'][$c]);
1348					$PosFormat = $this->read_ushort();
1349
1350					if ($GSLookup[$i]['Type'] == 5 && $PosFormat == 3) {
1351						$this->skip(4);
1352					} else {
1353						if ($GSLookup[$i]['Type'] == 6 && $PosFormat == 3) {
1354							$BacktrackGlyphCount = $this->read_ushort();
1355							$this->skip(2 * $BacktrackGlyphCount + 2);
1356						}
1357					}
1358					// NB Coverage only looks at glyphs for position 1 (i.e. 5.3 and 6.3)	// NEEDS TO READ ALL ********************
1359					$Coverage = $GSLookup[$i]['Subtables'][$c] + $this->read_ushort();
1360					$this->seek($Coverage);
1361					$glyphs = $this->_getCoverage();
1362					$this->GSLuCoverage[$i][$c] = implode('|', $glyphs);
1363				}
1364			}
1365
1366// $this->GSLuCoverage and $GSLookup
1367			//=====================================================================================
1368			$s = '<?php
1369$GSLuCoverage = ' . var_export($this->GSLuCoverage, true) . ';
1370?>';
1371
1372			//=====================================================================================
1373			$s = '<?php
1374$GlyphClassBases = \'' . $this->GlyphClassBases . '\';
1375$GlyphClassMarks = \'' . $this->GlyphClassMarks . '\';
1376$GlyphClassLigatures = \'' . $this->GlyphClassLigatures . '\';
1377$GlyphClassComponents = \'' . $this->GlyphClassComponents . '\';
1378$MarkGlyphSets = ' . var_export($this->MarkGlyphSets, true) . ';
1379$MarkAttachmentType = ' . var_export($this->MarkAttachmentType, true) . ';
1380?>';
1381
1382			//=====================================================================================
1383			//=====================================================================================
1384			//=====================================================================================
1385// Now repeats as original to get Substitution rules
1386			//=====================================================================================
1387			//=====================================================================================
1388			//=====================================================================================
1389			// Get metadata and offsets for whole Lookup List table
1390			$this->seek($LookupList_offset);
1391			$LookupCount = $this->read_ushort();
1392			$Lookup = [];
1393			for ($i = 0; $i < $LookupCount; $i++) {
1394				$Lookup[$i]['offset'] = $LookupList_offset + $this->read_ushort();
1395			}
1396			for ($i = 0; $i < $LookupCount; $i++) {
1397				$this->seek($Lookup[$i]['offset']);
1398				$Lookup[$i]['Type'] = $this->read_ushort();
1399				$Lookup[$i]['Flag'] = $flag = $this->read_ushort();
1400				$Lookup[$i]['SubtableCount'] = $this->read_ushort();
1401				for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
1402					$Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['offset'] + $this->read_ushort();
1403				}
1404				// MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure
1405				if (($flag & 0x0010) == 0x0010) {
1406					$Lookup[$i]['MarkFilteringSet'] = $this->read_ushort();
1407				} else {
1408					$Lookup[$i]['MarkFilteringSet'] = '';
1409				}
1410
1411				// Lookup Type 7: Extension
1412				if ($Lookup[$i]['Type'] == 7) {
1413					// Overwrites new offset (32-bit) for each subtable, and a new lookup Type
1414					for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
1415						$this->seek($Lookup[$i]['Subtable'][$c]['Offset']);
1416						$ExtensionPosFormat = $this->read_ushort();
1417						$type = $this->read_ushort();
1418						$Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ulong();
1419					}
1420					$Lookup[$i]['Type'] = $type;
1421				}
1422			}
1423
1424//print_r($Lookup); exit;
1425			//=====================================================================================
1426			// Process (1) Whole LookupList
1427			for ($i = 0; $i < $LookupCount; $i++) {
1428				for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
1429					$this->seek($Lookup[$i]['Subtable'][$c]['Offset']);
1430					$SubstFormat = $this->read_ushort();
1431					$Lookup[$i]['Subtable'][$c]['Format'] = $SubstFormat;
1432
1433					/*
1434					  Lookup['Type'] Enumeration table for glyph substitution
1435					  Value	Type	Description
1436					  1	Single	Replace one glyph with one glyph
1437					  2	Multiple	Replace one glyph with more than one glyph
1438					  3	Alternate	Replace one glyph with one of many glyphs
1439					  4	Ligature	Replace multiple glyphs with one glyph
1440					  5	Context	Replace one or more glyphs in context
1441					  6	Chaining Context	Replace one or more glyphs in chained context
1442					  7	Extension Substitution	Extension mechanism for other substitutions (i.e. this excludes the Extension type substitution itself)
1443					  8	Reverse chaining context single 	Applied in reverse order, replace single glyph in chaining context
1444					 */
1445
1446					// LookupType 1: Single Substitution Subtable
1447					if ($Lookup[$i]['Type'] == 1) {
1448						$Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1449						if ($SubstFormat == 1) { // Calculated output glyph indices
1450							$Lookup[$i]['Subtable'][$c]['DeltaGlyphID'] = $this->read_short();
1451						} else {
1452							if ($SubstFormat == 2) { // Specified output glyph indices
1453								$GlyphCount = $this->read_ushort();
1454								for ($g = 0; $g < $GlyphCount; $g++) {
1455									$Lookup[$i]['Subtable'][$c]['Glyphs'][] = $this->read_ushort();
1456								}
1457							}
1458						}
1459					} // LookupType 2: Multiple Substitution Subtable
1460					else {
1461						if ($Lookup[$i]['Type'] == 2) {
1462							$Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1463							$Lookup[$i]['Subtable'][$c]['SequenceCount'] = $SequenceCount = $this->read_short();
1464							for ($s = 0; $s < $SequenceCount; $s++) {
1465								$Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
1466							}
1467							for ($s = 0; $s < $SequenceCount; $s++) {
1468								// Sequence Tables
1469								$this->seek($Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset']);
1470								$Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount'] = $this->read_short();
1471								for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount']; $g++) {
1472									$Lookup[$i]['Subtable'][$c]['Sequences'][$s]['SubstituteGlyphID'][] = $this->read_ushort();
1473								}
1474							}
1475						} // LookupType 3: Alternate Forms
1476						else {
1477							if ($Lookup[$i]['Type'] == 3) {
1478								$Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1479								$Lookup[$i]['Subtable'][$c]['AlternateSetCount'] = $AlternateSetCount = $this->read_short();
1480								for ($s = 0; $s < $AlternateSetCount; $s++) {
1481									$Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
1482								}
1483
1484								for ($s = 0; $s < $AlternateSetCount; $s++) {
1485									// AlternateSet Tables
1486									$this->seek($Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset']);
1487									$Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount'] = $this->read_short();
1488									for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount']; $g++) {
1489										$Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['SubstituteGlyphID'][] = $this->read_ushort();
1490									}
1491								}
1492							} // LookupType 4: Ligature Substitution Subtable
1493							else {
1494								if ($Lookup[$i]['Type'] == 4) {
1495									$Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1496									$Lookup[$i]['Subtable'][$c]['LigSetCount'] = $LigSetCount = $this->read_short();
1497									for ($s = 0; $s < $LigSetCount; $s++) {
1498										$Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
1499									}
1500									for ($s = 0; $s < $LigSetCount; $s++) {
1501										// LigatureSet Tables
1502										$this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset']);
1503										$Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount'] = $this->read_short();
1504										for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
1505											$Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g] = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] + $this->read_ushort();
1506										}
1507									}
1508									for ($s = 0; $s < $LigSetCount; $s++) {
1509										for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
1510											// Ligature tables
1511											$this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g]);
1512											$Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph'] = $this->read_ushort();
1513											$Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount'] = $this->read_ushort();
1514											for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) {
1515												$Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l] = $this->read_ushort();
1516											}
1517										}
1518									}
1519								} // LookupType 5: Contextual Substitution Subtable
1520								else {
1521									if ($Lookup[$i]['Type'] == 5) {
1522										// Format 1: Context Substitution
1523										if ($SubstFormat == 1) {
1524											$Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1525											$Lookup[$i]['Subtable'][$c]['SubRuleSetCount'] = $SubRuleSetCount = $this->read_short();
1526											for ($s = 0; $s < $SubRuleSetCount; $s++) {
1527												$Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
1528											}
1529											for ($s = 0; $s < $SubRuleSetCount; $s++) {
1530												// SubRuleSet Tables
1531												$this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset']);
1532												$Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount'] = $this->read_short();
1533												for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) {
1534													$Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] + $this->read_ushort();
1535												}
1536											}
1537											for ($s = 0; $s < $SubRuleSetCount; $s++) {
1538												// SubRule Tables
1539												for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) {
1540													// Ligature tables
1541													$this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g]);
1542
1543													$Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount'] = $this->read_ushort();
1544													$Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount'] = $this->read_ushort();
1545													// "Input"::[GlyphCount - 1]::Array of input GlyphIDs-start with second glyph
1546													for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount']; $l++) {
1547														$Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['Input'][$l] = $this->read_ushort();
1548													}
1549													// "SubstLookupRecord"::[SubstCount]::Array of SubstLookupRecords-in design order
1550													for ($l = 0; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount']; $l++) {
1551														$Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['SequenceIndex'] = $this->read_ushort();
1552														$Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['LookupListIndex'] = $this->read_ushort();
1553													}
1554												}
1555											}
1556										} // Format 2: Class-based Context Glyph Substitution
1557										else {
1558											if ($SubstFormat == 2) {
1559												$Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1560												$Lookup[$i]['Subtable'][$c]['ClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1561												$Lookup[$i]['Subtable'][$c]['SubClassSetCnt'] = $this->read_ushort();
1562												for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $b++) {
1563													$offset = $this->read_ushort();
1564													if ($offset == 0x0000) {
1565														$Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = 0;
1566													} else {
1567														$Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset;
1568													}
1569												}
1570											} else {
1571												throw new \Mpdf\Exception\FontException("GPOS Lookup Type " . $Lookup[$i]['Type'] . ", Format " . $SubstFormat . " not supported (ttfontsuni.php).");
1572											}
1573										}
1574									} // LookupType 6: Chaining Contextual Substitution Subtable
1575									else {
1576										if ($Lookup[$i]['Type'] == 6) {
1577											// Format 1: Simple Chaining Context Glyph Substitution  p255
1578											if ($SubstFormat == 1) {
1579												$Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1580												$Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount'] = $this->read_ushort();
1581												for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $b++) {
1582													$Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1583												}
1584											} // Format 2: Class-based Chaining Context Glyph Substitution  p257
1585											else {
1586												if ($SubstFormat == 2) {
1587													$Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1588													$Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1589													$Lookup[$i]['Subtable'][$c]['InputClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1590													$Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1591													$Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt'] = $this->read_ushort();
1592													for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $b++) {
1593														$offset = $this->read_ushort();
1594														if ($offset == 0x0000) {
1595															$Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $offset;
1596														} else {
1597															$Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset;
1598														}
1599													}
1600												} // Format 3: Coverage-based Chaining Context Glyph Substitution  p259
1601												else {
1602													if ($SubstFormat == 3) {
1603														$Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount'] = $this->read_ushort();
1604														for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) {
1605															$Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1606														}
1607														$Lookup[$i]['Subtable'][$c]['InputGlyphCount'] = $this->read_ushort();
1608														for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) {
1609															$Lookup[$i]['Subtable'][$c]['CoverageInput'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1610														}
1611														$Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount'] = $this->read_ushort();
1612														for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) {
1613															$Lookup[$i]['Subtable'][$c]['CoverageLookahead'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1614														}
1615														$Lookup[$i]['Subtable'][$c]['SubstCount'] = $this->read_ushort();
1616														for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) {
1617															$Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'] = $this->read_ushort();
1618															$Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'] = $this->read_ushort();
1619															/*
1620															  Substitution Lookup Record
1621															  All contextual substitution subtables specify the substitution data in a Substitution Lookup Record (SubstLookupRecord). Each record contains a SequenceIndex, which indicates the position where the substitution will occur in the glyph sequence. In addition, a LookupListIndex identifies the lookup to be applied at the glyph position specified by the SequenceIndex.
1622															 */
1623														}
1624													}
1625												}
1626											}
1627										} else {
1628											throw new \Mpdf\Exception\FontException("Lookup Type " . $Lookup[$i]['Type'] . " not supported.");
1629										}
1630									}
1631								}
1632							}
1633						}
1634					}
1635				}
1636			}
1637//print_r($Lookup); exit;
1638			//=====================================================================================
1639			// Process (2) Whole LookupList
1640			// Get Coverage tables and prepare preg_replace
1641			for ($i = 0; $i < $LookupCount; $i++) {
1642				for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
1643					$SubstFormat = $Lookup[$i]['Subtable'][$c]['Format'];
1644
1645					// LookupType 1: Single Substitution Subtable 1 => 1
1646					if ($Lookup[$i]['Type'] == 1) {
1647						$this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1648						$glyphs = $this->_getCoverage(false);
1649						for ($g = 0; $g < count($glyphs); $g++) {
1650							$replace = [];
1651							$substitute = [];
1652							$replace[] = unicode_hex($this->glyphToChar[$glyphs[$g]][0]);
1653							// Flag = Ignore
1654							if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
1655								continue;
1656							}
1657							if (isset($Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])) { // Format 1
1658								$substitute[] = unicode_hex($this->glyphToChar[($glyphs[$g] + $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])][0]);
1659							} else { // Format 2
1660								$substitute[] = unicode_hex($this->glyphToChar[($Lookup[$i]['Subtable'][$c]['Glyphs'][$g])][0]);
1661							}
1662							$Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute];
1663						}
1664					} // LookupType 2: Multiple Substitution Subtable 1 => n
1665					else {
1666						if ($Lookup[$i]['Type'] == 2) {
1667							$this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1668							$glyphs = $this->_getCoverage();
1669							for ($g = 0; $g < count($glyphs); $g++) {
1670								$replace = [];
1671								$substitute = [];
1672								$replace[] = $glyphs[$g];
1673								// Flag = Ignore
1674								if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
1675									continue;
1676								}
1677								if (!isset($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) || count($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) == 0) {
1678									continue;
1679								} // Illegal for GlyphCount to be 0; either error in font, or something has gone wrong - lets carry on for now!
1680								foreach ($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID'] as $sub) {
1681									$substitute[] = unicode_hex($this->glyphToChar[$sub][0]);
1682								}
1683								$Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute];
1684							}
1685						} // LookupType 3: Alternate Forms 1 => 1 (only first alternate form is used)
1686						else {
1687							if ($Lookup[$i]['Type'] == 3) {
1688								$this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1689								$glyphs = $this->_getCoverage();
1690								for ($g = 0; $g < count($glyphs); $g++) {
1691									$replace = [];
1692									$substitute = [];
1693									$replace[] = $glyphs[$g];
1694									// Flag = Ignore
1695									if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
1696										continue;
1697									}
1698
1699									for ($gl = 0; $gl < $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['GlyphCount']; $gl++) {
1700										$gid = $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['SubstituteGlyphID'][$gl];
1701										$substitute[] = unicode_hex($this->glyphToChar[$gid][0]);
1702									}
1703
1704									//$gid = $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['SubstituteGlyphID'][0];
1705									//$substitute[] = unicode_hex($this->glyphToChar[$gid][0]);
1706
1707									$Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute];
1708								}
1709								if ($i == 166) {
1710									print_r($Lookup[$i]['Subtable']);
1711									exit;
1712								}
1713							} // LookupType 4: Ligature Substitution Subtable n => 1
1714							else {
1715								if ($Lookup[$i]['Type'] == 4) {
1716									$this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1717									$glyphs = $this->_getCoverage();
1718									$LigSetCount = $Lookup[$i]['Subtable'][$c]['LigSetCount'];
1719									for ($s = 0; $s < $LigSetCount; $s++) {
1720										for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
1721											$replace = [];
1722											$substitute = [];
1723											$replace[] = $glyphs[$s];
1724											// Flag = Ignore
1725											if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
1726												continue;
1727											}
1728											for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) {
1729												$gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l];
1730												$rpl = unicode_hex($this->glyphToChar[$gid][0]);
1731												// Flag = Ignore
1732												if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $rpl, $Lookup[$i]['MarkFilteringSet'])) {
1733													continue 2;
1734												}
1735												$replace[] = $rpl;
1736											}
1737											$gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph'];
1738											$substitute[] = unicode_hex($this->glyphToChar[$gid][0]);
1739											$Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute, 'CompCount' => $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']];
1740										}
1741									}
1742								} // LookupType 5: Contextual Substitution Subtable
1743								else {
1744									if ($Lookup[$i]['Type'] == 5) {
1745										// Format 1: Context Substitution
1746										if ($SubstFormat == 1) {
1747											$this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1748											$Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
1749
1750											for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) {
1751												$SubRuleSet = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s];
1752												$Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph'] = $CoverageGlyphs[$s];
1753												for ($r = 0; $r < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $r++) {
1754													$GlyphCount = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['GlyphCount'];
1755													for ($g = 1; $g < $GlyphCount; $g++) {
1756														$glyphID = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['Input'][$g];
1757														$Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
1758													}
1759												}
1760											}
1761										} // Format 2: Class-based Context Glyph Substitution
1762										else {
1763											if ($SubstFormat == 2) {
1764												$this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1765												$Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
1766
1767												$InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['ClassDefOffset']);
1768												$Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses;
1769
1770												for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) {
1771													if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) {
1772														$this->seek($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s]);
1773														$Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt'] = $SubClassRuleCnt = $this->read_ushort();
1774														$SubClassRule = [];
1775														for ($b = 0; $b < $SubClassRuleCnt; $b++) {
1776															$SubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] + $this->read_ushort();
1777															$Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $SubClassRule[$b];
1778														}
1779													}
1780												}
1781
1782												for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) {
1783													$SubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt'];
1784													for ($b = 0; $b < $SubClassRuleCnt; $b++) {
1785														if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) {
1786															$this->seek($Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b]);
1787															$Rule = [];
1788															$Rule['InputGlyphCount'] = $this->read_ushort();
1789															$Rule['SubstCount'] = $this->read_ushort();
1790															for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) {
1791																$Rule['Input'][$r] = $this->read_ushort();
1792															}
1793															for ($r = 0; $r < $Rule['SubstCount']; $r++) {
1794																$Rule['SequenceIndex'][$r] = $this->read_ushort();
1795																$Rule['LookupListIndex'][$r] = $this->read_ushort();
1796															}
1797
1798															$Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $Rule;
1799														}
1800													}
1801												}
1802											} // Format 3: Coverage-based Context Glyph Substitution
1803											else {
1804												if ($SubstFormat == 3) {
1805													for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) {
1806														$this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]);
1807														$glyphs = $this->_getCoverage();
1808														$Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs);
1809													}
1810													throw new \Mpdf\Exception\FontException("Lookup Type 5, SubstFormat 3 not tested. Please report this with the name of font used - " . $this->fontkey);
1811												}
1812											}
1813										}
1814									} // LookupType 6: Chaining Contextual Substitution Subtable
1815									else {
1816										if ($Lookup[$i]['Type'] == 6) {
1817											// Format 1: Simple Chaining Context Glyph Substitution  p255
1818											if ($SubstFormat == 1) {
1819												$this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1820												$Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
1821
1822												$ChainSubRuleSetCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount'];
1823
1824												for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) {
1825													$this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s]);
1826													$ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount'] = $this->read_ushort();
1827													for ($r = 0; $r < $ChainSubRuleCnt; $r++) {
1828														$Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r] = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s] + $this->read_ushort();
1829													}
1830												}
1831												for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) {
1832													$ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount'];
1833													for ($r = 0; $r < $ChainSubRuleCnt; $r++) {
1834														// ChainSubRule
1835														$this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r]);
1836
1837														$BacktrackGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphCount'] = $this->read_ushort();
1838														for ($g = 0; $g < $BacktrackGlyphCount; $g++) {
1839															$glyphID = $this->read_ushort();
1840															$Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
1841														}
1842
1843														$InputGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphCount'] = $this->read_ushort();
1844														for ($g = 1; $g < $InputGlyphCount; $g++) {
1845															$glyphID = $this->read_ushort();
1846															$Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
1847														}
1848
1849														$LookaheadGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphCount'] = $this->read_ushort();
1850														for ($g = 0; $g < $LookaheadGlyphCount; $g++) {
1851															$glyphID = $this->read_ushort();
1852															$Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
1853														}
1854
1855														$SubstCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SubstCount'] = $this->read_ushort();
1856														for ($lu = 0; $lu < $SubstCount; $lu++) {
1857															$Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SequenceIndex'][$lu] = $this->read_ushort();
1858															$Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookupListIndex'][$lu] = $this->read_ushort();
1859														}
1860													}
1861												}
1862											} // Format 2: Class-based Chaining Context Glyph Substitution  p257
1863											else {
1864												if ($SubstFormat == 2) {
1865													$this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1866													$Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
1867
1868													$BacktrackClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset']);
1869													$Lookup[$i]['Subtable'][$c]['BacktrackClasses'] = $BacktrackClasses;
1870
1871													$InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['InputClassDefOffset']);
1872													$Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses;
1873
1874													$LookaheadClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset']);
1875													$Lookup[$i]['Subtable'][$c]['LookaheadClasses'] = $LookaheadClasses;
1876
1877													for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) {
1878														if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) {
1879															$this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s]);
1880															$Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'] = $ChainSubClassRuleCnt = $this->read_ushort();
1881															$ChainSubClassRule = [];
1882															for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) {
1883																$ChainSubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] + $this->read_ushort();
1884																$Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $ChainSubClassRule[$b];
1885															}
1886														}
1887													}
1888
1889													for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) {
1890														$ChainSubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'];
1891														for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) {
1892															if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) {
1893																$this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b]);
1894																$Rule = [];
1895																$Rule['BacktrackGlyphCount'] = $this->read_ushort();
1896																for ($r = 0; $r < $Rule['BacktrackGlyphCount']; $r++) {
1897																	$Rule['Backtrack'][$r] = $this->read_ushort();
1898																}
1899																$Rule['InputGlyphCount'] = $this->read_ushort();
1900																for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) {
1901																	$Rule['Input'][$r] = $this->read_ushort();
1902																}
1903																$Rule['LookaheadGlyphCount'] = $this->read_ushort();
1904																for ($r = 0; $r < $Rule['LookaheadGlyphCount']; $r++) {
1905																	$Rule['Lookahead'][$r] = $this->read_ushort();
1906																}
1907																$Rule['SubstCount'] = $this->read_ushort();
1908																for ($r = 0; $r < $Rule['SubstCount']; $r++) {
1909																	$Rule['SequenceIndex'][$r] = $this->read_ushort();
1910																	$Rule['LookupListIndex'][$r] = $this->read_ushort();
1911																}
1912
1913																$Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $Rule;
1914															}
1915														}
1916													}
1917												} // Format 3: Coverage-based Chaining Context Glyph Substitution  p259
1918												else {
1919													if ($SubstFormat == 3) {
1920														for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) {
1921															$this->seek($Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][$b]);
1922															$glyphs = $this->_getCoverage();
1923															$Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'][] = implode("|", $glyphs);
1924														}
1925														for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) {
1926															$this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]);
1927															$glyphs = $this->_getCoverage();
1928															$Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs);
1929															// Don't use above value as these are ordered numerically not as need to process
1930														}
1931														for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) {
1932															$this->seek($Lookup[$i]['Subtable'][$c]['CoverageLookahead'][$b]);
1933															$glyphs = $this->_getCoverage();
1934															$Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'][] = implode("|", $glyphs);
1935														}
1936													}
1937												}
1938											}
1939										}
1940									}
1941								}
1942							}
1943						}
1944					}
1945				}
1946			}
1947
1948			//=====================================================================================
1949			//=====================================================================================
1950			//=====================================================================================
1951
1952			$st = $this->mpdf->OTLscript;
1953			$t = $this->mpdf->OTLlang;
1954			$langsys = $gsub[$st][$t];
1955
1956			$lul = []; // array of LookupListIndexes
1957			$tags = []; // corresponding array of feature tags e.g. 'ccmp'
1958			foreach ($langsys as $tag => $ft) {
1959				foreach ($ft as $ll) {
1960					$lul[$ll] = $tag;
1961				}
1962			}
1963			ksort($lul); // Order the Lookups in the order they are in the GUSB table, regardless of Feature order
1964			$this->_getGSUBarray($Lookup, $lul, $st);
1965//print_r($lul); exit;
1966		}
1967
1968//print_r($Lookup); exit;
1969
1970		return [$GSUBScriptLang, $gsub, $GSLookup, $rtlPUAstr, $rtlPUAarr];
1971	}
1972
1973/////////////////////////////////////////////////////////////////////////////////////////
1974	// GSUB functions
1975	function _getGSUBarray(&$Lookup, &$lul, $scripttag, $level = 1, $coverage = '', $exB = '', $exL = '')
1976	{
1977		// Process (3) LookupList for specific Script-LangSys
1978		// Generate preg_replace
1979		$html = '';
1980		if ($level == 1) {
1981			$html .= '<bookmark level="0" content="GSUB features">';
1982		}
1983		foreach ($lul as $i => $tag) {
1984			$html .= '<div class="level' . $level . '">';
1985			$html .= '<h5 class="level' . $level . '">';
1986			if ($level == 1) {
1987				$html .= '<bookmark level="1" content="' . $tag . ' [#' . $i . ']">';
1988			}
1989			$html .= 'Lookup #' . $i . ' [tag: <span style="color:#000066;">' . $tag . '</span>]</h5>';
1990			$ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
1991			if ($ignore) {
1992				$html .= '<div class="ignore">Ignoring: ' . $ignore . '</div> ';
1993			}
1994
1995			$Type = $Lookup[$i]['Type'];
1996			$Flag = $Lookup[$i]['Flag'];
1997			if (($Flag & 0x0001) == 1) {
1998				$dir = 'RTL';
1999			} else {
2000				$dir = 'LTR';
2001			}
2002
2003			for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
2004				$html .= '<div class="subtable">Subtable #' . $c;
2005				if ($level == 1) {
2006					$html .= '<bookmark level="2" content="Subtable #' . $c . '">';
2007				}
2008				$html .= '</div>';
2009
2010				$SubstFormat = $Lookup[$i]['Subtable'][$c]['Format'];
2011
2012				// LookupType 1: Single Substitution Subtable
2013				if ($Lookup[$i]['Type'] == 1) {
2014					$html .= '<div class="lookuptype">LookupType 1: Single Substitution Subtable</div>';
2015					for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
2016						$inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
2017						$substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
2018						if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) {
2019							continue;
2020						}
2021						$html .= '<div class="substitution">';
2022						$html .= '<span class="unicode">' . $this->formatUni($inputGlyphs[0]) . '&nbsp;</span> ';
2023						if ($level == 2 && $exB) {
2024							$html .= $exB;
2025						}
2026						$html .= '<span class="unchanged">&nbsp;' . $this->formatEntity($inputGlyphs[0]) . '</span>';
2027						if ($level == 2 && $exL) {
2028							$html .= $exL;
2029						}
2030						$html .= '&nbsp; &raquo; &raquo; &nbsp;';
2031						if ($level == 2 && $exB) {
2032							$html .= $exB;
2033						}
2034						$html .= '<span class="changed">&nbsp;' . $this->formatEntity($substitute) . '</span>';
2035						if ($level == 2 && $exL) {
2036							$html .= $exL;
2037						}
2038						$html .= '&nbsp; <span class="unicode">' . $this->formatUni($substitute) . '</span> ';
2039						$html .= '</div>';
2040					}
2041				} // LookupType 2: Multiple Substitution Subtable
2042				else {
2043					if ($Lookup[$i]['Type'] == 2) {
2044						$html .= '<div class="lookuptype">LookupType 2: Multiple Substitution Subtable</div>';
2045						for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
2046							$inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
2047							$substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'];
2048							if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) {
2049								continue;
2050							}
2051							$html .= '<div class="substitution">';
2052							$html .= '<span class="unicode">' . $this->formatUni($inputGlyphs[0]) . '&nbsp;</span> ';
2053							if ($level == 2 && $exB) {
2054								$html .= $exB;
2055							}
2056							$html .= '<span class="unchanged">&nbsp;' . $this->formatEntity($inputGlyphs[0]) . '</span>';
2057							if ($level == 2 && $exL) {
2058								$html .= $exL;
2059							}
2060							$html .= '&nbsp; &raquo; &raquo; &nbsp;';
2061							if ($level == 2 && $exB) {
2062								$html .= $exB;
2063							}
2064							$html .= '<span class="changed">&nbsp;' . $this->formatEntityArr($substitute) . '</span>';
2065							if ($level == 2 && $exL) {
2066								$html .= $exL;
2067							}
2068							$html .= '&nbsp; <span class="unicode">' . $this->formatUniArr($substitute) . '</span> ';
2069							$html .= '</div>';
2070						}
2071					} // LookupType 3: Alternate Forms
2072					else {
2073						if ($Lookup[$i]['Type'] == 3) {
2074							$html .= '<div class="lookuptype">LookupType 3: Alternate Forms</div>';
2075							for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
2076								$inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
2077								$substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
2078								if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) {
2079									continue;
2080								}
2081								$html .= '<div class="substitution">';
2082								$html .= '<span class="unicode">' . $this->formatUni($inputGlyphs[0]) . '&nbsp;</span> ';
2083								if ($level == 2 && $exB) {
2084									$html .= $exB;
2085								}
2086								$html .= '<span class="unchanged">&nbsp;' . $this->formatEntity($inputGlyphs[0]) . '</span>';
2087								if ($level == 2 && $exL) {
2088									$html .= $exL;
2089								}
2090								$html .= '&nbsp; &raquo; &raquo; &nbsp;';
2091								if ($level == 2 && $exB) {
2092									$html .= $exB;
2093								}
2094								$html .= '<span class="changed">&nbsp;' . $this->formatEntity($substitute) . '</span>';
2095								if ($level == 2 && $exL) {
2096									$html .= $exL;
2097								}
2098								$html .= '&nbsp; <span class="unicode">' . $this->formatUni($substitute) . '</span> ';
2099								if (count($Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']) > 1) {
2100									for ($alt = 1; $alt < count($Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']); $alt++) {
2101										$substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][$alt];
2102										$html .= '&nbsp; | &nbsp; ALT #' . $alt . ' &nbsp; ';
2103										$html .= '<span class="changed">&nbsp;' . $this->formatEntity($substitute) . '</span>';
2104										$html .= '&nbsp; <span class="unicode">' . $this->formatUni($substitute) . '</span> ';
2105									}
2106								}
2107								$html .= '</div>';
2108							}
2109						} // LookupType 4: Ligature Substitution Subtable
2110						else {
2111							if ($Lookup[$i]['Type'] == 4) {
2112								$html .= '<div class="lookuptype">LookupType 4: Ligature Substitution Subtable</div>';
2113								for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
2114									$inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
2115									$substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
2116									if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) {
2117										continue;
2118									}
2119									$html .= '<div class="substitution">';
2120									$html .= '<span class="unicode">' . $this->formatUniArr($inputGlyphs) . '&nbsp;</span> ';
2121									if ($level == 2 && $exB) {
2122										$html .= $exB;
2123									}
2124									$html .= '<span class="unchanged">&nbsp;' . $this->formatEntityArr($inputGlyphs) . '</span>';
2125									if ($level == 2 && $exL) {
2126										$html .= $exL;
2127									}
2128									$html .= '&nbsp; &raquo; &raquo; &nbsp;';
2129									if ($level == 2 && $exB) {
2130										$html .= $exB;
2131									}
2132									$html .= '<span class="changed">&nbsp;' . $this->formatEntity($substitute) . '</span>';
2133									if ($level == 2 && $exL) {
2134										$html .= $exL;
2135									}
2136									$html .= '&nbsp; <span class="unicode">' . $this->formatUni($substitute) . '</span> ';
2137									$html .= '</div>';
2138								}
2139							} // LookupType 5: Contextual Substitution Subtable
2140							else {
2141								if ($Lookup[$i]['Type'] == 5) {
2142									$html .= '<div class="lookuptype">LookupType 5: Contextual Substitution Subtable</div>';
2143									// Format 1: Context Substitution
2144									if ($SubstFormat == 1) {
2145										$html .= '<div class="lookuptypesub">Format 1: Context Substitution</div>';
2146										for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) {
2147											// SubRuleSet
2148											$subRule = [];
2149											$html .= '<div class="rule">Subrule Set: ' . $s . '</div>';
2150											foreach ($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'] as $rctr => $rule) {
2151												// SubRule
2152												$html .= '<div class="rule">SubRule: ' . $rctr . '</div>';
2153												$inputGlyphs = [];
2154												if ($rule['GlyphCount'] > 1) {
2155													$inputGlyphs = $rule['InputGlyphs'];
2156												}
2157												$inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph'];
2158												ksort($inputGlyphs);
2159												$nInput = count($inputGlyphs);
2160
2161												$exampleI = [];
2162												$html .= '<div class="context">CONTEXT: ';
2163												for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
2164													$html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
2165													$exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
2166												}
2167												$html .= '</div>';
2168
2169												for ($b = 0; $b < $rule['SubstCount']; $b++) {
2170													$lup = $rule['SubstLookupRecord'][$b]['LookupListIndex'];
2171													$seqIndex = $rule['SubstLookupRecord'][$b]['SequenceIndex'];
2172
2173													// GENERATE exampleI[<seqIndex] .... exampleI[>seqIndex]
2174													$exB = '';
2175													$exL = '';
2176													if ($seqIndex > 0) {
2177														$exB .= '<span class="inputother">';
2178														for ($ip = 0; $ip < $seqIndex; $ip++) {
2179															$exB .= $this->formatEntity($inputGlyphs[$ip]) . '&#x200d;';
2180														}
2181														$exB .= '</span>';
2182													}
2183													if (count($inputGlyphs) > ($seqIndex + 1)) {
2184														$exL .= '<span class="inputother">';
2185														for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
2186															$exL .= $this->formatEntity($inputGlyphs[$ip]) . '&#x200d;';
2187														}
2188														$exL .= '</span>';
2189													}
2190													$html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
2191
2192													$lul2 = [$lup => $tag];
2193
2194													// Only apply if the (first) 'Replace' glyph from the
2195													// Lookup list is in the [inputGlyphs] at ['SequenceIndex']
2196													// Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
2197													// to level 2 and only apply if first Replace glyph is in this list
2198													$html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
2199												}
2200
2201												if (count($subRule['rules'])) {
2202													$volt[] = $subRule;
2203												}
2204											}
2205										}
2206									} // Format 2: Class-based Context Glyph Substitution
2207									else {
2208										if ($SubstFormat == 2) {
2209											$html .= '<div class="lookuptypesub">Format 2: Class-based Context Glyph Substitution</div>';
2210											foreach ($Lookup[$i]['Subtable'][$c]['SubClassSet'] as $inputClass => $cscs) {
2211												$html .= '<div class="rule">Input Class: ' . $inputClass . '</div>';
2212												for ($cscrule = 0; $cscrule < $cscs['SubClassRuleCnt']; $cscrule++) {
2213													$html .= '<div class="rule">Rule: ' . $cscrule . '</div>';
2214													$rule = $cscs['SubClassRule'][$cscrule];
2215
2216													$inputGlyphs = [];
2217
2218													$inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass];
2219
2220													if ($rule['InputGlyphCount'] > 1) {
2221														//  NB starts at 1
2222														for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) {
2223															$classindex = $rule['Input'][$gcl];
2224															$inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex];
2225														}
2226													}
2227
2228													// Class 0 contains all the glyphs NOT in the other classes
2229													$class0excl = implode('|', $Lookup[$i]['Subtable'][$c]['InputClasses']);
2230
2231													$exampleI = [];
2232													$html .= '<div class="context">CONTEXT: ';
2233													for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
2234														if (!$inputGlyphs[$ff]) {
2235															$html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;[NOT ' . $this->formatEntityStr($class0excl) . ']&nbsp;</span></div>';
2236															$exampleI[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']';
2237														} else {
2238															$html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
2239															$exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
2240														}
2241													}
2242													$html .= '</div>';
2243
2244													for ($b = 0; $b < $rule['SubstCount']; $b++) {
2245														$lup = $rule['LookupListIndex'][$b];
2246														$seqIndex = $rule['SequenceIndex'][$b];
2247
2248														// GENERATE exampleI[<seqIndex] .... exampleI[>seqIndex]
2249														$exB = '';
2250														$exL = '';
2251
2252														if ($seqIndex > 0) {
2253															$exB .= '<span class="inputother">';
2254															for ($ip = 0; $ip < $seqIndex; $ip++) {
2255																if (!$inputGlyphs[$ip]) {
2256																	$exB .= '[*]';
2257																} else {
2258																	$exB .= $this->formatEntityFirst($inputGlyphs[$ip]) . '&#x200d;';
2259																}
2260															}
2261															$exB .= '</span>';
2262														}
2263
2264														if (count($inputGlyphs) > ($seqIndex + 1)) {
2265															$exL .= '<span class="inputother">';
2266															for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
2267																if (!$inputGlyphs[$ip]) {
2268																	$exL .= '[*]';
2269																} else {
2270																	$exL .= $this->formatEntityFirst($inputGlyphs[$ip]) . '&#x200d;';
2271																}
2272															}
2273															$exL .= '</span>';
2274														}
2275
2276														$html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
2277
2278														$lul2 = [$lup => $tag];
2279
2280														// Only apply if the (first) 'Replace' glyph from the
2281														// Lookup list is in the [inputGlyphs] at ['SequenceIndex']
2282														// Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
2283														// to level 2 and only apply if first Replace glyph is in this list
2284														$html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
2285													}
2286													if (count($subRule['rules'])) {
2287														$volt[] = $subRule;
2288													}
2289												}
2290											}
2291										} // Format 3: Coverage-based Context Glyph Substitution  p259
2292										else {
2293											if ($SubstFormat == 3) {
2294												$html .= '<div class="lookuptypesub">Format 3: Coverage-based Context Glyph Substitution  </div>';
2295												// IgnoreMarks flag set on main Lookup table
2296												$inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'];
2297												$CoverageInputGlyphs = implode('|', $inputGlyphs);
2298												$nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount'];
2299
2300												$exampleI = [];
2301												$html .= '<div class="context">CONTEXT: ';
2302												for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
2303													$html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
2304													$exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
2305												}
2306												$html .= '</div>';
2307
2308												for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) {
2309													$lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'];
2310													$seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'];
2311													// GENERATE exampleI[<seqIndex] .... exampleI[>seqIndex]
2312													$exB = '';
2313													$exL = '';
2314													if ($seqIndex > 0) {
2315														$exB .= '<span class="inputother">';
2316														for ($ip = 0; $ip < $seqIndex; $ip++) {
2317															$exB .= $exampleI[$ip] . '&#x200d;';
2318														}
2319														$exB .= '</span>';
2320													}
2321
2322													if (count($inputGlyphs) > ($seqIndex + 1)) {
2323														$exL .= '<span class="inputother">';
2324														for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
2325															$exL .= $exampleI[$ip] . '&#x200d;';
2326														}
2327														$exL .= '</span>';
2328													}
2329
2330													$html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
2331
2332													$lul2 = [$lup => $tag];
2333
2334													// Only apply if the (first) 'Replace' glyph from the
2335													// Lookup list is in the [inputGlyphs] at ['SequenceIndex']
2336													// Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
2337													// to level 2 and only apply if first Replace glyph is in this list
2338													$html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
2339												}
2340												if (count($subRule['rules'])) {
2341													$volt[] = $subRule;
2342												}
2343											}
2344										}
2345									}
2346
2347//print_r($Lookup[$i]);
2348//print_r($volt[(count($volt)-1)]); exit;
2349								} // LookupType 6: Chaining Contextual Substitution Subtable
2350								else {
2351									if ($Lookup[$i]['Type'] == 6) {
2352										$html .= '<div class="lookuptype">LookupType 6: Chaining Contextual Substitution Subtable</div>';
2353										// Format 1: Simple Chaining Context Glyph Substitution  p255
2354										if ($SubstFormat == 1) {
2355											$html .= '<div class="lookuptypesub">Format 1: Simple Chaining Context Glyph Substitution  </div>';
2356											for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $s++) {
2357												// ChainSubRuleSet
2358												$subRule = [];
2359												$html .= '<div class="rule">Subrule Set: ' . $s . '</div>';
2360												$firstInputGlyph = $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'][$s]; // First input gyyph
2361												foreach ($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'] as $rctr => $rule) {
2362													$html .= '<div class="rule">SubRule: ' . $rctr . '</div>';
2363													// ChainSubRule
2364													$inputGlyphs = [];
2365													if ($rule['InputGlyphCount'] > 1) {
2366														$inputGlyphs = $rule['InputGlyphs'];
2367													}
2368													$inputGlyphs[0] = $firstInputGlyph;
2369													ksort($inputGlyphs);
2370													$nInput = count($inputGlyphs);
2371
2372													if ($rule['BacktrackGlyphCount']) {
2373														$backtrackGlyphs = $rule['BacktrackGlyphs'];
2374													} else {
2375														$backtrackGlyphs = [];
2376													}
2377
2378													if ($rule['LookaheadGlyphCount']) {
2379														$lookaheadGlyphs = $rule['LookaheadGlyphs'];
2380													} else {
2381														$lookaheadGlyphs = [];
2382													}
2383
2384													$exampleB = [];
2385													$exampleI = [];
2386													$exampleL = [];
2387													$html .= '<div class="context">CONTEXT: ';
2388													for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) {
2389														$html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>';
2390														$exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]);
2391													}
2392													for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
2393														$html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
2394														$exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
2395													}
2396													for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) {
2397														$html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>';
2398														$exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]);
2399													}
2400													$html .= '</div>';
2401
2402													for ($b = 0; $b < $rule['SubstCount']; $b++) {
2403														$lup = $rule['LookupListIndex'][$b];
2404														$seqIndex = $rule['SequenceIndex'][$b];
2405
2406														// GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n]
2407														$exB = '';
2408														$exL = '';
2409														if (count($exampleB)) {
2410															$exB .= '<span class="backtrack">' . implode('&#x200d;', $exampleB) . '</span>';
2411														}
2412
2413														if ($seqIndex > 0) {
2414															$exB .= '<span class="inputother">';
2415															for ($ip = 0; $ip < $seqIndex; $ip++) {
2416																$exB .= $this->formatEntity($inputGlyphs[$ip]) . '&#x200d;';
2417															}
2418															$exB .= '</span>';
2419														}
2420
2421														if (count($inputGlyphs) > ($seqIndex + 1)) {
2422															$exL .= '<span class="inputother">';
2423															for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
2424																$exL .= $this->formatEntity($inputGlyphs[$ip]) . '&#x200d;';
2425															}
2426															$exL .= '</span>';
2427														}
2428
2429														if (count($exampleL)) {
2430															$exL .= '<span class="lookahead">' . implode('&#x200d;', $exampleL) . '</span>';
2431														}
2432
2433														$html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
2434
2435														$lul2 = [$lup => $tag];
2436
2437														// Only apply if the (first) 'Replace' glyph from the
2438														// Lookup list is in the [inputGlyphs] at ['SequenceIndex']
2439														// Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
2440														// to level 2 and only apply if first Replace glyph is in this list
2441														$html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
2442													}
2443
2444													if (count($subRule['rules'])) {
2445														$volt[] = $subRule;
2446													}
2447												}
2448											}
2449										} // Format 2: Class-based Chaining Context Glyph Substitution  p257
2450										else {
2451											if ($SubstFormat == 2) {
2452												$html .= '<div class="lookuptypesub">Format 2: Class-based Chaining Context Glyph Substitution  </div>';
2453												foreach ($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'] as $inputClass => $cscs) {
2454													$html .= '<div class="rule">Input Class: ' . $inputClass . '</div>';
2455													for ($cscrule = 0; $cscrule < $cscs['ChainSubClassRuleCnt']; $cscrule++) {
2456														$html .= '<div class="rule">Rule: ' . $cscrule . '</div>';
2457														$rule = $cscs['ChainSubClassRule'][$cscrule];
2458
2459														// These contain classes of glyphs as strings
2460														// $Lookup[$i]['Subtable'][$c]['InputClasses'][(class)] e.g. 02E6|02E7|02E8
2461														// $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][(class)]
2462														// $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][(class)]
2463														// These contain arrays of classIndexes
2464														// [Backtrack] [Lookahead] and [Input] (Input is from the second position only)
2465
2466														$inputGlyphs = [];
2467
2468														$inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass];
2469														if ($rule['InputGlyphCount'] > 1) {
2470															//  NB starts at 1
2471															for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) {
2472																$classindex = $rule['Input'][$gcl];
2473																$inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex];
2474															}
2475														}
2476														// Class 0 contains all the glyphs NOT in the other classes
2477														$class0excl = implode('|', $Lookup[$i]['Subtable'][$c]['InputClasses']);
2478
2479														$nInput = $rule['InputGlyphCount'];
2480
2481														if ($rule['BacktrackGlyphCount']) {
2482															for ($gcl = 0; $gcl < $rule['BacktrackGlyphCount']; $gcl++) {
2483																$classindex = $rule['Backtrack'][$gcl];
2484																$backtrackGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][$classindex];
2485															}
2486														} else {
2487															$backtrackGlyphs = [];
2488														}
2489
2490														if ($rule['LookaheadGlyphCount']) {
2491															for ($gcl = 0; $gcl < $rule['LookaheadGlyphCount']; $gcl++) {
2492																$classindex = $rule['Lookahead'][$gcl];
2493																$lookaheadGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][$classindex];
2494															}
2495														} else {
2496															$lookaheadGlyphs = [];
2497														}
2498
2499														$exampleB = [];
2500														$exampleI = [];
2501														$exampleL = [];
2502														$html .= '<div class="context">CONTEXT: ';
2503														for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) {
2504															if (!$backtrackGlyphs[$ff]) {
2505																$html .= '<div>Backtrack #' . $ff . ': <span class="unchanged">&nbsp;[NOT ' . $this->formatEntityStr($class0excl) . ']&nbsp;</span></div>';
2506																$exampleB[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']';
2507															} else {
2508																$html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>';
2509																$exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]);
2510															}
2511														}
2512														for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
2513															if (!$inputGlyphs[$ff]) {
2514																$html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;[NOT ' . $this->formatEntityStr($class0excl) . ']&nbsp;</span></div>';
2515																$exampleI[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']';
2516															} else {
2517																$html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
2518																$exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
2519															}
2520														}
2521														for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) {
2522															if (!$lookaheadGlyphs[$ff]) {
2523																$html .= '<div>Lookahead #' . $ff . ': <span class="unchanged">&nbsp;[NOT ' . $this->formatEntityStr($class0excl) . ']&nbsp;</span></div>';
2524																$exampleL[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']';
2525															} else {
2526																$html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>';
2527																$exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]);
2528															}
2529														}
2530														$html .= '</div>';
2531
2532														for ($b = 0; $b < $rule['SubstCount']; $b++) {
2533															$lup = $rule['LookupListIndex'][$b];
2534															$seqIndex = $rule['SequenceIndex'][$b];
2535
2536															// GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n]
2537															$exB = '';
2538															$exL = '';
2539															if (count($exampleB)) {
2540																$exB .= '<span class="backtrack">' . implode('&#x200d;', $exampleB) . '</span>';
2541															}
2542
2543															if ($seqIndex > 0) {
2544																$exB .= '<span class="inputother">';
2545																for ($ip = 0; $ip < $seqIndex; $ip++) {
2546																	if (!$inputGlyphs[$ip]) {
2547																		$exB .= '[*]';
2548																	} else {
2549																		$exB .= $this->formatEntityFirst($inputGlyphs[$ip]) . '&#x200d;';
2550																	}
2551																}
2552																$exB .= '</span>';
2553															}
2554
2555															if (count($inputGlyphs) > ($seqIndex + 1)) {
2556																$exL .= '<span class="inputother">';
2557																for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
2558																	if (!$inputGlyphs[$ip]) {
2559																		$exL .= '[*]';
2560																	} else {
2561																		$exL .= $this->formatEntityFirst($inputGlyphs[$ip]) . '&#x200d;';
2562																	}
2563																}
2564																$exL .= '</span>';
2565															}
2566
2567															if (count($exampleL)) {
2568																$exL .= '<span class="lookahead">' . implode('&#x200d;', $exampleL) . '</span>';
2569															}
2570
2571															$html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
2572
2573															$lul2 = [$lup => $tag];
2574
2575															// Only apply if the (first) 'Replace' glyph from the
2576															// Lookup list is in the [inputGlyphs] at ['SequenceIndex']
2577															// Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
2578															// to level 2 and only apply if first Replace glyph is in this list
2579															$html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
2580														}
2581													}
2582												}
2583
2584//print_r($Lookup[$i]['Subtable'][$c]); exit;
2585											} // Format 3: Coverage-based Chaining Context Glyph Substitution  p259
2586											else {
2587												if ($SubstFormat == 3) {
2588													$html .= '<div class="lookuptypesub">Format 3: Coverage-based Chaining Context Glyph Substitution  </div>';
2589													// IgnoreMarks flag set on main Lookup table
2590													$inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'];
2591													$CoverageInputGlyphs = implode('|', $inputGlyphs);
2592													$nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount'];
2593
2594													if ($Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']) {
2595														$backtrackGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'];
2596													} else {
2597														$backtrackGlyphs = [];
2598													}
2599
2600													if ($Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']) {
2601														$lookaheadGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'];
2602													} else {
2603														$lookaheadGlyphs = [];
2604													}
2605
2606													$exampleB = [];
2607													$exampleI = [];
2608													$exampleL = [];
2609													$html .= '<div class="context">CONTEXT: ';
2610													for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) {
2611														$html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>';
2612														$exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]);
2613													}
2614													for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
2615														$html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
2616														$exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
2617													}
2618													for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) {
2619														$html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>';
2620														$exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]);
2621													}
2622													$html .= '</div>';
2623
2624													for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) {
2625														$lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'];
2626														$seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'];
2627
2628														// GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n]
2629														$exB = '';
2630														$exL = '';
2631														if (count($exampleB)) {
2632															$exB .= '<span class="backtrack">' . implode('&#x200d;', $exampleB) . '</span>';
2633														}
2634
2635														if ($seqIndex > 0) {
2636															$exB .= '<span class="inputother">';
2637															for ($ip = 0; $ip < $seqIndex; $ip++) {
2638																$exB .= $exampleI[$ip] . '&#x200d;';
2639															}
2640															$exB .= '</span>';
2641														}
2642
2643														if (count($inputGlyphs) > ($seqIndex + 1)) {
2644															$exL .= '<span class="inputother">';
2645															for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
2646																$exL .= $exampleI[$ip] . '&#x200d;';
2647															}
2648															$exL .= '</span>';
2649														}
2650
2651														if (count($exampleL)) {
2652															$exL .= '<span class="lookahead">' . implode('&#x200d;', $exampleL) . '</span>';
2653														}
2654
2655														$html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
2656
2657														$lul2 = [$lup => $tag];
2658
2659														// Only apply if the (first) 'Replace' glyph from the
2660														// Lookup list is in the [inputGlyphs] at ['SequenceIndex']
2661														// Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
2662														// to level 2 and only apply if first Replace glyph is in this list
2663														$html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
2664													}
2665												}
2666											}
2667										}
2668									}
2669								}
2670							}
2671						}
2672					}
2673				}
2674			}
2675			$html .= '</div>';
2676		}
2677		if ($level == 1) {
2678			$this->mpdf->WriteHTML($html);
2679		} else {
2680			return $html;
2681		}
2682//print_r($Lookup); exit;
2683	}
2684
2685	//=====================================================================================
2686	//=====================================================================================
2687	// mPDF 5.7.1
2688	function _checkGSUBignore($flag, $glyph, $MarkFilteringSet)
2689	{
2690		$ignore = false;
2691		// Flag & 0x0008 = Ignore Marks
2692		if ((($flag & 0x0008) == 0x0008) && strpos($this->GlyphClassMarks, $glyph)) {
2693			$ignore = true;
2694		}
2695		if ((($flag & 0x0004) == 0x0004) && strpos($this->GlyphClassLigatures, $glyph)) {
2696			$ignore = true;
2697		}
2698		if ((($flag & 0x0002) == 0x0002) && strpos($this->GlyphClassBases, $glyph)) {
2699			$ignore = true;
2700		}
2701		// Flag & 0xFF?? = MarkAttachmentType
2702		if (($flag & 0xFF00) && strpos($this->MarkAttachmentType[($flag >> 8)], $glyph)) {
2703			$ignore = true;
2704		}
2705		// Flag & 0x0010 = UseMarkFilteringSet
2706		if (($flag & 0x0010) && strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) {
2707			$ignore = true;
2708		}
2709
2710		return $ignore;
2711	}
2712
2713	function _getGSUBignoreString($flag, $MarkFilteringSet)
2714	{
2715		// If ignoreFlag set, combine all ignore glyphs into -> "((?:(?: FBA1| FBA2| FBA3))*)"
2716		// else "()"
2717		// for Input - set on secondary Lookup table if in Context, and set Backtrack and Lookahead on Context Lookup
2718		$str = "";
2719		$ignoreflag = 0;
2720
2721		// Flag & 0xFF?? = MarkAttachmentType
2722		if ($flag & 0xFF00) {
2723			$MarkAttachmentType = $flag >> 8;
2724			$ignoreflag = $flag;
2725			//$str = $this->MarkAttachmentType[$MarkAttachmentType];
2726			$str = "MarkAttachmentType[" . $MarkAttachmentType . "] ";
2727		}
2728
2729		// Flag & 0x0010 = UseMarkFilteringSet
2730		if ($flag & 0x0010) {
2731			throw new \Mpdf\Exception\FontException("This font " . $this->fontkey . " contains MarkGlyphSets");
2732			$str = "Mark Glyph Set: ";
2733			$str .= $this->MarkGlyphSets[$MarkFilteringSet];
2734		}
2735
2736		// If Ignore Marks set, supercedes any above
2737		// Flag & 0x0008 = Ignore Marks
2738		if (($flag & 0x0008) == 0x0008) {
2739			$ignoreflag = 8;
2740			//$str = $this->GlyphClassMarks;
2741			$str = "Mark Glyphs ";
2742		}
2743
2744		// Flag & 0x0004 = Ignore Ligatures
2745		if (($flag & 0x0004) == 0x0004) {
2746			$ignoreflag += 4;
2747			if ($str) {
2748				$str .= "|";
2749			}
2750			//$str .= $this->GlyphClassLigatures;
2751			$str .= "Ligature Glyphs ";
2752		}
2753		// Flag & 0x0002 = Ignore BaseGlyphs
2754		if (($flag & 0x0002) == 0x0002) {
2755			$ignoreflag += 2;
2756			if ($str) {
2757				$str .= "|";
2758			}
2759			//$str .= $this->GlyphClassBases;
2760			$str .= "Base Glyphs ";
2761		}
2762		if ($str) {
2763			return $str;
2764		} else {
2765			return "";
2766		}
2767	}
2768
2769	// GSUB Patterns
2770
2771	/*
2772	  BACKTRACK                        INPUT                   LOOKAHEAD
2773	  ==================================  ==================  ==================================
2774	  (FEEB|FEEC)(ign) ¦(FD12|FD13)(ign) ¦(0612)¦(ign) (0613)¦(ign) (FD12|FD13)¦(ign) (FEEB|FEEC)
2775	  ----------------  ----------------  -----  ------------  ---------------   ---------------
2776	  Backtrack 1       Backtrack 2     Input 1   Input 2       Lookahead 1      Lookahead 2
2777	  --------   ---    ---------  ---    ----   ---   ----   ---   ---------   ---    -------
2778	  \${1}  \${2}     \${3}   \${4}                      \${5+}  \${6+}    \${7+}  \${8+}
2779
2780	  nBacktrack = 2               nInput = 2                 nLookahead = 2
2781
2782	  nBsubs = 2xnBack          nIsubs = (nBsubs+)    nLsubs = (nBsubs+nIsubs+) 2xnLookahead
2783	  "\${1}\${2} "                 (nInput*2)-1               "\${5+} \${6+}"
2784	  "REPL"
2785
2786	  ¦\${1}\${2} ¦\${3}\${4} ¦REPL¦\${5+} \${6+}¦\${7+} \${8+}¦
2787
2788
2789	  INPUT nInput = 5
2790	  ============================================================
2791	  ¦(0612)¦(ign) (0613)¦(ign) (0614)¦(ign) (0615)¦(ign) (0615)¦
2792	  \${1}  \${2}  \${3}  \${4} \${5} \${6}  \${7} \${8}  \${9} (All backreference numbers are + nBsubs)
2793	  -----  ------------ ------------ ------------ ------------
2794	  Input 1   Input 2      Input 3      Input 4      Input 5
2795
2796	  A======  SequenceIndex=1 ; Lookup match nGlyphs=1
2797	  B===================  SequenceIndex=1 ; Lookup match nGlyphs=2
2798	  C===============================  SequenceIndex=1 ; Lookup match nGlyphs=3
2799	  D=======================  SequenceIndex=2 ; Lookup match nGlyphs=2
2800	  E=====================================  SequenceIndex=2 ; Lookup match nGlyphs=3
2801	  F======================  SequenceIndex=4 ; Lookup match nGlyphs=2
2802
2803	  All backreference numbers are + nBsubs
2804	  A - "REPL\${2} \${3}\${4} \${5}\${6} \${7}\${8} \${9}"
2805	  B - "REPL\${2}\${4} \${5}\${6} \${7}\${8} \${9}"
2806	  C - "REPL\${2}\${4}\${6} \${7}\${8} \${9}"
2807	  D - "\${1} REPL\${2}\${4}\${6} \${7}\${8} \${9}"
2808	  E - "\${1} REPL\${2}\${4}\${6}\${8} \${9}"
2809	  F - "\${1}\${2} \${3}\${4} \${5} REPL\${6}\${8}"
2810	 */
2811
2812	function _makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex)
2813	{
2814		// $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
2815		// Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
2816		// $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context
2817		// $lookupGlyphs = array of glyphs (single Glyphs) making up Lookup Input sequence
2818		$mLen = count($lookupGlyphs);  // nGlyphs in the secondary Lookup match
2819		$nInput = count($inputGlyphs); // nGlyphs in the Primary Input sequence
2820		$str = "";
2821		for ($i = 0; $i < $nInput; $i++) {
2822			if ($i > 0) {
2823				$str .= $ignore . " ";
2824			}
2825			if ($i >= $seqIndex && $i < ($seqIndex + $mLen)) {
2826				$str .= "" . $lookupGlyphs[($i - $seqIndex)] . "";
2827			} else {
2828				$str .= "" . $inputGlyphs[($i)] . "";
2829			}
2830		}
2831
2832		return $str;
2833	}
2834
2835	function _makeGSUBinputMatch($inputGlyphs, $ignore)
2836	{
2837		// $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
2838		// Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
2839		// $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context
2840		// $lookupGlyphs = array of glyphs making up Lookup Input sequence - if applicable
2841		$str = "";
2842		for ($i = 1; $i <= count($inputGlyphs); $i++) {
2843			if ($i > 1) {
2844				$str .= $ignore . " ";
2845			}
2846			$str .= "" . $inputGlyphs[($i - 1)] . "";
2847		}
2848
2849		return $str;
2850	}
2851
2852	function _makeGSUBbacktrackMatch($backtrackGlyphs, $ignore)
2853	{
2854		// $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
2855		// Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦
2856		// $backtrackGlyphs = array of glyphstrings making up Backtrack sequence
2857		// 3  2  1  0
2858		// each item being e.g. E0AD|E0AF|F1FD
2859		$str = "";
2860		for ($i = (count($backtrackGlyphs) - 1); $i >= 0; $i--) {
2861			$str .= "" . $backtrackGlyphs[$i] . " " . $ignore . " ";
2862		}
2863
2864		return $str;
2865	}
2866
2867	function _makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore)
2868	{
2869		// $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
2870		// Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦
2871		// $lookaheadGlyphs = array of glyphstrings making up Lookahead sequence
2872		// 0  1  2  3
2873		// each item being e.g. E0AD|E0AF|F1FD
2874		$str = "";
2875		for ($i = 0; $i < count($lookaheadGlyphs); $i++) {
2876			$str .= $ignore . " " . $lookaheadGlyphs[$i] . "";
2877		}
2878
2879		return $str;
2880	}
2881
2882	function _makeGSUBinputReplacement($nInput, $REPL, $ignore, $nBsubs, $mLen, $seqIndex)
2883	{
2884		// Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}"
2885		// $nInput	nGlyphs in the Primary Input sequence
2886		// $REPL 	replacement glyphs from secondary lookup
2887		// $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
2888		// $nBsubs	Number of Backtrack substitutions (= 2x Number of Backtrack glyphs)
2889		// $mLen 	nGlyphs in the secondary Lookup match - if no secondary lookup, should=$nInput
2890		// $seqIndex	Sequence Index to apply the secondary match
2891		if ($ignore == "()") {
2892			$ign = false;
2893		} else {
2894			$ign = true;
2895		}
2896		$str = "";
2897		if ($nInput == 1) {
2898			$str = $REPL;
2899		} else {
2900			if ($nInput > 1) {
2901				if ($mLen == $nInput) { // whole string replaced
2902					$str = $REPL;
2903					if ($ign) {
2904						// for every nInput over 1, add another replacement backreference, to move IGNORES after replacement
2905						for ($x = 2; $x <= $nInput; $x++) {
2906							$str .= '\\' . ($nBsubs + (2 * ($x - 1)));
2907						}
2908					}
2909				} else { // if only part of string replaced:
2910					for ($x = 1; $x < ($seqIndex + 1); $x++) {
2911						if ($x == 1) {
2912							$str .= '\\' . ($nBsubs + 1);
2913						} else {
2914							if ($ign) {
2915								$str .= '\\' . ($nBsubs + (2 * ($x - 1)));
2916							}
2917							$str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1)));
2918						}
2919					}
2920					if ($seqIndex > 0) {
2921						$str .= " ";
2922					}
2923					$str .= $REPL;
2924					if ($ign) {
2925						for ($x = (max(($seqIndex + 1), 2)); $x < ($seqIndex + 1 + $mLen); $x++) { //  move IGNORES after replacement
2926							$str .= '\\' . ($nBsubs + (2 * ($x - 1)));
2927						}
2928					}
2929					for ($x = ($seqIndex + 1 + $mLen); $x <= $nInput; $x++) {
2930						if ($ign) {
2931							$str .= '\\' . ($nBsubs + (2 * ($x - 1)));
2932						}
2933						$str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1)));
2934					}
2935				}
2936			}
2937		}
2938
2939		return $str;
2940	}
2941
2942	//////////////////////////////////////////////////////////////////////////////////
2943	function _getCoverage($convert2hex = true)
2944	{
2945		$g = [];
2946		$CoverageFormat = $this->read_ushort();
2947		if ($CoverageFormat == 1) {
2948			$CoverageGlyphCount = $this->read_ushort();
2949			for ($gid = 0; $gid < $CoverageGlyphCount; $gid++) {
2950				$glyphID = $this->read_ushort();
2951				if ($convert2hex) {
2952					$g[] = unicode_hex($this->glyphToChar[$glyphID][0]);
2953				} else {
2954					$g[] = $glyphID;
2955				}
2956			}
2957		}
2958		if ($CoverageFormat == 2) {
2959			$RangeCount = $this->read_ushort();
2960			for ($r = 0; $r < $RangeCount; $r++) {
2961				$start = $this->read_ushort();
2962				$end = $this->read_ushort();
2963				$StartCoverageIndex = $this->read_ushort(); // n/a
2964				for ($gid = $start; $gid <= $end; $gid++) {
2965					$glyphID = $gid;
2966					if ($convert2hex) {
2967						$g[] = unicode_hex($this->glyphToChar[$glyphID][0]);
2968					} else {
2969						$g[] = $glyphID;
2970					}
2971				}
2972			}
2973		}
2974
2975		return $g;
2976	}
2977
2978	//////////////////////////////////////////////////////////////////////////////////
2979	function _getClasses($offset)
2980	{
2981		$this->seek($offset);
2982		$ClassFormat = $this->read_ushort();
2983		$GlyphByClass = [];
2984		if ($ClassFormat == 1) {
2985			$StartGlyph = $this->read_ushort();
2986			$GlyphCount = $this->read_ushort();
2987			for ($i = 0; $i < $GlyphCount; $i++) {
2988				$startGlyphID = $StartGlyph + $i;
2989				$endGlyphID = $StartGlyph + $i;
2990				$class = $this->read_ushort();
2991				for ($g = $startGlyphID; $g <= $endGlyphID; $g++) {
2992					if ($this->glyphToChar[$g][0]) {
2993						$GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]);
2994					}
2995				}
2996			}
2997		} else {
2998			if ($ClassFormat == 2) {
2999				$tableCount = $this->read_ushort();
3000				for ($i = 0; $i < $tableCount; $i++) {
3001					$startGlyphID = $this->read_ushort();
3002					$endGlyphID = $this->read_ushort();
3003					$class = $this->read_ushort();
3004					for ($g = $startGlyphID; $g <= $endGlyphID; $g++) {
3005						if ($this->glyphToChar[$g][0]) {
3006							$GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]);
3007						}
3008					}
3009				}
3010			}
3011		}
3012		$gbc = [];
3013		foreach ($GlyphByClass as $class => $garr) {
3014			$gbc[$class] = implode('|', $garr);
3015		}
3016
3017		return $gbc;
3018	}
3019
3020	//////////////////////////////////////////////////////////////////////////////////
3021	//////////////////////////////////////////////////////////////////////////////////
3022	//////////////////////////////////////////////////////////////////////////////////
3023	//////////////////////////////////////////////////////////////////////////////////
3024	//////////////////////////////////////////////////////////////////////////////////
3025	function _getGPOStables()
3026	{
3027		///////////////////////////////////
3028		// GPOS - Glyph Positioning
3029		///////////////////////////////////
3030		if (isset($this->tables["GPOS"])) {
3031			$this->mpdf->WriteHTML('<h1>GPOS Tables</h1>');
3032			$ffeats = [];
3033			$gpos_offset = $this->seek_table("GPOS");
3034			$this->skip(4);
3035			$ScriptList_offset = $gpos_offset + $this->read_ushort();
3036			$FeatureList_offset = $gpos_offset + $this->read_ushort();
3037			$LookupList_offset = $gpos_offset + $this->read_ushort();
3038
3039			// ScriptList
3040			$this->seek($ScriptList_offset);
3041			$ScriptCount = $this->read_ushort();
3042			for ($i = 0; $i < $ScriptCount; $i++) {
3043				$ScriptTag = $this->read_tag(); // = "beng", "deva" etc.
3044				$ScriptTableOffset = $this->read_ushort();
3045				$ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset;
3046			}
3047
3048			// Script Table
3049			foreach ($ffeats as $t => $o) {
3050				$ls = [];
3051				$this->seek($o);
3052				$DefLangSys_offset = $this->read_ushort();
3053				if ($DefLangSys_offset > 0) {
3054					$ls['DFLT'] = $DefLangSys_offset + $o;
3055				}
3056				$LangSysCount = $this->read_ushort();
3057				for ($i = 0; $i < $LangSysCount; $i++) {
3058					$LangTag = $this->read_tag(); // =
3059					$LangTableOffset = $this->read_ushort();
3060					$ls[$LangTag] = $o + $LangTableOffset;
3061				}
3062				$ffeats[$t] = $ls;
3063			}
3064
3065			// Get FeatureIndexList
3066			// LangSys Table - from first listed langsys
3067			foreach ($ffeats as $st => $scripts) {
3068				foreach ($scripts as $t => $o) {
3069					$FeatureIndex = [];
3070					$langsystable_offset = $o;
3071					$this->seek($langsystable_offset);
3072					$LookUpOrder = $this->read_ushort(); //==NULL
3073					$ReqFeatureIndex = $this->read_ushort();
3074					if ($ReqFeatureIndex != 0xFFFF) {
3075						$FeatureIndex[] = $ReqFeatureIndex;
3076					}
3077					$FeatureCount = $this->read_ushort();
3078					for ($i = 0; $i < $FeatureCount; $i++) {
3079						$FeatureIndex[] = $this->read_ushort(); // = index of feature
3080					}
3081					$ffeats[$st][$t] = $FeatureIndex;
3082				}
3083			}
3084//print_r($ffeats); exit;
3085			// Feauture List => LookupListIndex es
3086			$this->seek($FeatureList_offset);
3087			$FeatureCount = $this->read_ushort();
3088			$Feature = [];
3089			for ($i = 0; $i < $FeatureCount; $i++) {
3090				$Feature[$i] = ['tag' => $this->read_tag()];
3091				$Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort();
3092			}
3093			for ($i = 0; $i < $FeatureCount; $i++) {
3094				$this->seek($Feature[$i]['offset']);
3095				$this->read_ushort(); // null
3096				$Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort();
3097				$Feature[$i]['LookupListIndex'] = [];
3098				for ($c = 0; $c < $Lookupcount; $c++) {
3099					$Feature[$i]['LookupListIndex'][] = $this->read_ushort();
3100				}
3101			}
3102
3103			foreach ($ffeats as $st => $scripts) {
3104				foreach ($scripts as $t => $o) {
3105					$FeatureIndex = $ffeats[$st][$t];
3106					foreach ($FeatureIndex as $k => $fi) {
3107						$ffeats[$st][$t][$k] = $Feature[$fi];
3108					}
3109				}
3110			}
3111//print_r($ffeats); exit;
3112			//=====================================================================================
3113			$gpos = [];
3114			$GPOSScriptLang = [];
3115			foreach ($ffeats as $st => $scripts) {
3116				foreach ($scripts as $t => $langsys) {
3117					$lg = [];
3118					foreach ($langsys as $ft) {
3119						$lg[$ft['LookupListIndex'][0]] = $ft;
3120					}
3121					// list of Lookups in order they need to be run i.e. order listed in Lookup table
3122					ksort($lg);
3123					foreach ($lg as $ft) {
3124						$gpos[$st][$t][$ft['tag']] = $ft['LookupListIndex'];
3125					}
3126					if (!isset($GPOSScriptLang[$st])) {
3127						$GPOSScriptLang[$st] = '';
3128					}
3129					$GPOSScriptLang[$st] .= $t . ' ';
3130				}
3131			}
3132			if ($this->mode == 'summary') {
3133				$this->mpdf->WriteHTML('<h3>GPOS Scripts &amp; Languages</h3>');
3134				$html = '';
3135				if (count($gpos)) {
3136					foreach ($gpos as $st => $g) {
3137						$html .= '<h5>' . $st . '</h5>';
3138						foreach ($g as $l => $t) {
3139							$html .= '<div><a href="font_dump_OTL.php?script=' . $st . '&lang=' . $l . '">' . $l . '</a></b>: ';
3140							foreach ($t as $tag => $o) {
3141								$html .= $tag . ' ';
3142							}
3143							$html .= '</div>';
3144						}
3145					}
3146				} else {
3147					$html .= '<div>No entries in GPOS table.</div>';
3148				}
3149				$this->mpdf->WriteHTML($html);
3150				$this->mpdf->WriteHTML('</div>');
3151
3152				return 0;
3153			}
3154
3155			//=====================================================================================
3156			// Get metadata and offsets for whole Lookup List table
3157			$this->seek($LookupList_offset);
3158			$LookupCount = $this->read_ushort();
3159			$Lookup = [];
3160			$Offsets = [];
3161			$SubtableCount = [];
3162			for ($i = 0; $i < $LookupCount; $i++) {
3163				$Offsets[$i] = $LookupList_offset + $this->read_ushort();
3164			}
3165			for ($i = 0; $i < $LookupCount; $i++) {
3166				$this->seek($Offsets[$i]);
3167				$Lookup[$i]['Type'] = $this->read_ushort();
3168				$Lookup[$i]['Flag'] = $flag = $this->read_ushort();
3169				$Lookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort();
3170				for ($c = 0; $c < $SubtableCount[$i]; $c++) {
3171					$Lookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort();
3172				}
3173				// MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure
3174				if (($flag & 0x0010) == 0x0010) {
3175					$Lookup[$i]['MarkFilteringSet'] = $this->read_ushort();
3176				}
3177				// else { $Lookup[$i]['MarkFilteringSet'] = ''; }
3178				// Lookup Type 9: Extension
3179				if ($Lookup[$i]['Type'] == 9) {
3180					// Overwrites new offset (32-bit) for each subtable, and a new lookup Type
3181					for ($c = 0; $c < $SubtableCount[$i]; $c++) {
3182						$this->seek($Lookup[$i]['Subtables'][$c]);
3183						$ExtensionPosFormat = $this->read_ushort();
3184						$type = $this->read_ushort();
3185						$Lookup[$i]['Subtables'][$c] = $Lookup[$i]['Subtables'][$c] + $this->read_ulong();
3186					}
3187					$Lookup[$i]['Type'] = $type;
3188				}
3189			}
3190
3191			//=====================================================================================
3192
3193			$st = $this->mpdf->OTLscript;
3194			$t = $this->mpdf->OTLlang;
3195			$langsys = $gpos[$st][$t];
3196
3197			$lul = []; // array of LookupListIndexes
3198			$tags = []; // corresponding array of feature tags e.g. 'ccmp'
3199			if (count($langsys)) {
3200				foreach ($langsys as $tag => $ft) {
3201					foreach ($ft as $ll) {
3202						$lul[$ll] = $tag;
3203					}
3204				}
3205			}
3206			ksort($lul); // Order the Lookups in the order they are in the GUSB table, regardless of Feature order
3207			$this->_getGPOSarray($Lookup, $lul, $st);
3208
3209//print_r($lul); exit;
3210
3211			return [$GPOSScriptLang, $gpos, $Lookup];
3212		} // end if GPOS
3213	}
3214
3215	//////////////////////////////////////////////////////////////////////////////////
3216	//=====================================================================================
3217	//=====================================================================================
3218	//=====================================================================================
3219/////////////////////////////////////////////////////////////////////////////////////////
3220	// GPOS functions
3221	function _getGPOSarray(&$Lookup, $lul, $scripttag, $level = 1, $lcoverage = '', $exB = '', $exL = '')
3222	{
3223		// Process (3) LookupList for specific Script-LangSys
3224		$html = '';
3225		if ($level == 1) {
3226			$html .= '<bookmark level="0" content="GPOS features">';
3227		}
3228		foreach ($lul as $luli => $tag) {
3229			$html .= '<div class="level' . $level . '">';
3230			$html .= '<h5 class="level' . $level . '">';
3231			if ($level == 1) {
3232				$html .= '<bookmark level="1" content="' . $tag . ' [#' . $luli . ']">';
3233			}
3234			$html .= 'Lookup #' . $luli . ' [tag: <span style="color:#000066;">' . $tag . '</span>]</h5>';
3235			$ignore = $this->_getGSUBignoreString($Lookup[$luli]['Flag'], $Lookup[$luli]['MarkFilteringSet']);
3236			if ($ignore) {
3237				$html .= '<div class="ignore">Ignoring: ' . $ignore . '</div> ';
3238			}
3239
3240			$Type = $Lookup[$luli]['Type'];
3241			$Flag = $Lookup[$luli]['Flag'];
3242			if (($Flag & 0x0001) == 1) {
3243				$dir = 'RTL';
3244			} else {
3245				$dir = 'LTR';
3246			}
3247
3248			for ($c = 0; $c < $Lookup[$luli]['SubtableCount']; $c++) {
3249				$html .= '<div class="subtable">Subtable #' . $c;
3250				if ($level == 1) {
3251					$html .= '<bookmark level="2" content="Subtable #' . $c . '">';
3252				}
3253				$html .= '</div>';
3254
3255				// Lets start
3256				$subtable_offset = $Lookup[$luli]['Subtables'][$c];
3257				$this->seek($subtable_offset);
3258				$PosFormat = $this->read_ushort();
3259
3260				////////////////////////////////////////////////////////////////////////////////
3261				// LookupType 1: Single adjustment 	Adjust position of a single glyph (e.g. SmallCaps/Sups/Subs)
3262				////////////////////////////////////////////////////////////////////////////////
3263				if ($Lookup[$luli]['Type'] == 1) {
3264					$html .= '<div class="lookuptype">LookupType 1: Single adjustment [Format ' . $PosFormat . ']</div>';
3265					//===========
3266					// Format 1:
3267					//===========
3268					if ($PosFormat == 1) {
3269						$Coverage = $subtable_offset + $this->read_ushort();
3270						$ValueFormat = $this->read_ushort();
3271						$Value = $this->_getValueRecord($ValueFormat);
3272
3273						$this->seek($Coverage);
3274						$glyphs = $this->_getCoverage(); // Array of Hex Glyphs
3275						for ($g = 0; $g < count($glyphs); $g++) {
3276							if ($level == 2 && strpos($lcoverage, $glyphs[$g]) === false) {
3277								continue;
3278							}
3279
3280							$html .= '<div class="substitution">';
3281							$html .= '<span class="unicode">' . $this->formatUni($glyphs[$g]) . '&nbsp;</span> ';
3282							if ($level == 2 && $exB) {
3283								$html .= $exB;
3284							}
3285							$html .= '<span class="unchanged">&nbsp;' . $this->formatEntity($glyphs[$g]) . '</span>';
3286							if ($level == 2 && $exL) {
3287								$html .= $exL;
3288							}
3289							$html .= '&nbsp; &raquo; &raquo; &nbsp;';
3290							if ($level == 2 && $exB) {
3291								$html .= $exB;
3292							}
3293							$html .= '<span class="changed" style="font-feature-settings:\'' . $tag . '\' 1;">&nbsp;' . $this->formatEntity($glyphs[$g]) . '</span>';
3294							if ($