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

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

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