PluginProbe
PDF & Print by BestWebSoft – WordPress Posts and Pages PDF Generator Plugin / 2.4.3
PDF & Print by BestWebSoft – WordPress Posts and Pages PDF Generator Plugin v2.4.3
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 / ttfontsuni.php

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

4,998 lines 183.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 require_once __DIR__ . '/../MpdfException.php';
4
5 /*********************************************************************************
6 * TTFontFile class *
7 * *
8 * Version: 4.01 *
9 * Date: 2014-10-25 *
10 * Author: Ian Back <ianb@bpm1.com> *
11 * License: LGPL *
12 * Copyright (c) Ian Back, 2010 *
13 * This class is based on The ReportLab Open Source PDF library *
14 * written in Python - http://www.reportlab.com/software/opensource/ *
15 * together with ideas from the OpenOffice source code and others. *
16 * This header must be retained in any redistribution or *
17 * modification of the file. *
18 * *
19 ********************************************************************************/
20
21 // NOTE*** If you change the defined constants below, be sure to delete all temporary font data files in /ttfontdata/
22 // to force mPDF to regenerate cached font files.
23 if (!defined('_OTL_OLD_SPEC_COMPAT_2'))
24 define("_OTL_OLD_SPEC_COMPAT_2", true);
25
26 // Define the value used in the "head" table of a created TTF file
27 // 0x74727565 "true" for Mac
28 // 0x00010000 for Windows
29 // Either seems to work for a font embedded in a PDF file
30 // when read by Adobe Reader on a Windows PC(!)
31 if (!defined('_TTF_MAC_HEADER'))
32 define("_TTF_MAC_HEADER", false);
33
34 // Recalculate correct metadata/profiles when making subset fonts (not SIP/SMP)
35 // e.g. xMin, xMax, maxNContours
36 if (!defined('_RECALC_PROFILE'))
37 define("_RECALC_PROFILE", false);
38
39 // TrueType Font Glyph operators
40 define("GF_WORDS", (1 << 0));
41 define("GF_SCALE", (1 << 3));
42 define("GF_MORE", (1 << 5));
43 define("GF_XYSCALE", (1 << 6));
44 define("GF_TWOBYTWO", (1 << 7));
45
46 // mPDF 5.7.1
47 if (!function_exists('unicode_hex')) {
48
49 function unicode_hex($unicode_dec)
50 {
51 return (sprintf("%05s", strtoupper(dechex($unicode_dec))));
52 }
53
54 }
55
56 class TTFontFile
57 {
58
59 var $GPOSFeatures; // mPDF 5.7.1
60 var $GPOSLookups; // mPDF 5.7.1
61 var $GPOSScriptLang; // mPDF 5.7.1
62 var $MarkAttachmentType; // mPDF 5.7.1
63 var $MarkGlyphSets; // mPDF 7.5.1
64 var $GlyphClassMarks; // mPDF 5.7.1
65 var $GlyphClassLigatures; // mPDF 5.7.1
66 var $GlyphClassBases; // mPDF 5.7.1
67 var $GlyphClassComponents; // mPDF 5.7.1
68 var $GSUBScriptLang; // mPDF 5.7.1
69 var $rtlPUAstr; // mPDF 5.7.1
70 //var $rtlPUAarr; // mPDF 5.7.1
71 var $fontkey; // mPDF 5.7.1
72 var $useOTL; // mPDF 5.7.1 var $panose;
73 var $maxUni;
74 var $sFamilyClass;
75 var $sFamilySubClass;
76 var $sipset;
77 var $smpset;
78 var $_pos;
79 var $numTables;
80 var $searchRange;
81 var $entrySelector;
82 var $rangeShift;
83 var $tables;
84 var $otables;
85 var $filename;
86 var $fh;
87 var $glyphPos;
88 var $charToGlyph;
89 var $ascent;
90 var $descent;
91 var $lineGap; // mPDF 6
92 var $hheaascent;
93 var $hheadescent;
94 var $hhealineGap; // mPDF 6
95 var $advanceWidthMax; // mPDF 6
96 var $typoAscender; // mPDF 6
97 var $typoDescender; // mPDF 6
98 var $typoLineGap; // mPDF 6
99 var $usWinAscent; // mPDF 6
100 var $usWinDescent; // mPDF 6
101 var $strikeoutSize;
102 var $strikeoutPosition;
103 var $name;
104 var $familyName;
105 var $styleName;
106 var $fullName;
107 var $uniqueFontID;
108 var $unitsPerEm;
109 var $bbox;
110 var $capHeight;
111 var $xHeight; // mPDF 6
112 var $stemV;
113 var $italicAngle;
114 var $flags;
115 var $underlinePosition;
116 var $underlineThickness;
117 var $charWidths;
118 var $defaultWidth;
119 var $maxStrLenRead;
120 var $numTTCFonts;
121 var $TTCFonts;
122 var $maxUniChar;
123 var $kerninfo;
124 var $haskernGPOS;
125 var $hassmallcapsGSUB;
126
127 public function __construct()
128 {
129 $this->maxStrLenRead = 200000; // Maximum size of glyf table to read in as string (otherwise reads each glyph from file)
130 }
131
132 function getMetrics($file, $fontkey, $TTCfontID = 0, $debug = false, $BMPonly = false, $useOTL = 0)
133 { // mPDF 5.7.1
134 $this->useOTL = $useOTL; // mPDF 5.7.1
135 $this->fontkey = $fontkey; // mPDF 5.7.1
136 $this->filename = $file;
137 $this->fh = fopen($file, 'rb');
138
139 if (!$this->fh) {
140 throw new MpdfException('Can\'t open file ' . $file);
141 }
142
143 $this->_pos = 0;
144 $this->charWidths = '';
145 $this->glyphPos = array();
146 $this->charToGlyph = array();
147 $this->tables = array();
148 $this->otables = array();
149 $this->kerninfo = array();
150 $this->haskernGPOS = array();
151 $this->hassmallcapsGSUB = array();
152 $this->ascent = 0;
153 $this->descent = 0;
154 $this->lineGap = 0; // mPDF 6
155 $this->hheaascent = 0; // mPDF 6
156 $this->hheadescent = 0; // mPDF 6
157 $this->hhealineGap = 0; // mPDF 6
158 $this->xHeight = 0; // mPDF 6
159 $this->capHeight = 0; // mPDF 6
160 $this->panose = array();
161 $this->sFamilyClass = 0;
162 $this->sFamilySubClass = 0;
163 $this->typoAscender = 0; // mPDF 6
164 $this->typoDescender = 0; // mPDF 6
165 $this->typoLineGap = 0; // mPDF 6
166 $this->usWinAscent = 0; // mPDF 6
167 $this->usWinDescent = 0; // mPDF 6
168 $this->advanceWidthMax = 0; // mPDF 6
169 $this->strikeoutSize = 0;
170 $this->strikeoutPosition = 0;
171 $this->numTTCFonts = 0;
172 $this->TTCFonts = array();
173 $this->version = $version = $this->read_ulong();
174 $this->panose = array();
175
176 if ($version == 0x4F54544F) {
177 throw new MpdfException("Postscript outlines are not supported");
178 }
179
180 if ($version == 0x74746366 && !$TTCfontID) {
181 throw new MpdfException("ERROR - You must define the TTCfontID for a TrueType Collection in config_fonts.php (" . $file . ")");
182 }
183
184 if (!in_array($version, array(0x00010000, 0x74727565)) && !$TTCfontID) {
185 throw new MpdfException("Not a TrueType font: version=" . $version);
186 }
187
188 if ($TTCfontID > 0) {
189 $this->version = $version = $this->read_ulong(); // TTC Header version now
190 if (!in_array($version, array(0x00010000, 0x00020000))) {
191 throw new MpdfException("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
192 }
193 $this->numTTCFonts = $this->read_ulong();
194 for ($i = 1; $i <= $this->numTTCFonts; $i++) {
195 $this->TTCFonts[$i]['offset'] = $this->read_ulong();
196 }
197 $this->seek($this->TTCFonts[$TTCfontID]['offset']);
198 $this->version = $version = $this->read_ulong(); // TTFont version again now
199 }
200
201 $this->readTableDirectory($debug);
202 $this->extractInfo($debug, $BMPonly, $useOTL);
203 fclose($this->fh);
204 }
205
206 function readTableDirectory($debug = false)
207 {
208 $this->numTables = $this->read_ushort();
209 $this->searchRange = $this->read_ushort();
210 $this->entrySelector = $this->read_ushort();
211 $this->rangeShift = $this->read_ushort();
212 $this->tables = array();
213 for ($i = 0; $i < $this->numTables; $i++) {
214 $record = array();
215 $record['tag'] = $this->read_tag();
216 $record['checksum'] = array($this->read_ushort(), $this->read_ushort());
217 $record['offset'] = $this->read_ulong();
218 $record['length'] = $this->read_ulong();
219 $this->tables[$record['tag']] = $record;
220 }
221 if ($debug)
222 $this->checksumTables();
223 }
224
225 function checksumTables()
226 {
227 // Check the checksums for all tables
228 foreach ($this->tables AS $t) {
229 if ($t['length'] > 0 && $t['length'] < $this->maxStrLenRead) { // 1.02
230 $table = $this->get_chunk($t['offset'], $t['length']);
231 $checksum = $this->calcChecksum($table);
232 if ($t['tag'] == 'head') {
233 $up = unpack('n*', substr($table, 8, 4));
234 $adjustment[0] = $up[1];
235 $adjustment[1] = $up[2];
236 $checksum = $this->sub32($checksum, $adjustment);
237 }
238 $xchecksum = $t['checksum'];
239 if ($xchecksum != $checksum) {
240 throw new MpdfException(sprintf('TTF file "%s": invalid checksum %s table: %s (expected %s)', $this->filename, dechex($checksum[0]) . dechex($checksum[1]), $t['tag'], dechex($xchecksum[0]) . dechex($xchecksum[1])));
241 }
242 }
243 }
244 }
245
246 function sub32($x, $y)
247 {
248 $xlo = $x[1];
249 $xhi = $x[0];
250 $ylo = $y[1];
251 $yhi = $y[0];
252 if ($ylo > $xlo) {
253 $xlo += 1 << 16;
254 $yhi += 1;
255 }
256 $reslo = $xlo - $ylo;
257 if ($yhi > $xhi) {
258 $xhi += 1 << 16;
259 }
260 $reshi = $xhi - $yhi;
261 $reshi = $reshi & 0xFFFF;
262 return array($reshi, $reslo);
263 }
264
265 function calcChecksum($data)
266 {
267 if (strlen($data) % 4) {
268 $data .= str_repeat("\0", (4 - (strlen($data) % 4)));
269 }
270 $len = strlen($data);
271 $hi = 0x0000;
272 $lo = 0x0000;
273 for ($i = 0; $i < $len; $i+=4) {
274 $hi += (ord($data[$i]) << 8) + ord($data[$i + 1]);
275 $lo += (ord($data[$i + 2]) << 8) + ord($data[$i + 3]);
276 $hi += ($lo >> 16) & 0xFFFF;
277 $lo = $lo & 0xFFFF;
278 }
279 $hi = $hi & 0xFFFF; // mPDF 5.7.1
280 return array($hi, $lo);
281 }
282
283 function get_table_pos($tag)
284 {
285 if (!isset($this->tables[$tag])) {
286 return array(0, 0);
287 }
288 $offset = $this->tables[$tag]['offset'];
289 $length = $this->tables[$tag]['length'];
290 return array($offset, $length);
291 }
292
293 function seek($pos)
294 {
295 $this->_pos = $pos;
296 fseek($this->fh, $this->_pos);
297 }
298
299 function skip($delta)
300 {
301 $this->_pos = $this->_pos + $delta;
302 fseek($this->fh, $delta, SEEK_CUR);
303 }
304
305 function seek_table($tag, $offset_in_table = 0)
306 {
307 $tpos = $this->get_table_pos($tag);
308 $this->_pos = $tpos[0] + $offset_in_table;
309 fseek($this->fh, $this->_pos);
310 return $this->_pos;
311 }
312
313 function read_tag()
314 {
315 $this->_pos += 4;
316 return fread($this->fh, 4);
317 }
318
319 function read_short()
320 {
321 $this->_pos += 2;
322 $s = fread($this->fh, 2);
323 $a = (ord($s[0]) << 8) + ord($s[1]);
324 if ($a & (1 << 15)) {
325 $a = ($a - (1 << 16));
326 }
327 return $a;
328 }
329
330 function unpack_short($s)
331 {
332 $a = (ord($s[0]) << 8) + ord($s[1]);
333 if ($a & (1 << 15)) {
334 $a = ($a - (1 << 16));
335 }
336 return $a;
337 }
338
339 function read_ushort()
340 {
341 $this->_pos += 2;
342 $s = fread($this->fh, 2);
343 return (ord($s[0]) << 8) + ord($s[1]);
344 }
345
346 function read_ulong()
347 {
348 $this->_pos += 4;
349 $s = fread($this->fh, 4);
350 // if large uInt32 as an integer, PHP converts it to -ve
351 return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 16777216 = 1<<24
352 }
353
354 function get_ushort($pos)
355 {
356 fseek($this->fh, $pos);
357 $s = fread($this->fh, 2);
358 return (ord($s[0]) << 8) + ord($s[1]);
359 }
360
361 function get_ulong($pos)
362 {
363 fseek($this->fh, $pos);
364 $s = fread($this->fh, 4);
365 // iF large uInt32 as an integer, PHP converts it to -ve
366 return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 16777216 = 1<<24
367 }
368
369 function pack_short($val)
370 {
371 if ($val < 0) {
372 $val = abs($val);
373 $val = ~$val;
374 $val += 1;
375 }
376 return pack("n", $val);
377 }
378
379 function splice($stream, $offset, $value)
380 {
381 return substr($stream, 0, $offset) . $value . substr($stream, $offset + strlen($value));
382 }
383
384 function _set_ushort($stream, $offset, $value)
385 {
386 $up = pack("n", $value);
387 return $this->splice($stream, $offset, $up);
388 }
389
390 function _set_short($stream, $offset, $val)
391 {
392 if ($val < 0) {
393 $val = abs($val);
394 $val = ~$val;
395 $val += 1;
396 }
397 $up = pack("n", $val);
398 return $this->splice($stream, $offset, $up);
399 }
400
401 function get_chunk($pos, $length)
402 {
403 fseek($this->fh, $pos);
404 if ($length < 1) {
405 return '';
406 }
407 return (fread($this->fh, $length));
408 }
409
410 function get_table($tag)
411 {
412 list($pos, $length) = $this->get_table_pos($tag);
413 if ($length == 0) {
414 return '';
415 }
416 fseek($this->fh, $pos);
417 return (fread($this->fh, $length));
418 }
419
420 function add($tag, $data)
421 {
422 if ($tag == 'head') {
423 $data = $this->splice($data, 8, "\0\0\0\0");
424 }
425 $this->otables[$tag] = $data;
426 }
427
428 /////////////////////////////////////////////////////////////////////////////////////////
429 function getCTG($file, $TTCfontID = 0, $debug = false, $useOTL = false)
430 { // mPDF 5.7.1
431 // Only called if font is not to be used as embedded subset i.e. NOT called for SIP/SMP fonts
432 $this->useOTL = $useOTL; // mPDF 5.7.1
433 $this->filename = $file;
434 $this->fh = fopen($file, 'rb');
435
436 if (!$this->fh) {
437 throw new MpdfException('Can\'t open file ' . $file);
438 }
439
440 $this->_pos = 0;
441 $this->charWidths = '';
442 $this->glyphPos = array();
443 $this->charToGlyph = array();
444 $this->tables = array();
445 $this->numTTCFonts = 0;
446 $this->TTCFonts = array();
447 $this->skip(4);
448 if ($TTCfontID > 0) {
449 $this->version = $version = $this->read_ulong(); // TTC Header version now
450 if (!in_array($version, array(0x00010000, 0x00020000))) {
451 throw new MpdfException("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
452 }
453 $this->numTTCFonts = $this->read_ulong();
454 for ($i = 1; $i <= $this->numTTCFonts; $i++) {
455 $this->TTCFonts[$i]['offset'] = $this->read_ulong();
456 }
457 $this->seek($this->TTCFonts[$TTCfontID]['offset']);
458 $this->version = $version = $this->read_ulong(); // TTFont version again now
459 }
460 $this->readTableDirectory($debug);
461
462
463 // cmap - Character to glyph index mapping table
464 $cmap_offset = $this->seek_table("cmap");
465 $this->skip(2);
466 $cmapTableCount = $this->read_ushort();
467 $unicode_cmap_offset = 0;
468 for ($i = 0; $i < $cmapTableCount; $i++) {
469 $platformID = $this->read_ushort();
470 $encodingID = $this->read_ushort();
471 $offset = $this->read_ulong();
472 $save_pos = $this->_pos;
473 if ($platformID == 3 && $encodingID == 1) { // Microsoft, Unicode
474 $format = $this->get_ushort($cmap_offset + $offset);
475 if ($format == 4) {
476 $unicode_cmap_offset = $cmap_offset + $offset;
477 break;
478 }
479 } else if ($platformID == 0) { // Unicode -- assume all encodings are compatible
480 $format = $this->get_ushort($cmap_offset + $offset);
481 if ($format == 4) {
482 $unicode_cmap_offset = $cmap_offset + $offset;
483 break;
484 }
485 }
486 $this->seek($save_pos);
487 }
488
489 $glyphToChar = array();
490 $charToGlyph = array();
491 $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph);
492
493 ///////////////////////////////////
494 // mPDF 5.7.1
495 // Map Unmapped glyphs - from $numGlyphs
496 if ($useOTL) {
497 $this->seek_table("maxp");
498 $this->skip(4);
499 $numGlyphs = $this->read_ushort();
500 $bctr = 0xE000;
501 for ($gid = 1; $gid < $numGlyphs; $gid++) {
502 if (!isset($glyphToChar[$gid])) {
503 while (isset($charToGlyph[$bctr])) {
504 $bctr++;
505 } // Avoid overwriting a glyph already mapped in PUA
506 if ($bctr > 0xF8FF) {
507 throw new MpdfException($file . " : WARNING - Font cannot map all included glyphs into Private Use Area U+E000 - U+F8FF; cannot use useOTL on this font");
508 }
509 $glyphToChar[$gid][] = $bctr;
510 $charToGlyph[$bctr] = $gid;
511 $bctr++;
512 }
513 }
514 }
515 ///////////////////////////////////
516
517 fclose($this->fh);
518 return ($charToGlyph);
519 }
520
521 /////////////////////////////////////////////////////////////////////////////////////////
522 function getTTCFonts($file)
523 {
524 $this->filename = $file;
525 $this->fh = fopen($file, 'rb');
526 if (!$this->fh) {
527 return ('ERROR - Can\'t open file ' . $file);
528 }
529 $this->numTTCFonts = 0;
530 $this->TTCFonts = array();
531 $this->version = $version = $this->read_ulong();
532 if ($version == 0x74746366) {
533 $this->version = $version = $this->read_ulong(); // TTC Header version now
534 if (!in_array($version, array(0x00010000, 0x00020000)))
535 return("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
536 }
537 else {
538 return("ERROR - Not a TrueType Collection: version=" . $version . " - " . $file);
539 }
540 $this->numTTCFonts = $this->read_ulong();
541 for ($i = 1; $i <= $this->numTTCFonts; $i++) {
542 $this->TTCFonts[$i]['offset'] = $this->read_ulong();
543 }
544 }
545
546 /////////////////////////////////////////////////////////////////////////////////////////
547 /////////////////////////////////////////////////////////////////////////////////////////
548
549 function extractInfo($debug = false, $BMPonly = false, $useOTL = 0)
550 {
551 // Values are all set to 0 or blank at start of getMetrics
552 ///////////////////////////////////
553 // name - Naming table
554 ///////////////////////////////////
555 $name_offset = $this->seek_table("name");
556 $format = $this->read_ushort();
557 if ($format != 0 && $format != 1)
558 throw new MpdfException("Unknown name table format " . $format);
559 $numRecords = $this->read_ushort();
560 $string_data_offset = $name_offset + $this->read_ushort();
561 $names = array(1 => '', 2 => '', 3 => '', 4 => '', 6 => '');
562 $K = array_keys($names);
563 $nameCount = count($names);
564 for ($i = 0; $i < $numRecords; $i++) {
565 $platformId = $this->read_ushort();
566 $encodingId = $this->read_ushort();
567 $languageId = $this->read_ushort();
568 $nameId = $this->read_ushort();
569 $length = $this->read_ushort();
570 $offset = $this->read_ushort();
571 if (!in_array($nameId, $K))
572 continue;
573 $N = '';
574 if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name
575 $opos = $this->_pos;
576 $this->seek($string_data_offset + $offset);
577 if ($length % 2 != 0)
578 throw new MpdfException("PostScript name is UTF-16BE string of odd length");
579 $length /= 2;
580 $N = '';
581 while ($length > 0) {
582 $char = $this->read_ushort();
583 $N .= (chr($char));
584 $length -= 1;
585 }
586 $this->_pos = $opos;
587 $this->seek($opos);
588 } else if ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name
589 $opos = $this->_pos;
590 $N = $this->get_chunk($string_data_offset + $offset, $length);
591 $this->_pos = $opos;
592 $this->seek($opos);
593 }
594 if ($N && $names[$nameId] == '') {
595 $names[$nameId] = $N;
596 $nameCount -= 1;
597 if ($nameCount == 0)
598 break;
599 }
600 }
601 if ($names[6])
602 $psName = $names[6];
603 else if ($names[4])
604 $psName = preg_replace('/ /', '-', $names[4]);
605 else if ($names[1])
606 $psName = preg_replace('/ /', '-', $names[1]);
607 else
608 $psName = array();
609 if (!$psName)
610 throw new MpdfException("Could not find PostScript font name: " . $this->filename);
611 // CHECK IF psName valid (PadaukBook contains illegal characters in Name ID 6 i.e. Postscript Name)
612 $psNameInvalid = false;
613 for ($i = 0; $i < count($psName); $i++) {
614 $c = $psName[$i];
615 $oc = ord($c);
616 if ($oc > 126 || strpos(' [](){}<>/%', $c) !== false) {
617 //throw new MpdfException("psName=".$psName." contains invalid character ".$c." ie U+".ord(c));
618 $psNameInvalid = true;
619 break;
620 }
621 }
622
623 if ($psNameInvalid && $names[4])
624 $psName = preg_replace('/ /', '-', $names[4]);
625
626
627 $this->name = $psName;
628 if ($names[1]) {
629 $this->familyName = $names[1];
630 } else {
631 $this->familyName = $psName;
632 }
633 if ($names[2]) {
634 $this->styleName = $names[2];
635 } else {
636 $this->styleName = 'Regular';
637 }
638 if ($names[4]) {
639 $this->fullName = $names[4];
640 } else {
641 $this->fullName = $psName;
642 }
643 if ($names[3]) {
644 $this->uniqueFontID = $names[3];
645 } else {
646 $this->uniqueFontID = $psName;
647 }
648
649 if (!$psNameInvalid && $names[6]) {
650 $this->fullName = $names[6];
651 }
652
653 ///////////////////////////////////
654 // head - Font header table
655 ///////////////////////////////////
656 $this->seek_table("head");
657 if ($debug) {
658 $ver_maj = $this->read_ushort();
659 $ver_min = $this->read_ushort();
660 if ($ver_maj != 1)
661 throw new MpdfException('Unknown head table version ' . $ver_maj . '.' . $ver_min);
662 $this->fontRevision = $this->read_ushort() . $this->read_ushort();
663
664 $this->skip(4);
665 $magic = $this->read_ulong();
666 if ($magic != 0x5F0F3CF5)
667 throw new MpdfException('Invalid head table magic ' . $magic);
668 $this->skip(2);
669 }
670 else {
671 $this->skip(18);
672 }
673 $this->unitsPerEm = $unitsPerEm = $this->read_ushort();
674 $scale = 1000 / $unitsPerEm;
675 $this->skip(16);
676 $xMin = $this->read_short();
677 $yMin = $this->read_short();
678 $xMax = $this->read_short();
679 $yMax = $this->read_short();
680 $this->bbox = array(($xMin * $scale), ($yMin * $scale), ($xMax * $scale), ($yMax * $scale));
681
682 $this->skip(3 * 2);
683 $indexToLocFormat = $this->read_ushort();
684 $glyphDataFormat = $this->read_ushort();
685 if ($glyphDataFormat != 0) {
686 throw new MpdfException('Unknown glyph data format ' . $glyphDataFormat);
687 }
688
689 ///////////////////////////////////
690 // hhea metrics table
691 ///////////////////////////////////
692 if (isset($this->tables["hhea"])) {
693 $this->seek_table("hhea");
694 $this->skip(4);
695 $hheaAscender = $this->read_short();
696 $hheaDescender = $this->read_short();
697 $hheaLineGap = $this->read_short(); // mPDF 6
698 $hheaAdvanceWidthMax = $this->read_ushort(); // mPDF 6
699 $this->hheaascent = ($hheaAscender * $scale);
700 $this->hheadescent = ($hheaDescender * $scale);
701 $this->hhealineGap = ($hheaLineGap * $scale); // mPDF 6
702 $this->advanceWidthMax = ($hheaAdvanceWidthMax * $scale); // mPDF 6
703 }
704
705 ///////////////////////////////////
706 // OS/2 - OS/2 and Windows metrics table
707 ///////////////////////////////////
708 $use_typo_metrics = false;
709 if (isset($this->tables["OS/2"])) {
710 $this->seek_table("OS/2");
711 $version = $this->read_ushort();
712 $this->skip(2);
713 $usWeightClass = $this->read_ushort();
714 $this->skip(2);
715 $fsType = $this->read_ushort();
716 if ($fsType == 0x0002 || ($fsType & 0x0300) != 0) {
717 $this->restrictedUse = true;
718 }
719
720 // mPDF 6
721 $this->skip(16);
722 $yStrikeoutSize = $this->read_short();
723 $yStrikeoutPosition = $this->read_short();
724 $this->strikeoutSize = ($yStrikeoutSize * $scale);
725 $this->strikeoutPosition = ($yStrikeoutPosition * $scale);
726
727 $sF = $this->read_short();
728 $this->sFamilyClass = ($sF >> 8);
729 $this->sFamilySubClass = ($sF & 0xFF);
730 $this->_pos += 10; //PANOSE = 10 byte length
731 $panose = fread($this->fh, 10);
732 $this->panose = array();
733 for ($p = 0; $p < strlen($panose); $p++) {
734 $this->panose[] = ord($panose[$p]);
735 }
736 //$this->skip(26);
737 // mPDF 6
738 $this->skip(20);
739 $fsSelection = $this->read_ushort();
740 $use_typo_metrics = (($fsSelection & 0x80) == 0x80); // bit#7 = USE_TYPO_METRICS
741 $this->skip(4);
742
743 $sTypoAscender = $this->read_short();
744 $sTypoDescender = $this->read_short();
745 $sTypoLineGap = $this->read_short(); // mPDF 6
746 if ($sTypoAscender)
747 $this->typoAscender = ($sTypoAscender * $scale); // mPDF 6
748 if ($sTypoDescender)
749 $this->typoDescender = ($sTypoDescender * $scale); // mPDF 6
750 if ($sTypoLineGap)
751 $this->typoLineGap = ($sTypoLineGap * $scale); // mPDF 6
752
753 $usWinAscent = $this->read_ushort(); // mPDF 6
754 $usWinDescent = $this->read_ushort(); // mPDF 6
755 if ($usWinAscent)
756 $this->usWinAscent = ($usWinAscent * $scale); // mPDF 6
757 if ($usWinDescent)
758 $this->usWinDescent = ($usWinDescent * $scale); // mPDF 6
759
760 if ($version > 1) {
761 $this->skip(8); // mPDF 6
762 $sxHeight = $this->read_short();
763 $this->xHeight = ($sxHeight * $scale);
764 $sCapHeight = $this->read_short();
765 $this->capHeight = ($sCapHeight * $scale);
766 }
767 } else {
768 $usWeightClass = 400;
769 }
770 $this->stemV = 50 + intval(pow(($usWeightClass / 65.0), 2));
771
772
773 // FONT DESCRIPTOR METRICS
774 if (_FONT_DESCRIPTOR == 'winTypo') {
775 $this->ascent = $this->typoAscender;
776 $this->descent = $this->typoDescender;
777 $this->lineGap = $this->typoLineGap;
778 } else if (_FONT_DESCRIPTOR == 'mac') {
779 $this->ascent = $this->hheaascent;
780 $this->descent = $this->hheadescent;
781 $this->lineGap = $this->hhealineGap;
782 } else { // if (_FONT_DESCRIPTOR == 'win') { // default
783 $this->ascent = $this->usWinAscent;
784 $this->descent = -$this->usWinDescent;
785 $this->lineGap = 0;
786
787 /* Special case - if either the winAscent or winDescent are greater than the
788 font bounding box yMin yMax, then reduce them accordingly.
789 This works with Myanmar Text (Windows 8 version) to give a
790 line-height normal that is equivalent to that produced in browsers.
791 Also Khmer OS = compatible with MSWord, Wordpad and browser. */
792 if ($this->ascent > $this->bbox[3]) {
793 $this->ascent = $this->bbox[3];
794 }
795 if ($this->descent < $this->bbox[1]) {
796 $this->descent = $this->bbox[1];
797 }
798
799
800 /* Override case - if the USE_TYPO_METRICS bit is set on OS/2 fsSelection
801 this is telling the font to use the sTypo values and not the usWinAscent values.
802 This works as a fix with Cambria Math to give a normal line-height;
803 at present, this is the only font I have found with this bit set;
804 although note that MS WordPad and windows FF browser uses the big line-height from winAscent
805 but Word 2007 get it right . */
806 if ($use_typo_metrics && $this->typoAscender) {
807 $this->ascent = $this->typoAscender;
808 $this->descent = $this->typoDescender;
809 $this->lineGap = $this->typoLineGap;
810 }
811 }
812
813
814 ///////////////////////////////////
815 // post - PostScript table
816 ///////////////////////////////////
817 $this->seek_table("post");
818 if ($debug) {
819 $ver_maj = $this->read_ushort();
820 $ver_min = $this->read_ushort();
821 if ($ver_maj < 1 || $ver_maj > 4)
822 throw new MpdfException('Unknown post table version ' . $ver_maj);
823 }
824 else {
825 $this->skip(4);
826 }
827 $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0;
828 $this->underlinePosition = $this->read_short() * $scale;
829 $this->underlineThickness = $this->read_short() * $scale;
830 $isFixedPitch = $this->read_ulong();
831
832 $this->flags = 4;
833
834 if ($this->italicAngle != 0)
835 $this->flags = $this->flags | 64;
836 if ($usWeightClass >= 600)
837 $this->flags = $this->flags | 262144;
838 if ($isFixedPitch)
839 $this->flags = $this->flags | 1;
840
841 ///////////////////////////////////
842 // hhea - Horizontal header table
843 ///////////////////////////////////
844 $this->seek_table("hhea");
845 if ($debug) {
846 $ver_maj = $this->read_ushort();
847 $ver_min = $this->read_ushort();
848 if ($ver_maj != 1)
849 throw new MpdfException('Unknown hhea table version ' . $ver_maj);
850 $this->skip(28);
851 }
852 else {
853 $this->skip(32);
854 }
855
856 $metricDataFormat = $this->read_ushort();
857
858 if ($metricDataFormat != 0) {
859 throw new MpdfException('Unknown horizontal metric data format ' . $metricDataFormat);
860 }
861
862 $numberOfHMetrics = $this->read_ushort();
863
864 if ($numberOfHMetrics == 0) {
865 throw new MpdfException('Number of horizontal metrics is 0');
866 }
867
868 ///////////////////////////////////
869 // maxp - Maximum profile table
870 ///////////////////////////////////
871 $this->seek_table("maxp");
872 if ($debug) {
873 $ver_maj = $this->read_ushort();
874 $ver_min = $this->read_ushort();
875 if ($ver_maj != 1) {
876 throw new MpdfException('Unknown maxp table version ' . $ver_maj);
877 }
878 }
879 else {
880 $this->skip(4);
881 }
882 $numGlyphs = $this->read_ushort();
883
884
885 ///////////////////////////////////
886 // cmap - Character to glyph index mapping table
887 ///////////////////////////////////
888 $cmap_offset = $this->seek_table("cmap");
889 $this->skip(2);
890 $cmapTableCount = $this->read_ushort();
891 $unicode_cmap_offset = 0;
892 for ($i = 0; $i < $cmapTableCount; $i++) {
893 $platformID = $this->read_ushort();
894 $encodingID = $this->read_ushort();
895 $offset = $this->read_ulong();
896 $save_pos = $this->_pos;
897 if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
898 $format = $this->get_ushort($cmap_offset + $offset);
899 if ($format == 4) {
900 if (!$unicode_cmap_offset)
901 $unicode_cmap_offset = $cmap_offset + $offset;
902 if ($BMPonly)
903 break;
904 }
905 }
906 // Microsoft, Unicode Format 12 table HKCS
907 else if ((($platformID == 3 && $encodingID == 10) || $platformID == 0) && !$BMPonly) {
908 $format = $this->get_ushort($cmap_offset + $offset);
909 if ($format == 12) {
910 $unicode_cmap_offset = $cmap_offset + $offset;
911 break;
912 }
913 }
914 $this->seek($save_pos);
915 }
916
917 if (!$unicode_cmap_offset) {
918 throw new MpdfException('Font (' . $this->filename . ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
919 }
920
921 $sipset = false;
922 $smpset = false;
923
924 // mPDF 5.7.1
925 $this->rtlPUAstr = '';
926 //$this->rtlPUAarr = array();
927 $this->GSUBScriptLang = array();
928 $this->GSUBFeatures = array();
929 $this->GSUBLookups = array();
930 $this->GPOSScriptLang = array();
931 $this->GPOSFeatures = array();
932 $this->GPOSLookups = array();
933 $this->glyphIDtoUni = '';
934
935 // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
936 if ($format == 12 && !$BMPonly) {
937 $this->maxUniChar = 0;
938 $this->seek($unicode_cmap_offset + 4);
939 $length = $this->read_ulong();
940 $limit = $unicode_cmap_offset + $length;
941 $this->skip(4);
942
943 $nGroups = $this->read_ulong();
944
945 $glyphToChar = array();
946 $charToGlyph = array();
947 for ($i = 0; $i < $nGroups; $i++) {
948 $startCharCode = $this->read_ulong();
949 $endCharCode = $this->read_ulong();
950 $startGlyphCode = $this->read_ulong();
951 // ZZZ98
952 if ($endCharCode > 0x20000 && $endCharCode < 0x2FFFF) {
953 $sipset = true;
954 } else if ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) {
955 $smpset = true;
956 }
957 $offset = 0;
958 for ($unichar = $startCharCode; $unichar <= $endCharCode; $unichar++) {
959 $glyph = $startGlyphCode + $offset;
960 $offset++;
961 // ZZZ98
962 if ($unichar < 0x30000) {
963 $charToGlyph[$unichar] = $glyph;
964 $this->maxUniChar = max($unichar, $this->maxUniChar);
965 $glyphToChar[$glyph][] = $unichar;
966 }
967 }
968 }
969 } else {
970
971 $glyphToChar = array();
972 $charToGlyph = array();
973 $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph);
974 }
975 $this->sipset = $sipset;
976 $this->smpset = $smpset;
977
978 ///////////////////////////////////
979 // mPDF 5.7.1
980 // Map Unmapped glyphs (or glyphs mapped to upper PUA U+F00000 onwards i.e. > U+2FFFF) - from $numGlyphs
981 if ($this->useOTL) {
982 $bctr = 0xE000;
983 for ($gid = 1; $gid < $numGlyphs; $gid++) {
984 if (!isset($glyphToChar[$gid])) {
985 while (isset($charToGlyph[$bctr])) {
986 $bctr++;
987 } // Avoid overwriting a glyph already mapped in PUA
988 // ZZZ98
989 if (($bctr > 0xF8FF) && ($bctr < 0x2CEB0)) {
990 if (!$BMPonly) {
991 $bctr = 0x2CEB0; // Use unassigned area 0x2CEB0 to 0x2F7FF (space for 10,000 characters)
992 $this->sipset = $sipset = true; // forces subsetting; also ensure charwidths are saved
993 while (isset($charToGlyph[$bctr])) {
994 $bctr++;
995 }
996 } else {
997 throw new MpdfException($names[1] . " : WARNING - The font does not have enough space to map all (unmapped) included glyphs into Private Use Area U+E000 - U+F8FF");
998 }
999 }
1000 $glyphToChar[$gid][] = $bctr;
1001 $charToGlyph[$bctr] = $gid;
1002 $this->maxUniChar = max($bctr, $this->maxUniChar);
1003 $bctr++;
1004 }
1005 }
1006 }
1007
1008 $this->glyphToChar = $glyphToChar;
1009 ///////////////////////////////////
1010 // mPDF 5.7.1 OpenType Layout tables
1011 $this->GSUBScriptLang = array();
1012 $this->rtlPUAstr = '';
1013 //$this->rtlPUAarr = array();
1014 if ($useOTL) {
1015 $this->_getGDEFtables();
1016 list($this->GSUBScriptLang, $this->GSUBFeatures, $this->GSUBLookups, $this->rtlPUAstr) = $this->_getGSUBtables();
1017 // , $this->rtlPUAarr not needed
1018 list($this->GPOSScriptLang, $this->GPOSFeatures, $this->GPOSLookups) = $this->_getGPOStables();
1019 $this->glyphIDtoUni = str_pad('', 256 * 256 * 3, "\x00");
1020 foreach ($glyphToChar AS $gid => $arr) {
1021 if (isset($glyphToChar[$gid][0])) {
1022 $char = $glyphToChar[$gid][0];
1023
1024 if ($char != 0 && $char != 65535) {
1025 $this->glyphIDtoUni[$gid * 3] = chr($char >> 16);
1026 $this->glyphIDtoUni[$gid * 3 + 1] = chr(($char >> 8) & 0xFF);
1027 $this->glyphIDtoUni[$gid * 3 + 2] = chr($char & 0xFF);
1028 }
1029 }
1030 }
1031 }
1032 ///////////////////////////////////
1033 // if xHeight and/or CapHeight are not available from OS/2 (e.g. eraly versions)
1034 // Calculate from yMax of 'x' or 'H' Glyphs...
1035 if ($this->xHeight == 0) {
1036 if (isset($charToGlyph[0x78])) {
1037 $gidx = $charToGlyph[0x78]; // U+0078 (LATIN SMALL LETTER X)
1038 $start = $this->seek_table('loca');
1039 if ($indexToLocFormat == 0) {
1040 $this->skip($gidx * 2);
1041 $locax = $this->read_ushort() * 2;
1042 } else if ($indexToLocFormat == 1) {
1043 $this->skip($gidx * 4);
1044 $locax = $this->read_ulong();
1045 }
1046 $start = $this->seek_table('glyf');
1047 $this->skip($locax);
1048 $this->skip(8);
1049 $yMaxx = $this->read_short();
1050 $this->xHeight = $yMaxx * $scale;
1051 }
1052 }
1053 if ($this->capHeight == 0) {
1054 if (isset($charToGlyph[0x48])) {
1055 $gidH = $charToGlyph[0x48]; // U+0048 (LATIN CAPITAL LETTER H)
1056 $start = $this->seek_table('loca');
1057 if ($indexToLocFormat == 0) {
1058 $this->skip($gidH * 2);
1059 $locaH = $this->read_ushort() * 2;
1060 } else if ($indexToLocFormat == 1) {
1061 $this->skip($gidH * 4);
1062 $locaH = $this->read_ulong();
1063 }
1064 $start = $this->seek_table('glyf');
1065 $this->skip($locaH);
1066 $this->skip(8);
1067 $yMaxH = $this->read_short();
1068 $this->capHeight = $yMaxH * $scale;
1069 } else {
1070 $this->capHeight = $this->ascent;
1071 } // final default is to set it = to Ascent
1072 }
1073
1074
1075
1076
1077 ///////////////////////////////////
1078 // hmtx - Horizontal metrics table
1079 ///////////////////////////////////
1080 $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
1081
1082 ///////////////////////////////////
1083 // kern - Kerning pair table
1084 ///////////////////////////////////
1085 // Recognises old form of Kerning table - as required by Windows - Format 0 only
1086 $kern_offset = $this->seek_table("kern");
1087 $version = $this->read_ushort();
1088 $nTables = $this->read_ushort();
1089 // subtable header
1090 $sversion = $this->read_ushort();
1091 $slength = $this->read_ushort();
1092 $scoverage = $this->read_ushort();
1093 $format = $scoverage >> 8;
1094 if ($kern_offset && $version == 0 && $format == 0) {
1095 // Format 0
1096 $nPairs = $this->read_ushort();
1097 $this->skip(6);
1098 for ($i = 0; $i < $nPairs; $i++) {
1099 $left = $this->read_ushort();
1100 $right = $this->read_ushort();
1101 $val = $this->read_short();
1102 if (count($glyphToChar[$left]) == 1 && count($glyphToChar[$right]) == 1) {
1103 if ($left != 32 && $right != 32) {
1104 $this->kerninfo[$glyphToChar[$left][0]][$glyphToChar[$right][0]] = intval($val * $scale);
1105 }
1106 }
1107 }
1108 }
1109 }
1110
1111 /////////////////////////////////////////////////////////////////////////////////////////
1112 function _getGDEFtables()
1113 {
1114 ///////////////////////////////////
1115 // GDEF - Glyph Definition
1116 ///////////////////////////////////
1117 // http://www.microsoft.com/typography/otspec/gdef.htm
1118 if (isset($this->tables["GDEF"])) {
1119 $gdef_offset = $this->seek_table("GDEF");
1120 // ULONG Version of the GDEF table-currently 0x00010000
1121 $ver_maj = $this->read_ushort();
1122 $ver_min = $this->read_ushort();
1123 $GlyphClassDef_offset = $this->read_ushort();
1124 $AttachList_offset = $this->read_ushort();
1125 $LigCaretList_offset = $this->read_ushort();
1126 $MarkAttachClassDef_offset = $this->read_ushort();
1127 // Version 0x00010002 of GDEF header contains additional Offset to a list defining mark glyph set definitions (MarkGlyphSetDef)
1128 if ($ver_min == 2) {
1129 $MarkGlyphSetsDef_offset = $this->read_ushort();
1130 }
1131
1132 // GlyphClassDef
1133 if ($GlyphClassDef_offset) {
1134 $this->seek($gdef_offset + $GlyphClassDef_offset);
1135 /*
1136 1 Base glyph (single character, spacing glyph)
1137 2 Ligature glyph (multiple character, spacing glyph)
1138 3 Mark glyph (non-spacing combining glyph)
1139 4 Component glyph (part of single character, spacing glyph)
1140 */
1141 $GlyphByClass = $this->_getClassDefinitionTable();
1142 } else {
1143 $GlyphByClass = array();
1144 }
1145
1146 if (isset($GlyphByClass[1]) && count($GlyphByClass[1]) > 0) {
1147 $this->GlyphClassBases = ' ' . implode('| ', $GlyphByClass[1]);
1148 } else {
1149 $this->GlyphClassBases = '';
1150 }
1151 if (isset($GlyphByClass[2]) && count($GlyphByClass[2]) > 0) {
1152 $this->GlyphClassLigatures = ' ' . implode('| ', $GlyphByClass[2]);
1153 } else {
1154 $this->GlyphClassLigatures = '';
1155 }
1156 if (isset($GlyphByClass[3]) && count($GlyphByClass[3]) > 0) {
1157 $this->GlyphClassMarks = ' ' . implode('| ', $GlyphByClass[3]);
1158 } else {
1159 $this->GlyphClassMarks = '';
1160 }
1161 if (isset($GlyphByClass[4]) && count($GlyphByClass[4]) > 0) {
1162 $this->GlyphClassComponents = ' ' . implode('| ', $GlyphByClass[4]);
1163 } else {
1164 $this->GlyphClassComponents = '';
1165 }
1166
1167 if (isset($GlyphByClass[3]) && count($GlyphByClass[3]) > 0) {
1168 $Marks = $GlyphByClass[3];
1169 } // to use for MarkAttachmentType
1170 else {
1171 $Marks = array();
1172 }
1173
1174
1175
1176 /* Required for GPOS
1177 // Attachment List
1178 if ($AttachList_offset) {
1179 $this->seek($gdef_offset+$AttachList_offset );
1180 }
1181 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.
1182
1183 The Attachment List table (AttachList) may be used to cache attachment point coordinates along with glyph bitmaps.
1184
1185 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.
1186 AttachList table
1187 Type Name Description
1188 Offset Coverage Offset to Coverage table - from beginning of AttachList table
1189 uint16 GlyphCount Number of glyphs with attachment points
1190 Offset AttachPoint[GlyphCount] Array of offsets to AttachPoint tables-from beginning of AttachList table-in Coverage Index order
1191
1192 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.
1193
1194 AttachPoint table
1195 Type Name Description
1196 uint16 PointCount Number of attachment points on this glyph
1197 uint16 PointIndex[PointCount] Array of contour point indices -in increasing numerical order
1198
1199 See Example 3 - http://www.microsoft.com/typography/otspec/gdef.htm
1200 */
1201
1202
1203 // Ligature Caret List
1204 // The Ligature Caret List table (LigCaretList) defines caret positions for all the ligatures in a font.
1205 // Not required for mDPF
1206 // MarkAttachmentType
1207 if ($MarkAttachClassDef_offset) {
1208 $this->seek($gdef_offset + $MarkAttachClassDef_offset);
1209 $MarkAttachmentTypes = $this->_getClassDefinitionTable();
1210 foreach ($MarkAttachmentTypes AS $class => $glyphs) {
1211
1212 if (is_array($Marks) && count($Marks)) {
1213 $mat = array_diff($Marks, $MarkAttachmentTypes[$class]);
1214 sort($mat, SORT_STRING);
1215 } else {
1216 $mat = array();
1217 }
1218
1219 $this->MarkAttachmentType[$class] = ' ' . implode('| ', $mat);
1220 }
1221 } else {
1222 $this->MarkAttachmentType = array();
1223 }
1224
1225
1226 // MarkGlyphSets only in Version 0x00010002 of GDEF
1227 if ($ver_min == 2 && $MarkGlyphSetsDef_offset) {
1228 $this->seek($gdef_offset + $MarkGlyphSetsDef_offset);
1229 $MarkSetTableFormat = $this->read_ushort();
1230 $MarkSetCount = $this->read_ushort();
1231 $MarkSetOffset = array();
1232 for ($i = 0; $i < $MarkSetCount; $i++) {
1233 $MarkSetOffset[] = $this->read_ulong();
1234 }
1235 for ($i = 0; $i < $MarkSetCount; $i++) {
1236 $this->seek($MarkSetOffset[$i]);
1237 $glyphs = $this->_getCoverage();
1238 $this->MarkGlyphSets[$i] = ' ' . implode('| ', $glyphs);
1239 }
1240 } else {
1241 $this->MarkGlyphSets = array();
1242 }
1243 } else {
1244 throw new MpdfException('Warning - You cannot set this font (' . $this->filename . ') to use OTL, as it does not include OTL tables (or at least, not a GDEF table).');
1245 }
1246
1247 //=====================================================================================
1248 //=====================================================================================
1249 //=====================================================================================
1250 $GSUB_offset = 0;
1251 $GPOS_offset = 0;
1252 $GSUB_length = 0;
1253 $s = '';
1254 if (isset($this->tables["GSUB"])) {
1255 $GSUB_offset = $this->seek_table("GSUB");
1256 $GSUB_length = $this->tables["GSUB"]['length'];
1257 $s .= fread($this->fh, $this->tables["GSUB"]['length']);
1258 }
1259 if (isset($this->tables["GPOS"])) {
1260 $GPOS_offset = $this->seek_table("GPOS");
1261 $s .= fread($this->fh, $this->tables["GPOS"]['length']);
1262 }
1263 if ($s)
1264 file_put_contents(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GSUBGPOStables.dat', $s);
1265
1266 //=====================================================================================
1267 //=====================================================================================
1268
1269 $s = '<?php
1270 $GSUB_offset = ' . $GSUB_offset . ';
1271 $GPOS_offset = ' . $GPOS_offset . ';
1272 $GSUB_length = ' . $GSUB_length . ';
1273 $GlyphClassBases = \'' . $this->GlyphClassBases . '\';
1274 $GlyphClassMarks = \'' . $this->GlyphClassMarks . '\';
1275 $GlyphClassLigatures = \'' . $this->GlyphClassLigatures . '\';
1276 $GlyphClassComponents = \'' . $this->GlyphClassComponents . '\';
1277 $MarkGlyphSets = ' . var_export($this->MarkGlyphSets, true) . ';
1278 $MarkAttachmentType = ' . var_export($this->MarkAttachmentType, true) . ';
1279 ?>';
1280
1281
1282 file_put_contents(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GDEFdata.php', $s);
1283
1284 //=====================================================================================
1285 //echo $this->GlyphClassMarks ; exit;
1286 //print_r($GlyphClass); exit;
1287 //print_r($GlyphByClass); exit;
1288 }
1289
1290 function _getClassDefinitionTable()
1291 {
1292
1293 // NB Any glyph not included in the range of covered GlyphIDs automatically belongs to Class 0. This is not returned by this function
1294 $ClassFormat = $this->read_ushort();
1295 $GlyphByClass = array();
1296 if ($ClassFormat == 1) {
1297 $StartGlyph = $this->read_ushort();
1298 $GlyphCount = $this->read_ushort();
1299 for ($i = 0; $i < $GlyphCount; $i++) {
1300 $gid = $StartGlyph + $i;
1301 $class = $this->read_ushort();
1302 // Several fonts (mainly dejavu.../Freeserif etc) have a MarkAttachClassDef Format 1, where StartGlyph is 0 and GlyphCount is 1
1303 // This doesn't seem to do anything useful?
1304 // Freeserif does not have $this->glyphToChar[0] allocated and would throw an error, so check if isset:
1305 if (isset($this->glyphToChar[$gid][0])) {
1306 $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]);
1307 }
1308 }
1309 } else if ($ClassFormat == 2) {
1310 $tableCount = $this->read_ushort();
1311 for ($i = 0; $i < $tableCount; $i++) {
1312 $startGlyphID = $this->read_ushort();
1313 $endGlyphID = $this->read_ushort();
1314 $class = $this->read_ushort();
1315 for ($gid = $startGlyphID; $gid <= $endGlyphID; $gid++) {
1316 if (isset($this->glyphToChar[$gid][0])) {
1317 $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]);
1318 }
1319 }
1320 }
1321 }
1322 foreach ($GlyphByClass AS $class => $glyphs) {
1323 sort($GlyphByClass[$class], SORT_STRING); // SORT makes it easier to read in development ? order not important ???
1324 }
1325 ksort($GlyphByClass);
1326 return $GlyphByClass;
1327 }
1328
1329 function _getGSUBtables()
1330 {
1331 ///////////////////////////////////
1332 // GSUB - Glyph Substitution
1333 ///////////////////////////////////
1334 if (isset($this->tables["GSUB"])) {
1335 $ffeats = array();
1336 $gsub_offset = $this->seek_table("GSUB");
1337 $this->skip(4);
1338 $ScriptList_offset = $gsub_offset + $this->read_ushort();
1339 $FeatureList_offset = $gsub_offset + $this->read_ushort();
1340 $LookupList_offset = $gsub_offset + $this->read_ushort();
1341
1342 // ScriptList
1343 $this->seek($ScriptList_offset);
1344 $ScriptCount = $this->read_ushort();
1345 for ($i = 0; $i < $ScriptCount; $i++) {
1346 $ScriptTag = $this->read_tag(); // = "beng", "deva" etc.
1347 $ScriptTableOffset = $this->read_ushort();
1348 $ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset;
1349 }
1350
1351 // Script Table
1352 foreach ($ffeats AS $t => $o) {
1353 $ls = array();
1354 $this->seek($o);
1355 $DefLangSys_offset = $this->read_ushort();
1356 if ($DefLangSys_offset > 0) {
1357 $ls['DFLT'] = $DefLangSys_offset + $o;
1358 }
1359 $LangSysCount = $this->read_ushort();
1360 for ($i = 0; $i < $LangSysCount; $i++) {
1361 $LangTag = $this->read_tag(); // =
1362 $LangTableOffset = $this->read_ushort();
1363 $ls[$LangTag] = $o + $LangTableOffset;
1364 }
1365 $ffeats[$t] = $ls;
1366 }
1367 //print_r($ffeats); exit;
1368 // Get FeatureIndexList
1369 // LangSys Table - from first listed langsys
1370 foreach ($ffeats AS $st => $scripts) {
1371 foreach ($scripts AS $t => $o) {
1372 $FeatureIndex = array();
1373 $langsystable_offset = $o;
1374 $this->seek($langsystable_offset);
1375 $LookUpOrder = $this->read_ushort(); //==NULL
1376 $ReqFeatureIndex = $this->read_ushort();
1377 if ($ReqFeatureIndex != 0xFFFF) {
1378 $FeatureIndex[] = $ReqFeatureIndex;
1379 }
1380 $FeatureCount = $this->read_ushort();
1381 for ($i = 0; $i < $FeatureCount; $i++) {
1382 $FeatureIndex[] = $this->read_ushort(); // = index of feature
1383 }
1384 $ffeats[$st][$t] = $FeatureIndex;
1385 }
1386 }
1387 //print_r($ffeats); exit;
1388 // Feauture List => LookupListIndex es
1389 $this->seek($FeatureList_offset);
1390 $FeatureCount = $this->read_ushort();
1391 $Feature = array();
1392 for ($i = 0; $i < $FeatureCount; $i++) {
1393 $tag = $this->read_tag();
1394 if ($tag == 'smcp') {
1395 $this->hassmallcapsGSUB = true;
1396 }
1397 $Feature[$i] = array('tag' => $tag);
1398 $Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort();
1399 }
1400 for ($i = 0; $i < $FeatureCount; $i++) {
1401 $this->seek($Feature[$i]['offset']);
1402 $this->read_ushort(); // null [FeatureParams]
1403 $Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort();
1404 $Feature[$i]['LookupListIndex'] = array();
1405 for ($c = 0; $c < $Lookupcount; $c++) {
1406 $Feature[$i]['LookupListIndex'][] = $this->read_ushort();
1407 }
1408 }
1409
1410 //print_r($Feature); exit;
1411
1412 foreach ($ffeats AS $st => $scripts) {
1413 foreach ($scripts AS $t => $o) {
1414 $FeatureIndex = $ffeats[$st][$t];
1415 foreach ($FeatureIndex AS $k => $fi) {
1416 $ffeats[$st][$t][$k] = $Feature[$fi];
1417 }
1418 }
1419 }
1420 //=====================================================================================
1421 $gsub = array();
1422 $GSUBScriptLang = array();
1423 foreach ($ffeats AS $st => $scripts) {
1424 foreach ($scripts AS $t => $langsys) {
1425 $lg = array();
1426 foreach ($langsys AS $ft) {
1427 $lg[$ft['LookupListIndex'][0]] = $ft;
1428 }
1429 // list of Lookups in order they need to be run i.e. order listed in Lookup table
1430 ksort($lg);
1431 foreach ($lg AS $ft) {
1432 $gsub[$st][$t][$ft['tag']] = $ft['LookupListIndex'];
1433 }
1434 if (!isset($GSUBScriptLang[$st])) {
1435 $GSUBScriptLang[$st] = '';
1436 }
1437 $GSUBScriptLang[$st] .= $t . ' ';
1438 }
1439 }
1440
1441 //print_r($gsub); exit;
1442 //=====================================================================================
1443 // Get metadata and offsets for whole Lookup List table
1444 $this->seek($LookupList_offset);
1445 $LookupCount = $this->read_ushort();
1446 $GSLookup = array();
1447 $Offsets = array();
1448 $SubtableCount = array();
1449 for ($i = 0; $i < $LookupCount; $i++) {
1450 $Offsets[$i] = $LookupList_offset + $this->read_ushort();
1451 }
1452 for ($i = 0; $i < $LookupCount; $i++) {
1453 $this->seek($Offsets[$i]);
1454 $GSLookup[$i]['Type'] = $this->read_ushort();
1455 $GSLookup[$i]['Flag'] = $flag = $this->read_ushort();
1456 $GSLookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort();
1457 for ($c = 0; $c < $SubtableCount[$i]; $c++) {
1458 $GSLookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort();
1459 }
1460 // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure
1461 if (($flag & 0x0010) == 0x0010) {
1462 $GSLookup[$i]['MarkFilteringSet'] = $this->read_ushort();
1463 } else {
1464 $GSLookup[$i]['MarkFilteringSet'] = '';
1465 }
1466
1467 // Lookup Type 7: Extension
1468 if ($GSLookup[$i]['Type'] == 7) {
1469 // Overwrites new offset (32-bit) for each subtable, and a new lookup Type
1470 for ($c = 0; $c < $SubtableCount[$i]; $c++) {
1471 $this->seek($GSLookup[$i]['Subtables'][$c]);
1472 $ExtensionPosFormat = $this->read_ushort();
1473 $type = $this->read_ushort();
1474 $ext_offset = $this->read_ulong();
1475 $GSLookup[$i]['Subtables'][$c] = $GSLookup[$i]['Subtables'][$c] + $ext_offset;
1476 }
1477 $GSLookup[$i]['Type'] = $type;
1478 }
1479 }
1480
1481 //print_r($GSLookup); exit;
1482 //=====================================================================================
1483 // Process Whole LookupList - Get LuCoverage = Lookup coverage just for first glyph
1484 $this->GSLuCoverage = array();
1485 for ($i = 0; $i < $LookupCount; $i++) {
1486 for ($c = 0; $c < $GSLookup[$i]['SubtableCount']; $c++) {
1487
1488 $this->seek($GSLookup[$i]['Subtables'][$c]);
1489 $PosFormat = $this->read_ushort();
1490
1491 if ($GSLookup[$i]['Type'] == 5 && $PosFormat == 3) {
1492 $this->skip(4);
1493 } else if ($GSLookup[$i]['Type'] == 6 && $PosFormat == 3) {
1494 $BacktrackGlyphCount = $this->read_ushort();
1495 $this->skip(2 * $BacktrackGlyphCount + 2);
1496 }
1497 // NB Coverage only looks at glyphs for position 1 (i.e. 5.3 and 6.3) // NEEDS TO READ ALL ********************
1498 $Coverage = $GSLookup[$i]['Subtables'][$c] + $this->read_ushort();
1499 $this->seek($Coverage);
1500 $glyphs = $this->_getCoverage(false, 2);
1501 $this->GSLuCoverage[$i][$c] = $glyphs;
1502 }
1503 }
1504
1505 // $this->GSLuCoverage and $GSLookup
1506 //=====================================================================================
1507 //=====================================================================================
1508 $s = '<?php
1509 $GSLuCoverage = ' . var_export($this->GSLuCoverage, true) . ';
1510 ?>';
1511
1512 file_put_contents(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GSUBdata.php', $s);
1513
1514 //=====================================================================================
1515 //=====================================================================================
1516 //=====================================================================================
1517 //=====================================================================================
1518 // Now repeats as original to get Substitution rules
1519 //=====================================================================================
1520 //=====================================================================================
1521 //=====================================================================================
1522 // Get metadata and offsets for whole Lookup List table
1523 $this->seek($LookupList_offset);
1524 $LookupCount = $this->read_ushort();
1525 $Lookup = array();
1526 for ($i = 0; $i < $LookupCount; $i++) {
1527 $Lookup[$i]['offset'] = $LookupList_offset + $this->read_ushort();
1528 }
1529 for ($i = 0; $i < $LookupCount; $i++) {
1530 $this->seek($Lookup[$i]['offset']);
1531 $Lookup[$i]['Type'] = $this->read_ushort();
1532 $Lookup[$i]['Flag'] = $flag = $this->read_ushort();
1533 $Lookup[$i]['SubtableCount'] = $this->read_ushort();
1534 for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
1535 $Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['offset'] + $this->read_ushort();
1536 }
1537 // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure
1538 if (($flag & 0x0010) == 0x0010) {
1539 $Lookup[$i]['MarkFilteringSet'] = $this->read_ushort();
1540 } else {
1541 $Lookup[$i]['MarkFilteringSet'] = '';
1542 }
1543
1544 // Lookup Type 7: Extension
1545 if ($Lookup[$i]['Type'] == 7) {
1546 // Overwrites new offset (32-bit) for each subtable, and a new lookup Type
1547 for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
1548 $this->seek($Lookup[$i]['Subtable'][$c]['Offset']);
1549 $ExtensionPosFormat = $this->read_ushort();
1550 $type = $this->read_ushort();
1551 $Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ulong();
1552 }
1553 $Lookup[$i]['Type'] = $type;
1554 }
1555 }
1556
1557 //print_r($Lookup); exit;
1558 //=====================================================================================
1559 // Process (1) Whole LookupList
1560 for ($i = 0; $i < $LookupCount; $i++) {
1561 for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
1562
1563 $this->seek($Lookup[$i]['Subtable'][$c]['Offset']);
1564 $SubstFormat = $this->read_ushort();
1565 $Lookup[$i]['Subtable'][$c]['Format'] = $SubstFormat;
1566
1567 /*
1568 Lookup['Type'] Enumeration table for glyph substitution
1569 Value Type Description
1570 1 Single Replace one glyph with one glyph
1571 2 Multiple Replace one glyph with more than one glyph
1572 3 Alternate Replace one glyph with one of many glyphs
1573 4 Ligature Replace multiple glyphs with one glyph
1574 5 Context Replace one or more glyphs in context
1575 6 Chaining Context Replace one or more glyphs in chained context
1576 7 Extension Substitution Extension mechanism for other substitutions (i.e. this excludes the Extension type substitution itself)
1577 8 Reverse chaining context single Applied in reverse order, replace single glyph in chaining context
1578 */
1579
1580 // LookupType 1: Single Substitution Subtable
1581 if ($Lookup[$i]['Type'] == 1) {
1582 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1583 if ($SubstFormat == 1) { // Calculated output glyph indices
1584 $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'] = $this->read_short();
1585 } else if ($SubstFormat == 2) { // Specified output glyph indices
1586 $GlyphCount = $this->read_ushort();
1587 for ($g = 0; $g < $GlyphCount; $g++) {
1588 $Lookup[$i]['Subtable'][$c]['Glyphs'][] = $this->read_ushort();
1589 }
1590 }
1591 }
1592 // LookupType 2: Multiple Substitution Subtable
1593 else if ($Lookup[$i]['Type'] == 2) {
1594 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1595 $Lookup[$i]['Subtable'][$c]['SequenceCount'] = $SequenceCount = $this->read_short();
1596 for ($s = 0; $s < $SequenceCount; $s++) {
1597 $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
1598 }
1599 for ($s = 0; $s < $SequenceCount; $s++) {
1600 // Sequence Tables
1601 $this->seek($Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset']);
1602 $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount'] = $this->read_short();
1603 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount']; $g++) {
1604 $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['SubstituteGlyphID'][] = $this->read_ushort();
1605 }
1606 }
1607 }
1608 // LookupType 3: Alternate Forms
1609 else if ($Lookup[$i]['Type'] == 3) {
1610 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1611 $Lookup[$i]['Subtable'][$c]['AlternateSetCount'] = $AlternateSetCount = $this->read_short();
1612 for ($s = 0; $s < $AlternateSetCount; $s++) {
1613 $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
1614 }
1615
1616 for ($s = 0; $s < $AlternateSetCount; $s++) {
1617 // AlternateSet Tables
1618 $this->seek($Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset']);
1619 $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount'] = $this->read_short();
1620 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount']; $g++) {
1621 $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['SubstituteGlyphID'][] = $this->read_ushort();
1622 }
1623 }
1624 }
1625 // LookupType 4: Ligature Substitution Subtable
1626 else if ($Lookup[$i]['Type'] == 4) {
1627 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1628 $Lookup[$i]['Subtable'][$c]['LigSetCount'] = $LigSetCount = $this->read_short();
1629 for ($s = 0; $s < $LigSetCount; $s++) {
1630 $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
1631 }
1632 for ($s = 0; $s < $LigSetCount; $s++) {
1633 // LigatureSet Tables
1634 $this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset']);
1635 $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount'] = $this->read_short();
1636 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
1637 $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g] = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] + $this->read_ushort();
1638 }
1639 }
1640 for ($s = 0; $s < $LigSetCount; $s++) {
1641 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
1642 // Ligature tables
1643 $this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g]);
1644 $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph'] = $this->read_ushort();
1645 $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount'] = $this->read_ushort();
1646 for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) {
1647 $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l] = $this->read_ushort();
1648 }
1649 }
1650 }
1651 }
1652
1653 // LookupType 5: Contextual Substitution Subtable
1654 else if ($Lookup[$i]['Type'] == 5) {
1655 // Format 1: Context Substitution
1656 if ($SubstFormat == 1) {
1657 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1658 $Lookup[$i]['Subtable'][$c]['SubRuleSetCount'] = $SubRuleSetCount = $this->read_short();
1659 for ($s = 0; $s < $SubRuleSetCount; $s++) {
1660 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
1661 }
1662 for ($s = 0; $s < $SubRuleSetCount; $s++) {
1663 // SubRuleSet Tables
1664 $this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset']);
1665 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount'] = $this->read_short();
1666 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) {
1667 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] + $this->read_ushort();
1668 }
1669 }
1670 for ($s = 0; $s < $SubRuleSetCount; $s++) {
1671 // SubRule Tables
1672 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) {
1673 // Ligature tables
1674 $this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g]);
1675
1676 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount'] = $this->read_ushort();
1677 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount'] = $this->read_ushort();
1678 // "Input"::[GlyphCount - 1]::Array of input GlyphIDs-start with second glyph
1679 for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount']; $l++) {
1680 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['Input'][$l] = $this->read_ushort();
1681 }
1682 // "SubstLookupRecord"::[SubstCount]::Array of SubstLookupRecords-in design order
1683 for ($l = 0; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount']; $l++) {
1684 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['SequenceIndex'] = $this->read_ushort();
1685 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['LookupListIndex'] = $this->read_ushort();
1686 }
1687 }
1688 }
1689 }
1690 // Format 2: Class-based Context Glyph Substitution
1691 else if ($SubstFormat == 2) {
1692 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1693 $Lookup[$i]['Subtable'][$c]['ClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1694 $Lookup[$i]['Subtable'][$c]['SubClassSetCnt'] = $this->read_ushort();
1695 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $b++) {
1696 $offset = $this->read_ushort();
1697 if ($offset == 0x0000) {
1698 $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = 0;
1699 } else {
1700 $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset;
1701 }
1702 }
1703 } else {
1704 throw new MpdfException("GPOS Lookup Type " . $Lookup[$i]['Type'] . ", Format " . $SubstFormat . " not supported (ttfontsuni.php).");
1705 }
1706 }
1707
1708 // LookupType 6: Chaining Contextual Substitution Subtable
1709 else if ($Lookup[$i]['Type'] == 6) {
1710 // Format 1: Simple Chaining Context Glyph Substitution p255
1711 if ($SubstFormat == 1) {
1712 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1713 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount'] = $this->read_ushort();
1714 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $b++) {
1715 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1716 }
1717 }
1718 // Format 2: Class-based Chaining Context Glyph Substitution p257
1719 else if ($SubstFormat == 2) {
1720 $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1721 $Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1722 $Lookup[$i]['Subtable'][$c]['InputClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1723 $Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1724 $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt'] = $this->read_ushort();
1725 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $b++) {
1726 $offset = $this->read_ushort();
1727 if ($offset == 0x0000) {
1728 $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $offset;
1729 } else {
1730 $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset;
1731 }
1732 }
1733 }
1734 // Format 3: Coverage-based Chaining Context Glyph Substitution p259
1735 else if ($SubstFormat == 3) {
1736 $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount'] = $this->read_ushort();
1737 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) {
1738 $Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1739 }
1740 $Lookup[$i]['Subtable'][$c]['InputGlyphCount'] = $this->read_ushort();
1741 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) {
1742 $Lookup[$i]['Subtable'][$c]['CoverageInput'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1743 }
1744 $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount'] = $this->read_ushort();
1745 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) {
1746 $Lookup[$i]['Subtable'][$c]['CoverageLookahead'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
1747 }
1748 $Lookup[$i]['Subtable'][$c]['SubstCount'] = $this->read_ushort();
1749 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) {
1750 $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'] = $this->read_ushort();
1751 $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'] = $this->read_ushort();
1752 /*
1753 Substitution Lookup Record
1754 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.
1755 */
1756 }
1757 }
1758 } else {
1759 throw new MpdfException("Lookup Type " . $Lookup[$i]['Type'] . " not supported.");
1760 }
1761 }
1762 }
1763 //print_r($Lookup); exit;
1764 //=====================================================================================
1765 // Process (2) Whole LookupList
1766 // Get Coverage tables and prepare preg_replace
1767 for ($i = 0; $i < $LookupCount; $i++) {
1768 for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
1769 $SubstFormat = $Lookup[$i]['Subtable'][$c]['Format'];
1770
1771 // LookupType 1: Single Substitution Subtable 1 => 1
1772 if ($Lookup[$i]['Type'] == 1) {
1773 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1774 $glyphs = $this->_getCoverage(false);
1775 for ($g = 0; $g < count($glyphs); $g++) {
1776 $replace = array();
1777 $substitute = array();
1778 $replace[] = unicode_hex($this->glyphToChar[$glyphs[$g]][0]);
1779 // Flag = Ignore
1780 if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
1781 continue;
1782 }
1783 if (isset($Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])) { // Format 1
1784 $substitute[] = unicode_hex($this->glyphToChar[($glyphs[$g] + $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])][0]);
1785 } else { // Format 2
1786 $substitute[] = unicode_hex($this->glyphToChar[($Lookup[$i]['Subtable'][$c]['Glyphs'][$g])][0]);
1787 }
1788 $Lookup[$i]['Subtable'][$c]['subs'][] = array('Replace' => $replace, 'substitute' => $substitute);
1789 }
1790 }
1791
1792 // LookupType 2: Multiple Substitution Subtable 1 => n
1793 else if ($Lookup[$i]['Type'] == 2) {
1794 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1795 $glyphs = $this->_getCoverage();
1796 for ($g = 0; $g < count($glyphs); $g++) {
1797 $replace = array();
1798 $substitute = array();
1799 $replace[] = $glyphs[$g];
1800 // Flag = Ignore
1801 if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
1802 continue;
1803 }
1804 if (!isset($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) || count($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) == 0) {
1805 continue;
1806 } // Illegal for GlyphCount to be 0; either error in font, or something has gone wrong - lets carry on for now!
1807 foreach ($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID'] AS $sub) {
1808 $substitute[] = unicode_hex($this->glyphToChar[$sub][0]);
1809 }
1810 $Lookup[$i]['Subtable'][$c]['subs'][] = array('Replace' => $replace, 'substitute' => $substitute);
1811 }
1812 }
1813 // LookupType 3: Alternate Forms 1 => 1 (only first alternate form is used)
1814 else if ($Lookup[$i]['Type'] == 3) {
1815 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1816 $glyphs = $this->_getCoverage();
1817 for ($g = 0; $g < count($glyphs); $g++) {
1818 $replace = array();
1819 $substitute = array();
1820 $replace[] = $glyphs[$g];
1821 // Flag = Ignore
1822 if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
1823 continue;
1824 }
1825 $gid = $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['SubstituteGlyphID'][0];
1826 if (!isset($this->glyphToChar[$gid][0])) {
1827 continue;
1828 }
1829 $substitute[] = unicode_hex($this->glyphToChar[$gid][0]);
1830 $Lookup[$i]['Subtable'][$c]['subs'][] = array('Replace' => $replace, 'substitute' => $substitute);
1831 }
1832 }
1833 // LookupType 4: Ligature Substitution Subtable n => 1
1834 else if ($Lookup[$i]['Type'] == 4) {
1835 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1836 $glyphs = $this->_getCoverage();
1837 $LigSetCount = $Lookup[$i]['Subtable'][$c]['LigSetCount'];
1838 for ($s = 0; $s < $LigSetCount; $s++) {
1839 for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
1840 $replace = array();
1841 $substitute = array();
1842 $replace[] = $glyphs[$s];
1843 // Flag = Ignore
1844 if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
1845 continue;
1846 }
1847 for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) {
1848 $gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l];
1849 $rpl = unicode_hex($this->glyphToChar[$gid][0]);
1850 // Flag = Ignore
1851 if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $rpl, $Lookup[$i]['MarkFilteringSet'])) {
1852 continue 2;
1853 }
1854 $replace[] = $rpl;
1855 }
1856 $gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph'];
1857 if (!isset($this->glyphToChar[$gid][0])) {
1858 continue;
1859 }
1860 $substitute[] = unicode_hex($this->glyphToChar[$gid][0]);
1861 $Lookup[$i]['Subtable'][$c]['subs'][] = array('Replace' => $replace, 'substitute' => $substitute, 'CompCount' => $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']);
1862 }
1863 }
1864 }
1865
1866 // LookupType 5: Contextual Substitution Subtable
1867 else if ($Lookup[$i]['Type'] == 5) {
1868 // Format 1: Context Substitution
1869 if ($SubstFormat == 1) {
1870 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1871 $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
1872
1873 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) {
1874 $SubRuleSet = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s];
1875 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph'] = $CoverageGlyphs[$s];
1876 for ($r = 0; $r < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $r++) {
1877 $GlyphCount = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['GlyphCount'];
1878 for ($g = 1; $g < $GlyphCount; $g++) {
1879 $glyphID = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['Input'][$g];
1880 $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
1881 }
1882 }
1883 }
1884 }
1885 // Format 2: Class-based Context Glyph Substitution
1886 else if ($SubstFormat == 2) {
1887 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1888 $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
1889
1890 $InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['ClassDefOffset']);
1891 $Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses;
1892 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) {
1893 if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) {
1894 $this->seek($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s]);
1895 $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt'] = $SubClassRuleCnt = $this->read_ushort();
1896 $SubClassRule = array();
1897 for ($b = 0; $b < $SubClassRuleCnt; $b++) {
1898 $SubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] + $this->read_ushort();
1899 $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $SubClassRule[$b];
1900 }
1901 }
1902 }
1903
1904 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) {
1905 if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) {
1906 $SubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt'];
1907 for ($b = 0; $b < $SubClassRuleCnt; $b++) {
1908 $this->seek($Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b]);
1909 $Rule = array();
1910 $Rule['InputGlyphCount'] = $this->read_ushort();
1911 $Rule['SubstCount'] = $this->read_ushort();
1912 for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) {
1913 $Rule['Input'][$r] = $this->read_ushort();
1914 }
1915 for ($r = 0; $r < $Rule['SubstCount']; $r++) {
1916 $Rule['SequenceIndex'][$r] = $this->read_ushort();
1917 $Rule['LookupListIndex'][$r] = $this->read_ushort();
1918 }
1919
1920 $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $Rule;
1921 }
1922 }
1923 }
1924 }
1925 // Format 3: Coverage-based Context Glyph Substitution
1926 else if ($SubstFormat == 3) {
1927 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) {
1928 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]);
1929 $glyphs = $this->_getCoverage();
1930 $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs);
1931 }
1932 throw new MpdfException("Lookup Type 5, SubstFormat 3 not tested. Please report this with the name of font used - " . $this->fontkey);
1933 }
1934 }
1935
1936 // LookupType 6: Chaining Contextual Substitution Subtable
1937 else if ($Lookup[$i]['Type'] == 6) {
1938 // Format 1: Simple Chaining Context Glyph Substitution p255
1939 if ($SubstFormat == 1) {
1940 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1941 $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
1942
1943 $ChainSubRuleSetCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount'];
1944
1945 for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) {
1946 $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s]);
1947 $ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount'] = $this->read_ushort();
1948 for ($r = 0; $r < $ChainSubRuleCnt; $r++) {
1949 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r] = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s] + $this->read_ushort();
1950 }
1951 }
1952 for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) {
1953 $ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount'];
1954 for ($r = 0; $r < $ChainSubRuleCnt; $r++) {
1955 // ChainSubRule
1956 $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r]);
1957
1958 $BacktrackGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphCount'] = $this->read_ushort();
1959 for ($g = 0; $g < $BacktrackGlyphCount; $g++) {
1960 $glyphID = $this->read_ushort();
1961 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
1962 }
1963
1964 $InputGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphCount'] = $this->read_ushort();
1965 for ($g = 1; $g < $InputGlyphCount; $g++) {
1966 $glyphID = $this->read_ushort();
1967 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
1968 }
1969
1970
1971 $LookaheadGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphCount'] = $this->read_ushort();
1972 for ($g = 0; $g < $LookaheadGlyphCount; $g++) {
1973 $glyphID = $this->read_ushort();
1974 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
1975 }
1976
1977 $SubstCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SubstCount'] = $this->read_ushort();
1978 for ($lu = 0; $lu < $SubstCount; $lu++) {
1979 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SequenceIndex'][$lu] = $this->read_ushort();
1980 $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookupListIndex'][$lu] = $this->read_ushort();
1981 }
1982 }
1983 }
1984 }
1985 // Format 2: Class-based Chaining Context Glyph Substitution p257
1986 else if ($SubstFormat == 2) {
1987 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
1988 $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
1989
1990 $BacktrackClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset']);
1991 $Lookup[$i]['Subtable'][$c]['BacktrackClasses'] = $BacktrackClasses;
1992
1993 $InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['InputClassDefOffset']);
1994 $Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses;
1995
1996 $LookaheadClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset']);
1997 $Lookup[$i]['Subtable'][$c]['LookaheadClasses'] = $LookaheadClasses;
1998
1999 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) {
2000 if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) {
2001 $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s]);
2002 $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'] = $ChainSubClassRuleCnt = $this->read_ushort();
2003 $ChainSubClassRule = array();
2004 for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) {
2005 $ChainSubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] + $this->read_ushort();
2006 $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $ChainSubClassRule[$b];
2007 }
2008 }
2009 }
2010
2011 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) {
2012 if (isset($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'])) {
2013 $ChainSubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'];
2014 } else {
2015 $ChainSubClassRuleCnt = 0;
2016 }
2017 for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) {
2018 if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) {
2019 $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b]);
2020 $Rule = array();
2021 $Rule['BacktrackGlyphCount'] = $this->read_ushort();
2022 for ($r = 0; $r < $Rule['BacktrackGlyphCount']; $r++) {
2023 $Rule['Backtrack'][$r] = $this->read_ushort();
2024 }
2025 $Rule['InputGlyphCount'] = $this->read_ushort();
2026 for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) {
2027 $Rule['Input'][$r] = $this->read_ushort();
2028 }
2029 $Rule['LookaheadGlyphCount'] = $this->read_ushort();
2030 for ($r = 0; $r < $Rule['LookaheadGlyphCount']; $r++) {
2031 $Rule['Lookahead'][$r] = $this->read_ushort();
2032 }
2033 $Rule['SubstCount'] = $this->read_ushort();
2034 for ($r = 0; $r < $Rule['SubstCount']; $r++) {
2035 $Rule['SequenceIndex'][$r] = $this->read_ushort();
2036 $Rule['LookupListIndex'][$r] = $this->read_ushort();
2037 }
2038
2039 $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $Rule;
2040 }
2041 }
2042 }
2043 }
2044 // Format 3: Coverage-based Chaining Context Glyph Substitution p259
2045 else if ($SubstFormat == 3) {
2046 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) {
2047 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][$b]);
2048 $glyphs = $this->_getCoverage();
2049 $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'][] = implode("|", $glyphs);
2050 }
2051 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) {
2052 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]);
2053 $glyphs = $this->_getCoverage();
2054 $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs);
2055 // Don't use above value as these are ordered numerically not as need to process
2056 }
2057 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) {
2058 $this->seek($Lookup[$i]['Subtable'][$c]['CoverageLookahead'][$b]);
2059 $glyphs = $this->_getCoverage();
2060 $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'][] = implode("|", $glyphs);
2061 }
2062 }
2063 }
2064 }
2065 }
2066
2067
2068 //=====================================================================================
2069 //=====================================================================================
2070 //=====================================================================================
2071 //=====================================================================================
2072
2073 $GSUBScriptLang = array();
2074 $rtlpua = array(); // All glyphs added to PUA [for magic_reverse]
2075 foreach ($gsub AS $st => $scripts) {
2076 foreach ($scripts AS $t => $langsys) {
2077 $lul = array(); // array of LookupListIndexes
2078 $tags = array(); // corresponding array of feature tags e.g. 'ccmp'
2079 //print_r($langsys ); exit;
2080 foreach ($langsys AS $tag => $ft) {
2081 foreach ($ft AS $ll) {
2082 $lul[$ll] = $tag;
2083 }
2084 }
2085 ksort($lul); // Order the Lookups in the order they are in the GUSB table, regardless of Feature order
2086 $volt = $this->_getGSUBarray($Lookup, $lul, $st);
2087 //print_r($lul); exit;
2088 //=====================================================================================
2089 //=====================================================================================
2090 // Interrogate $volt
2091 // isol, fin, medi, init(arab syrc) into $rtlSUB for use in ArabJoin
2092 // but also identify all RTL chars in PUA for magic_reverse (arab syrc hebr thaa nko samr)
2093 // identify reph, matras, vatu, half forms etc for Indic for final re-ordering
2094 //=====================================================================================
2095 //=====================================================================================
2096 $rtl = array();
2097 $rtlSUB = "array()";
2098 $finals = '';
2099 if (strpos('arab syrc hebr thaa nko samr', $st) !== false) { // all RTL scripts [any/all languages] ? Mandaic
2100 //print_r($volt); exit;
2101 foreach ($volt AS $v) {
2102 // isol fina fin2 fin3 medi med2 for Syriac
2103 // ISOLATED FORM :: FINAL :: INITIAL :: MEDIAL :: MED2 :: FIN2 :: FIN3
2104 if (strpos('isol fina init medi fin2 fin3 med2', $v['tag']) !== false) {
2105 $key = $v['match'];
2106 $key = preg_replace('/[\(\)]*/', '', $key);
2107 $sub = $v['replace'];
2108 if ($v['tag'] == 'isol')
2109 $kk = 0;
2110 else if ($v['tag'] == 'fina')
2111 $kk = 1;
2112 else if ($v['tag'] == 'init')
2113 $kk = 2;
2114 else if ($v['tag'] == 'medi')
2115 $kk = 3;
2116 else if ($v['tag'] == 'med2')
2117 $kk = 4;
2118 else if ($v['tag'] == 'fin2')
2119 $kk = 5;
2120 else if ($v['tag'] == 'fin3')
2121 $kk = 6;
2122 $rtl[$key][$kk] = $sub;
2123 if (isset($v['prel']) && count($v['prel']))
2124 $rtl[$key]['prel'][$kk] = $v['prel'];
2125 if (isset($v['postl']) && count($v['postl']))
2126 $rtl[$key]['postl'][$kk] = $v['postl'];
2127 if (isset($v['ignore']) && $v['ignore']) {
2128 $rtl[$key]['ignore'][$kk] = $v['ignore'];
2129 }
2130 $rtlpua[] = $sub;
2131 }
2132 // Add any other glyphs which are in PUA
2133 else {
2134 if (isset($v['context']) && $v['context']) {
2135 foreach ($v['rules'] AS $vs) {
2136 for ($i = 0; $i < count($vs['match']); $i++) {
2137 if (isset($vs['replace'][$i]) && preg_match('/^0[A-F0-9]{4}$/', $vs['match'][$i])) {
2138 if (preg_match('/^0[EF][A-F0-9]{3}$/', $vs['replace'][$i])) {
2139 $rtlpua[] = $vs['replace'][$i];
2140 }
2141 }
2142 }
2143 }
2144 } else {
2145 preg_match_all('/\((0[A-F0-9]{4})\)/', $v['match'], $m);
2146 for ($i = 0; $i < count($m[0]); $i++) {
2147 $sb = explode(' ', $v['replace']);
2148 foreach ($sb AS $sbg) {
2149 if (preg_match('/(0[EF][A-F0-9]{3})/', $sbg, $mr)) {
2150 $rtlpua[] = $mr[1];
2151 }
2152 }
2153 }
2154 }
2155 }
2156 }
2157 //print_r($rtl); exit;
2158 // For kashida, need to determine all final forms except ones already identified by kashida
2159 // priority rules (see otl.php)
2160 foreach ($rtl AS $base => $variants) {
2161 if (isset($variants[1])) { // i.e. final form
2162 if (strpos('0FE8E 0FE94 0FEA2 0FEAA 0FEAE 0FEC2 0FEDA 0FEDE 0FB93 0FECA 0FED2 0FED6 0FEEE 0FEF0 0FEF2', $variants[1]) === false) { // not already included
2163 // This version does not exclude RA (0631) FEAE; Ya (064A) FEF2; Alef Maqsurah (0649) FEF0 which
2164 // are selected in priority if connected to a medial Bah
2165 //if (strpos('0FE8E 0FE94 0FEA2 0FEAA 0FEC2 0FEDA 0FEDE 0FB93 0FECA 0FED2 0FED6 0FEEE', $variants[1])===false) { // not already included
2166 $finals .= $variants[1] . ' ';
2167 }
2168 }
2169 }
2170 //echo $finals ; exit;
2171 //print_r($rtlpua); exit;
2172 ksort($rtl);
2173 $a = var_export($rtl, true);
2174 $a = preg_replace('/\\\\\\\\/', "\\", $a);
2175 $a = preg_replace('/\'/', '"', $a);
2176 $a = preg_replace('/\r/', '', $a);
2177 $a = preg_replace('/> \n/', '>', $a);
2178 $a = preg_replace('/\n \)/', ')', $a);
2179 $a = preg_replace('/\n /', ' ', $a);
2180 $a = preg_replace('/\[IGNORE(\d+)\]/', '".$ignore[\\1]."', $a);
2181 $rtlSUB = preg_replace('/[ ]+/', ' ', $a);
2182 }
2183 //=====================================================================================
2184 // INDIC - Dynamic properties
2185 //=====================================================================================
2186 $rphf = array();
2187 $half = array();
2188 $pref = array();
2189 $blwf = array();
2190 $pstf = array();
2191 if (strpos('dev2 bng2 gur2 gjr2 ory2 tml2 tel2 knd2 mlm2 deva beng guru gujr orya taml telu knda mlym', $st) !== false) { // all INDIC scripts [any/all languages]
2192 if (strpos('deva beng guru gujr orya taml telu knda mlym', $st) !== false) {
2193 $is_old_spec = true;
2194 } else {
2195 $is_old_spec = false;
2196 }
2197
2198 // First get 'locl' substitutions (reversed!)
2199 $loclsubs = array();
2200 foreach ($volt AS $v) {
2201 if (strpos('locl', $v['tag']) !== false) {
2202 $key = $v['match'];
2203 $key = preg_replace('/[\(\)]*/', '', $key);
2204 $sub = $v['replace'];
2205 if ($key && strlen(trim($key)) == 5 && $sub) {
2206 $loclsubs[$sub] = $key;
2207 }
2208 }
2209 }
2210 //if (count($loclsubs)) { print_r($loclsubs); exit; }
2211
2212 foreach ($volt AS $v) {
2213 // <rphf> <half> <pref> <blwf> <pstf>
2214 // defines consonant types:
2215 // Reph <rphf>
2216 // Half forms <half>
2217 // Pre-base-reordering forms of Ra/Rra <pref>
2218 // Below-base forms <blwf>
2219 // Post-base forms <pstf>
2220 // applied together with <locl> feature to input sequences consisting of two characters
2221 // This is done for each consonant
2222 // for <rphf> and <half>, features are applied to Consonant + Halant combinations
2223 // for <pref>, <blwf> and <pstf>, features are applied to Halant + Consonant combinations
2224 // Old version eg 'deva' <pref>, <blwf> and <pstf>, features are applied to Consonant + Halant
2225 // Some malformed fonts still do Consonant + Halant for these - so match both??
2226 // If these two glyphs form a ligature, with no additional glyphs in context
2227 // this means the consonant has the corresponding form
2228 // Currently set to cope with both
2229 // See also classes/otl.php
2230
2231 if (strpos('rphf half pref blwf pstf', $v['tag']) !== false) {
2232 if (isset($v['context']) && $v['context'] && $v['nBacktrack'] == 0 && $v['nLookahead'] == 0) {
2233 foreach ($v['rules'] AS $vs) {
2234 if (count($vs['match']) == 2 && count($vs['replace']) == 1) {
2235 $sub = $vs['replace'][0];
2236 // If Halant Cons <pref>, <blwf> and <pstf> in New version only
2237 if (strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', $vs['match'][0]) !== false && strpos('pref blwf pstf', $v['tag']) !== false && !$is_old_spec) {
2238 $key = $vs['match'][1];
2239 $tag = $v['tag'];
2240 if (isset($loclsubs[$key])) {
2241 $$tag[$loclsubs[$key]] = $sub;
2242 }
2243 $tmp = &$$tag;
2244 $tmp[hexdec($key)] = hexdec($sub);
2245 }
2246 // If Cons Halant <rphf> and <half> always
2247 // and <pref>, <blwf> and <pstf> in Old version
2248 else if (strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', $vs['match'][1]) !== false && (strpos('rphf half', $v['tag']) !== false || (strpos('pref blwf pstf', $v['tag']) !== false && ($is_old_spec || _OTL_OLD_SPEC_COMPAT_2)))) {
2249 $key = $vs['match'][0];
2250 $tag = $v['tag'];
2251 if (isset($loclsubs[$key])) {
2252 $$tag[$loclsubs[$key]] = $sub;
2253 }
2254 $tmp = &$$tag;
2255 $tmp[hexdec($key)] = hexdec($sub);
2256 }
2257 }
2258 }
2259 } else if (!isset($v['context'])) {
2260 $key = $v['match'];
2261 $key = preg_replace('/[\(\)]*/', '', $key);
2262 $sub = $v['replace'];
2263 if ($key && strlen(trim($key)) == 11 && $sub) {
2264 // If Cons Halant <rphf> and <half> always
2265 // and <pref>, <blwf> and <pstf> in Old version
2266 // If Halant Cons <pref>, <blwf> and <pstf> in New version only
2267 if (strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', substr($key, 0, 5)) !== false && strpos('pref blwf pstf', $v['tag']) !== false && !$is_old_spec) {
2268 $key = substr($key, 6, 5);
2269 $tag = $v['tag'];
2270 if (isset($loclsubs[$key])) {
2271 $$tag[$loclsubs[$key]] = $sub;
2272 }
2273 $tmp = &$$tag;
2274 $tmp[hexdec($key)] = hexdec($sub);
2275 } else if (strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', substr($key, 6, 5)) !== false && (strpos('rphf half', $v['tag']) !== false || (strpos('pref blwf pstf', $v['tag']) !== false && ($is_old_spec || _OTL_OLD_SPEC_COMPAT_2)))) {
2276 $key = substr($key, 0, 5);
2277 $tag = $v['tag'];
2278 if (isset($loclsubs[$key])) {
2279 $$tag[$loclsubs[$key]] = $sub;
2280 }
2281 $tmp = &$$tag;
2282 $tmp[hexdec($key)] = hexdec($sub);
2283 }
2284 }
2285 }
2286 }
2287 }
2288 /*
2289 print_r($rphf );
2290 print_r($half );
2291 print_r($pref );
2292 print_r($blwf );
2293 print_r($pstf ); exit;
2294 */
2295 }
2296 //print_r($rtlpua); exit;
2297 //=====================================================================================
2298 //=====================================================================================
2299 //=====================================================================================
2300 //=====================================================================================
2301 if (count($rtl) || count($rphf) || count($half) || count($pref) || count($blwf) || count($pstf) || $finals) {
2302 // SAVE LOOKUPS TO FILE fontname.GSUB.scripttag.langtag.php
2303
2304
2305 $s = '<?php
2306
2307 $rtlSUB = ' . $rtlSUB . ';
2308 $finals = \'' . $finals . '\';
2309 $rphf = ' . var_export($rphf, true) . ';
2310 $half = ' . var_export($half, true) . ';
2311 $pref = ' . var_export($pref, true) . ';
2312 $blwf = ' . var_export($blwf, true) . ';
2313 $pstf = ' . var_export($pstf, true) . ';
2314
2315 ' . "\n" . '?>';
2316
2317
2318 file_put_contents(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GSUB.' . $st . '.' . $t . '.php', $s);
2319 }
2320 //=====================================================================================
2321 if (!isset($GSUBScriptLang[$st])) {
2322 $GSUBScriptLang[$st] = '';
2323 }
2324 $GSUBScriptLang[$st] .= $t . ' ';
2325 //=====================================================================================
2326 }
2327 }
2328 //print_r($rtlpua); exit;
2329 // All RTL glyphs from font added to (or already in) PUA [reqd for magic_reverse]
2330 $rtlPUAstr = "";
2331 if (count($rtlpua)) {
2332 $rtlpua = array_unique($rtlpua);
2333 sort($rtlpua);
2334 $n = count($rtlpua);
2335 for ($i = 0; $i < $n; $i++) {
2336 if (hexdec($rtlpua[$i]) < hexdec('E000') || hexdec($rtlpua[$i]) > hexdec('F8FF')) {
2337 unset($rtlpua[$i]);
2338 }
2339 }
2340 sort($rtlpua, SORT_STRING);
2341
2342 $rangeid = -1;
2343 $range = array();
2344 $prevgid = -2;
2345 // for each character
2346 foreach ($rtlpua as $gidhex) {
2347 $gid = hexdec($gidhex);
2348 if ($gid == ($prevgid + 1)) {
2349 $range[$rangeid]['end'] = $gidhex;
2350 $range[$rangeid]['count'] ++;
2351 } else {
2352 // new range
2353 $rangeid++;
2354 $range[$rangeid] = array();
2355 $range[$rangeid]['start'] = $gidhex;
2356 $range[$rangeid]['end'] = $gidhex;
2357 $range[$rangeid]['count'] = 1;
2358 }
2359 $prevgid = $gid;
2360 }
2361 foreach ($range AS $rg) {
2362 if ($rg['count'] == 1) {
2363 $rtlPUAstr .= "\x{" . $rg['start'] . "}";
2364 } else if ($rg['count'] == 2) {
2365 $rtlPUAstr .= "\x{" . $rg['start'] . "}\x{" . $rg['end'] . "}";
2366 } else {
2367 $rtlPUAstr .= "\x{" . $rg['start'] . "}-\x{" . $rg['end'] . "}";
2368 }
2369 }
2370 }
2371
2372 //print_r($rtlPUAstr ); exit;
2373 //=====================================================================================
2374 //=====================================================================================
2375 //=====================================================================================
2376 //=====================================================================================
2377 //print_r($rtlpua); exit;
2378 //print_r($GSUBScriptLang); exit;
2379 }
2380 //print_r($Lookup); exit;
2381
2382 return array($GSUBScriptLang, $gsub, $GSLookup, $rtlPUAstr); // , $rtlPUAarr Not needed
2383 }
2384
2385 /////////////////////////////////////////////////////////////////////////////////////////
2386 // GSUB functions
2387 function _getGSUBarray(&$Lookup, &$lul, $scripttag)
2388 {
2389 // Process (3) LookupList for specific Script-LangSys
2390 // Generate preg_replace
2391 $volt = array();
2392 $reph = '';
2393 $matraE = '';
2394 $vatu = '';
2395 foreach ($lul AS $i => $tag) {
2396 for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
2397
2398 $SubstFormat = $Lookup[$i]['Subtable'][$c]['Format'];
2399
2400 // LookupType 1: Single Substitution Subtable
2401 if ($Lookup[$i]['Type'] == 1) {
2402 for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
2403 $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
2404 $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
2405 // Ignore has already been applied earlier on
2406 $repl = $this->_makeGSUBinputMatch($inputGlyphs, "()");
2407 $subs = $this->_makeGSUBinputReplacement(1, $substitute, "()", 0, 1, 0);
2408 $volt[] = array('match' => $repl, 'replace' => $subs, 'tag' => $tag, 'key' => $inputGlyphs[0], 'type' => 1);
2409 }
2410 }
2411 // LookupType 2: Multiple Substitution Subtable
2412 else if ($Lookup[$i]['Type'] == 2) {
2413 for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
2414 $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
2415 $substitute = implode(" ", $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']);
2416 // Ignore has already been applied earlier on
2417 $repl = $this->_makeGSUBinputMatch($inputGlyphs, "()");
2418 $subs = $this->_makeGSUBinputReplacement(1, $substitute, "()", 0, 1, 0);
2419 $volt[] = array('match' => $repl, 'replace' => $subs, 'tag' => $tag, 'key' => $inputGlyphs[0], 'type' => 2);
2420 }
2421 }
2422 // LookupType 3: Alternate Forms
2423 else if ($Lookup[$i]['Type'] == 3) {
2424 for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
2425 $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
2426 $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
2427 // Ignore has already been applied earlier on
2428 $repl = $this->_makeGSUBinputMatch($inputGlyphs, "()");
2429 $subs = $this->_makeGSUBinputReplacement(1, $substitute, "()", 0, 1, 0);
2430 $volt[] = array('match' => $repl, 'replace' => $subs, 'tag' => $tag, 'key' => $inputGlyphs[0], 'type' => 3);
2431 }
2432 }
2433 // LookupType 4: Ligature Substitution Subtable
2434 else if ($Lookup[$i]['Type'] == 4) {
2435 for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
2436 $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
2437 $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
2438 // Ignore has already been applied earlier on
2439 $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
2440 $repl = $this->_makeGSUBinputMatch($inputGlyphs, $ignore);
2441 $subs = $this->_makeGSUBinputReplacement(count($inputGlyphs), $substitute, $ignore, 0, count($inputGlyphs), 0);
2442 $volt[] = array('match' => $repl, 'replace' => $subs, 'tag' => $tag, 'key' => $inputGlyphs[0], 'type' => 4, 'CompCount' => $Lookup[$i]['Subtable'][$c]['subs'][$s]['CompCount'], 'Lig' => $substitute);
2443 }
2444 }
2445
2446 // LookupType 5: Chaining Contextual Substitution Subtable
2447 else if ($Lookup[$i]['Type'] == 5) {
2448 // Format 1: Context Substitution
2449 if ($SubstFormat == 1) {
2450 $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
2451 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) {
2452 // SubRuleSet
2453 $subRule = array();
2454 foreach ($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'] AS $rule) {
2455 // SubRule
2456 $inputGlyphs = array();
2457 if ($rule['GlyphCount'] > 1) {
2458 $inputGlyphs = $rule['InputGlyphs'];
2459 }
2460 $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph'];
2461 ksort($inputGlyphs);
2462 $nInput = count($inputGlyphs);
2463
2464 $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, array(), 0);
2465 $subRule = array('context' => 1, 'tag' => $tag, 'matchback' => '', 'match' => $contextInputMatch, 'nBacktrack' => 0, 'nInput' => $nInput, 'nLookahead' => 0, 'rules' => array(),);
2466
2467 for ($b = 0; $b < $rule['SubstCount']; $b++) {
2468 $lup = $rule['SubstLookupRecord'][$b]['LookupListIndex'];
2469 $seqIndex = $rule['SubstLookupRecord'][$b]['SequenceIndex'];
2470
2471 // $Lookup[$lup] = secondary Lookup
2472 for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
2473 if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
2474 foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] AS $luss) {
2475
2476 $lookupGlyphs = $luss['Replace'];
2477 $mLen = count($lookupGlyphs);
2478
2479 // Only apply if the (first) 'Replace' glyph from the
2480 // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
2481 // then apply the substitution
2482 if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) {
2483 continue;
2484 }
2485 $REPL = implode(" ", $luss['substitute']);
2486 if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
2487 $volt[] = array('match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore);
2488 } else {
2489 $subRule['rules'][] = array('type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],);
2490 }
2491 }
2492 }
2493 }
2494 }
2495
2496
2497 if (count($subRule['rules']))
2498 $volt[] = $subRule;
2499 }
2500 }
2501 }
2502 // Format 2: Class-based Context Glyph Substitution
2503 else if ($SubstFormat == 2) {
2504 $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
2505 foreach ($Lookup[$i]['Subtable'][$c]['SubClassSet'] AS $inputClass => $cscs) {
2506 for ($cscrule = 0; $cscrule < $cscs['SubClassRuleCnt']; $cscrule++) {
2507 $rule = $cscs['SubClassRule'][$cscrule];
2508
2509 $inputGlyphs = array();
2510
2511 $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass];
2512 if ($rule['InputGlyphCount'] > 1) {
2513 // NB starts at 1
2514 for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) {
2515 $classindex = $rule['Input'][$gcl];
2516 if (isset($Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex])) {
2517 $inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex];
2518 }
2519 // if class[0] = all glyphs excluding those specified in all other classes
2520 // set to blank '' for now
2521 else {
2522 $inputGlyphs[$gcl] = '';
2523 }
2524 }
2525 }
2526
2527 $nInput = $rule['InputGlyphCount'];
2528
2529 $nIsubs = (2 * $nInput) - 1;
2530
2531 $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, array(), 0);
2532 $subRule = array('context' => 1, 'tag' => $tag, 'matchback' => '', 'match' => $contextInputMatch, 'nBacktrack' => 0, 'nInput' => $nInput, 'nLookahead' => 0, 'rules' => array(),);
2533
2534 for ($b = 0; $b < $rule['SubstCount']; $b++) {
2535 $lup = $rule['LookupListIndex'][$b];
2536 $seqIndex = $rule['SequenceIndex'][$b];
2537
2538 // $Lookup[$lup] = secondary Lookup
2539 for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
2540 if (isset($Lookup[$lup]['Subtable'][$lus]['subs']) && count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
2541 foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] AS $luss) {
2542
2543 $lookupGlyphs = $luss['Replace'];
2544 $mLen = count($lookupGlyphs);
2545
2546 // Only apply if the (first) 'Replace' glyph from the
2547 // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
2548 // then apply the substitution
2549 if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) {
2550 continue;
2551 }
2552
2553 // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
2554 $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex);
2555 $REPL = implode(" ", $luss['substitute']);
2556 // Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}"
2557
2558 if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
2559 $volt[] = array('match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore);
2560 } else {
2561 $subRule['rules'][] = array('type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],);
2562 }
2563 }
2564 }
2565 }
2566 }
2567 if (count($subRule['rules']))
2568 $volt[] = $subRule;
2569 }
2570 }
2571 }
2572 // Format 3: Coverage-based Context Glyph Substitution p259
2573 else if ($SubstFormat == 3) {
2574 // IgnoreMarks flag set on main Lookup table
2575 $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
2576 $inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'];
2577 $CoverageInputGlyphs = implode('|', $inputGlyphs);
2578 $nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount'];
2579
2580 if ($Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']) {
2581 $backtrackGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'];
2582 } else {
2583 $backtrackGlyphs = array();
2584 }
2585 // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦
2586 $backtrackMatch = $this->_makeGSUBbacktrackMatch($backtrackGlyphs, $ignore);
2587
2588 if ($Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']) {
2589 $lookaheadGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'];
2590 } else {
2591 $lookaheadGlyphs = array();
2592 }
2593 // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦
2594 $lookaheadMatch = $this->_makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore);
2595
2596 $nBsubs = 2 * count($backtrackGlyphs);
2597 $nIsubs = (2 * $nInput) - 1;
2598 $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, array(), 0);
2599 $subRule = array('context' => 1, 'tag' => $tag, 'matchback' => $backtrackMatch, 'match' => ($contextInputMatch . $lookaheadMatch), 'nBacktrack' => count($backtrackGlyphs), 'nInput' => $nInput, 'nLookahead' => count($lookaheadGlyphs), 'rules' => array(),);
2600
2601 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) {
2602 $lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'];
2603 $seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'];
2604 for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
2605 if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
2606 foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] AS $luss) {
2607 $lookupGlyphs = $luss['Replace'];
2608 $mLen = count($lookupGlyphs);
2609
2610 // Only apply if the (first) 'Replace' glyph from the
2611 // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
2612 // then apply the substitution
2613 if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) {
2614 continue;
2615 }
2616
2617 // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
2618 $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex);
2619 $REPL = implode(" ", $luss['substitute']);
2620
2621 if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
2622 $volt[] = array('match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore);
2623 } else {
2624 $subRule['rules'][] = array('type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],);
2625 }
2626 }
2627 }
2628 }
2629 }
2630 if (count($subRule['rules']))
2631 $volt[] = $subRule;
2632 }
2633
2634 //print_r($Lookup[$i]);
2635 //print_r($volt[(count($volt)-1)]); exit;
2636 }
2637 // LookupType 6: ing Contextual Substitution Subtable
2638 else if ($Lookup[$i]['Type'] == 6) {
2639 // Format 1: Simple Chaining Context Glyph Substitution p255
2640 if ($SubstFormat == 1) {
2641 $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
2642 for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $s++) {
2643 // ChainSubRuleSet
2644 $subRule = array();
2645 $firstInputGlyph = $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'][$s]; // First input gyyph
2646 foreach ($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'] AS $rule) {
2647 // ChainSubRule
2648 $inputGlyphs = array();
2649 if ($rule['InputGlyphCount'] > 1) {
2650 $inputGlyphs = $rule['InputGlyphs'];
2651 }
2652 $inputGlyphs[0] = $firstInputGlyph;
2653 ksort($inputGlyphs);
2654 $nInput = count($inputGlyphs);
2655
2656 if ($rule['BacktrackGlyphCount']) {
2657 $backtrackGlyphs = $rule['BacktrackGlyphs'];
2658 } else {
2659 $backtrackGlyphs = array();
2660 }
2661 $backtrackMatch = $this->_makeGSUBbacktrackMatch($backtrackGlyphs, $ignore);
2662
2663 if ($rule['LookaheadGlyphCount']) {
2664 $lookaheadGlyphs = $rule['LookaheadGlyphs'];
2665 } else {
2666 $lookaheadGlyphs = array();
2667 }
2668
2669 $lookaheadMatch = $this->_makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore);
2670
2671 $nBsubs = 2 * count($backtrackGlyphs);
2672 $nIsubs = (2 * $nInput) - 1;
2673
2674 $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, array(), 0);
2675 $subRule = array('context' => 1, 'tag' => $tag, 'matchback' => $backtrackMatch, 'match' => ($contextInputMatch . $lookaheadMatch), 'nBacktrack' => count($backtrackGlyphs), 'nInput' => $nInput, 'nLookahead' => count($lookaheadGlyphs), 'rules' => array(),);
2676
2677
2678 for ($b = 0; $b < $rule['SubstCount']; $b++) {
2679 $lup = $rule['LookupListIndex'][$b];
2680 $seqIndex = $rule['SequenceIndex'][$b];
2681
2682 // $Lookup[$lup] = secondary Lookup
2683 for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
2684 if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
2685 foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] AS $luss) {
2686
2687 $lookupGlyphs = $luss['Replace'];
2688 $mLen = count($lookupGlyphs);
2689
2690 // Only apply if the (first) 'Replace' glyph from the
2691 // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
2692 // then apply the substitution
2693 if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) {
2694 continue;
2695 }
2696
2697 // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
2698 $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex);
2699
2700 $REPL = implode(" ", $luss['substitute']);
2701
2702 if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
2703 $volt[] = array('match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore);
2704 } else {
2705 $subRule['rules'][] = array('type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],);
2706 }
2707 }
2708 }
2709 }
2710 }
2711
2712
2713 if (count($subRule['rules']))
2714 $volt[] = $subRule;
2715 }
2716 }
2717 }
2718 // Format 2: Class-based Chaining Context Glyph Substitution p257
2719 else if ($SubstFormat == 2) {
2720 $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
2721 foreach ($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'] AS $inputClass => $cscs) {
2722 for ($cscrule = 0; $cscrule < $cscs['ChainSubClassRuleCnt']; $cscrule++) {
2723 $rule = $cscs['ChainSubClassRule'][$cscrule];
2724
2725 // These contain classes of glyphs as strings
2726 // $Lookup[$i]['Subtable'][$c]['InputClasses'][(class)] e.g. 02E6|02E7|02E8
2727 // $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][(class)]
2728 // $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][(class)]
2729 // These contain arrays of classIndexes
2730 // [Backtrack] [Lookahead] and [Input] (Input is from the second position only)
2731
2732 $inputGlyphs = array();
2733
2734 if (isset($Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass])) {
2735 $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass];
2736 } else {
2737 $inputGlyphs[0] = '';
2738 }
2739 if ($rule['InputGlyphCount'] > 1) {
2740 // NB starts at 1
2741 for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) {
2742 $classindex = $rule['Input'][$gcl];
2743 if (isset($Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex])) {
2744 $inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex];
2745 }
2746 // if class[0] = all glyphs excluding those specified in all other classes
2747 // set to blank '' for now
2748 else {
2749 $inputGlyphs[$gcl] = '';
2750 }
2751 }
2752 }
2753
2754 $nInput = $rule['InputGlyphCount'];
2755
2756 if ($rule['BacktrackGlyphCount']) {
2757 for ($gcl = 0; $gcl < $rule['BacktrackGlyphCount']; $gcl++) {
2758 $classindex = $rule['Backtrack'][$gcl];
2759 if (isset($Lookup[$i]['Subtable'][$c]['BacktrackClasses'][$classindex])) {
2760 $backtrackGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][$classindex];
2761 }
2762 // if class[0] = all glyphs excluding those specified in all other classes
2763 // set to blank '' for now
2764 else {
2765 $backtrackGlyphs[$gcl] = '';
2766 }
2767 }
2768 } else {
2769 $backtrackGlyphs = array();
2770 }
2771 // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦
2772 $backtrackMatch = $this->_makeGSUBbacktrackMatch($backtrackGlyphs, $ignore);
2773
2774 if ($rule['LookaheadGlyphCount']) {
2775 for ($gcl = 0; $gcl < $rule['LookaheadGlyphCount']; $gcl++) {
2776 $classindex = $rule['Lookahead'][$gcl];
2777 if (isset($Lookup[$i]['Subtable'][$c]['LookaheadClasses'][$classindex])) {
2778 $lookaheadGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][$classindex];
2779 }
2780 // if class[0] = all glyphs excluding those specified in all other classes
2781 // set to blank '' for now
2782 else {
2783 $lookaheadGlyphs[$gcl] = '';
2784 }
2785 }
2786 } else {
2787 $lookaheadGlyphs = array();
2788 }
2789 // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦
2790 $lookaheadMatch = $this->_makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore);
2791
2792 $nBsubs = 2 * count($backtrackGlyphs);
2793 $nIsubs = (2 * $nInput) - 1;
2794
2795 $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, array(), 0);
2796 $subRule = array('context' => 1, 'tag' => $tag, 'matchback' => $backtrackMatch, 'match' => ($contextInputMatch . $lookaheadMatch), 'nBacktrack' => count($backtrackGlyphs), 'nInput' => $nInput, 'nLookahead' => count($lookaheadGlyphs), 'rules' => array(),);
2797
2798 for ($b = 0; $b < $rule['SubstCount']; $b++) {
2799 $lup = $rule['LookupListIndex'][$b];
2800 $seqIndex = $rule['SequenceIndex'][$b];
2801
2802 // $Lookup[$lup] = secondary Lookup
2803 for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
2804 if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
2805 foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] AS $luss) {
2806
2807 $lookupGlyphs = $luss['Replace'];
2808 $mLen = count($lookupGlyphs);
2809
2810 // Only apply if the (first) 'Replace' glyph from the
2811 // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
2812 // then apply the substitution
2813 if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) {
2814 continue;
2815 }
2816
2817 // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
2818 $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex);
2819 $REPL = implode(" ", $luss['substitute']);
2820 // Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}"
2821
2822 if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
2823 $volt[] = array('match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore);
2824 } else {
2825 $subRule['rules'][] = array('type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],);
2826 }
2827 }
2828 }
2829 }
2830 }
2831 if (count($subRule['rules']))
2832 $volt[] = $subRule;
2833 }
2834 }
2835
2836
2837 //print_r($Lookup[$i]['Subtable'][$c]); exit;
2838 }
2839 // Format 3: Coverage-based Chaining Context Glyph Substitution p259
2840 else if ($SubstFormat == 3) {
2841 // IgnoreMarks flag set on main Lookup table
2842 $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
2843 $inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'];
2844 $CoverageInputGlyphs = implode('|', $inputGlyphs);
2845 $nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount'];
2846
2847 if ($Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']) {
2848 $backtrackGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'];
2849 } else {
2850 $backtrackGlyphs = array();
2851 }
2852 // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦
2853 $backtrackMatch = $this->_makeGSUBbacktrackMatch($backtrackGlyphs, $ignore);
2854
2855 if ($Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']) {
2856 $lookaheadGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'];
2857 } else {
2858 $lookaheadGlyphs = array();
2859 }
2860 // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦
2861 $lookaheadMatch = $this->_makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore);
2862
2863 $nBsubs = 2 * count($backtrackGlyphs);
2864 $nIsubs = (2 * $nInput) - 1;
2865 $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, array(), 0);
2866 $subRule = array('context' => 1, 'tag' => $tag, 'matchback' => $backtrackMatch, 'match' => ($contextInputMatch . $lookaheadMatch), 'nBacktrack' => count($backtrackGlyphs), 'nInput' => $nInput, 'nLookahead' => count($lookaheadGlyphs), 'rules' => array(),);
2867
2868 for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) {
2869 $lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'];
2870 $seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'];
2871 for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
2872 if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
2873 foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] AS $luss) {
2874 $lookupGlyphs = $luss['Replace'];
2875 $mLen = count($lookupGlyphs);
2876
2877 // Only apply if the (first) 'Replace' glyph from the
2878 // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
2879 // then apply the substitution
2880 if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) {
2881 continue;
2882 }
2883
2884 // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
2885 $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex);
2886 $REPL = implode(" ", $luss['substitute']);
2887
2888 if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
2889 $volt[] = array('match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore);
2890 } else {
2891 $subRule['rules'][] = array('type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],);
2892 }
2893 }
2894 }
2895 }
2896 }
2897 if (count($subRule['rules']))
2898 $volt[] = $subRule;
2899 }
2900 }
2901 }
2902 }
2903 //print_r($Lookup); exit;
2904 return $volt;
2905 }
2906
2907 //=====================================================================================
2908 //=====================================================================================
2909 // mPDF 5.7.1
2910 function _checkGSUBignore($flag, $glyph, $MarkFilteringSet)
2911 {
2912 $ignore = false;
2913 // Flag & 0x0008 = Ignore Marks - (unless already done with MarkAttachmentType)
2914 if ((($flag & 0x0008) == 0x0008 && ($flag & 0xFF00) == 0) && strpos($this->GlyphClassMarks, $glyph)) {
2915 $ignore = true;
2916 }
2917 if ((($flag & 0x0004) == 0x0004) && strpos($this->GlyphClassLigatures, $glyph)) {
2918 $ignore = true;
2919 }
2920 if ((($flag & 0x0002) == 0x0002) && strpos($this->GlyphClassBases, $glyph)) {
2921 $ignore = true;
2922 }
2923 // Flag & 0xFF?? = MarkAttachmentType
2924 if ($flag & 0xFF00) {
2925 // "a lookup must ignore any mark glyphs that are not in the specified mark attachment class"
2926 // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table
2927 if (strpos($this->MarkAttachmentType[($flag >> 8)], $glyph)) {
2928 $ignore = true;
2929 }
2930 }
2931 // Flag & 0x0010 = UseMarkFilteringSet
2932 if (($flag & 0x0010) && strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) {
2933 $ignore = true;
2934 }
2935 return $ignore;
2936 }
2937
2938 function _getGSUBignoreString($flag, $MarkFilteringSet)
2939 {
2940 // If ignoreFlag set, combine all ignore glyphs into -> "((?:(?: FBA1| FBA2| FBA3))*)"
2941 // else "()"
2942 // for Input - set on secondary Lookup table if in Context, and set Backtrack and Lookahead on Context Lookup
2943 $str = "";
2944 $ignoreflag = 0;
2945
2946 // Flag & 0xFF?? = MarkAttachmentType
2947 if ($flag & 0xFF00) {
2948 // "a lookup must ignore any mark glyphs that are not in the specified mark attachment class"
2949 // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table
2950 $MarkAttachmentType = $flag >> 8;
2951 $ignoreflag = $flag;
2952 $str = $this->MarkAttachmentType[$MarkAttachmentType];
2953 }
2954
2955 // Flag & 0x0010 = UseMarkFilteringSet
2956 if ($flag & 0x0010) {
2957 throw new MpdfException("This font " . $this->fontkey . " contains MarkGlyphSets - Not tested yet");
2958 $str = $this->MarkGlyphSets[$MarkFilteringSet];
2959 }
2960
2961 // If Ignore Marks set, supercedes any above
2962 // Flag & 0x0008 = Ignore Marks - (unless already done with MarkAttachmentType)
2963 if (($flag & 0x0008) == 0x0008 && ($flag & 0xFF00) == 0) {
2964 $ignoreflag = 8;
2965 $str = $this->GlyphClassMarks;
2966 }
2967
2968 // Flag & 0x0004 = Ignore Ligatures
2969 if (($flag & 0x0004) == 0x0004) {
2970 $ignoreflag += 4;
2971 if ($str) {
2972 $str .= "|";
2973 }
2974 $str .= $this->GlyphClassLigatures;
2975 }
2976 // Flag & 0x0002 = Ignore BaseGlyphs
2977 if (($flag & 0x0002) == 0x0002) {
2978 $ignoreflag += 2;
2979 if ($str) {
2980 $str .= "|";
2981 }
2982 $str .= $this->GlyphClassBases;
2983 }
2984 if ($str) {
2985 // This originally returned e.g. ((?:(?:[IGNORE8]))*) when NOT specific to a Lookup e.g. rtlSub in
2986 // arabictypesetting.GSUB.arab.DFLT.php
2987 // This would save repeatedly saving long text strings if used multiple times
2988 // When writing e.g. arabictypesetting.GSUB.arab.DFLT.php to file, included as $ignore[8]
2989 // Would need to also write the $ignore array to that file
2990 // // If UseMarkFilteringSet (specific to the Lookup) return the string
2991 // if (($flag & 0x0010) && ($flag & 0x0008) != 0x0008) {
2992 // return "((?:(?:" . $str . "))*)";
2993 // }
2994 // else { return "((?:(?:" . "[IGNORE".$ignoreflag."]" . "))*)"; }
2995 // // e.g. ((?:(?: 0031| 0032| 0033| 0034| 0045))*)
2996 // But never finished coding it to add the $ignore array to the file, and it doesn't seem to occur often enough to be worth
2997 // writing. So just output it as a string:
2998 return "((?:(?:" . $str . "))*)";
2999 } else
3000 return "()";
3001 }
3002
3003 // GSUB Patterns
3004
3005 /*
3006 BACKTRACK INPUT LOOKAHEAD
3007 ================================== ================== ==================================
3008 (FEEB|FEEC)(ign) ¦(FD12|FD13)(ign) ¦(0612)¦(ign) (0613)¦(ign) (FD12|FD13)¦(ign) (FEEB|FEEC)
3009 ---------------- ---------------- ----- ------------ --------------- ---------------
3010 Backtrack 1 Backtrack 2 Input 1 Input 2 Lookahead 1 Lookahead 2
3011 -------- --- --------- --- ---- --- ---- --- --------- --- -------
3012 \${1} \${2} \${3} \${4} \${5+} \${6+} \${7+} \${8+}
3013
3014 nBacktrack = 2 nInput = 2 nLookahead = 2
3015
3016 nBsubs = 2xnBack nIsubs = (nBsubs+) nLsubs = (nBsubs+nIsubs+) 2xnLookahead
3017 "\${1}\${2} " (nInput*2)-1 "\${5+} \${6+}"
3018 "REPL"
3019
3020 ¦\${1}\${2} ¦\${3}\${4} ¦REPL¦\${5+} \${6+}¦\${7+} \${8+}¦
3021
3022
3023 INPUT nInput = 5
3024 ============================================================
3025 ¦(0612)¦(ign) (0613)¦(ign) (0614)¦(ign) (0615)¦(ign) (0615)¦
3026 \${1} \${2} \${3} \${4} \${5} \${6} \${7} \${8} \${9} (All backreference numbers are + nBsubs)
3027 ----- ------------ ------------ ------------ ------------
3028 Input 1 Input 2 Input 3 Input 4 Input 5
3029
3030 A====== SequenceIndex=1 ; Lookup match nGlyphs=1
3031 B=================== SequenceIndex=1 ; Lookup match nGlyphs=2
3032 C=============================== SequenceIndex=1 ; Lookup match nGlyphs=3
3033 D======================= SequenceIndex=2 ; Lookup match nGlyphs=2
3034 E===================================== SequenceIndex=2 ; Lookup match nGlyphs=3
3035 F====================== SequenceIndex=4 ; Lookup match nGlyphs=2
3036
3037 All backreference numbers are + nBsubs
3038 A - "REPL\${2} \${3}\${4} \${5}\${6} \${7}\${8} \${9}"
3039 B - "REPL\${2}\${4} \${5}\${6} \${7}\${8} \${9}"
3040 C - "REPL\${2}\${4}\${6} \${7}\${8} \${9}"
3041 D - "\${1} REPL\${2}\${4}\${6} \${7}\${8} \${9}"
3042 E - "\${1} REPL\${2}\${4}\${6}\${8} \${9}"
3043 F - "\${1}\${2} \${3}\${4} \${5} REPL\${6}\${8}"
3044 */
3045
3046 function _makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex)
3047 {
3048 // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
3049 // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
3050 // $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context
3051 // $lookupGlyphs = array of glyphs (single Glyphs) making up Lookup Input sequence
3052 $mLen = count($lookupGlyphs); // nGlyphs in the secondary Lookup match
3053 $nInput = count($inputGlyphs); // nGlyphs in the Primary Input sequence
3054 $str = "";
3055 for ($i = 0; $i < $nInput; $i++) {
3056 if ($i > 0) {
3057 $str .= $ignore . " ";
3058 }
3059 if ($i >= $seqIndex && $i < ($seqIndex + $mLen)) {
3060 $str .= "(" . $lookupGlyphs[($i - $seqIndex)] . ")";
3061 } else {
3062 $str .= "(" . $inputGlyphs[($i)] . ")";
3063 }
3064 }
3065 return $str;
3066 }
3067
3068 function _makeGSUBinputMatch($inputGlyphs, $ignore)
3069 {
3070 // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
3071 // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
3072 // $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context
3073 // $lookupGlyphs = array of glyphs making up Lookup Input sequence - if applicable
3074 $str = "";
3075 for ($i = 1; $i <= count($inputGlyphs); $i++) {
3076 if ($i > 1) {
3077 $str .= $ignore . " ";
3078 }
3079 $str .= "(" . $inputGlyphs[($i - 1)] . ")";
3080 }
3081 return $str;
3082 }
3083
3084 function _makeGSUBbacktrackMatch($backtrackGlyphs, $ignore)
3085 {
3086 // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
3087 // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦
3088 // $backtrackGlyphs = array of glyphstrings making up Backtrack sequence
3089 // 3 2 1 0
3090 // each item being e.g. E0AD|E0AF|F1FD
3091 $str = "";
3092 for ($i = (count($backtrackGlyphs) - 1); $i >= 0; $i--) {
3093 $str .= "(" . $backtrackGlyphs[$i] . ")" . $ignore . " ";
3094 }
3095 return $str;
3096 }
3097
3098 function _makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore)
3099 {
3100 // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
3101 // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦
3102 // $lookaheadGlyphs = array of glyphstrings making up Lookahead sequence
3103 // 0 1 2 3
3104 // each item being e.g. E0AD|E0AF|F1FD
3105 $str = "";
3106 for ($i = 0; $i < count($lookaheadGlyphs); $i++) {
3107 $str .= $ignore . " (" . $lookaheadGlyphs[$i] . ")";
3108 }
3109 return $str;
3110 }
3111
3112 function _makeGSUBinputReplacement($nInput, $REPL, $ignore, $nBsubs, $mLen, $seqIndex)
3113 {
3114 // Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}"
3115 // $nInput nGlyphs in the Primary Input sequence
3116 // $REPL replacement glyphs from secondary lookup
3117 // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
3118 // $nBsubs Number of Backtrack substitutions (= 2x Number of Backtrack glyphs)
3119 // $mLen nGlyphs in the secondary Lookup match - if no secondary lookup, should=$nInput
3120 // $seqIndex Sequence Index to apply the secondary match
3121 if ($ignore == "()") {
3122 $ign = false;
3123 } else {
3124 $ign = true;
3125 }
3126 $str = "";
3127 if ($nInput == 1) {
3128 $str = $REPL;
3129 } else if ($nInput > 1) {
3130 if ($mLen == $nInput) { // whole string replaced
3131 $str = $REPL;
3132 if ($ign) {
3133 // for every nInput over 1, add another replacement backreference, to move IGNORES after replacement
3134 for ($x = 2; $x <= $nInput; $x++) {
3135 $str .= '\\' . ($nBsubs + (2 * ($x - 1)));
3136 }
3137 }
3138 } else { // if only part of string replaced:
3139 for ($x = 1; $x < ($seqIndex + 1); $x++) {
3140 if ($x == 1) {
3141 $str .= '\\' . ($nBsubs + 1);
3142 } else {
3143 if ($ign) {
3144 $str .= '\\' . ($nBsubs + (2 * ($x - 1)));
3145 }
3146 $str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1)));
3147 }
3148 }
3149 if ($seqIndex > 0) {
3150 $str .= " ";
3151 }
3152 $str .= $REPL;
3153 if ($ign) {
3154 for ($x = (max(($seqIndex + 1), 2)); $x < ($seqIndex + 1 + $mLen); $x++) { // move IGNORES after replacement
3155 $str .= '\\' . ($nBsubs + (2 * ($x - 1)));
3156 }
3157 }
3158 for ($x = ($seqIndex + 1 + $mLen); $x <= $nInput; $x++) {
3159 if ($ign) {
3160 $str .= '\\' . ($nBsubs + (2 * ($x - 1)));
3161 }
3162 $str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1)));
3163 }
3164 }
3165 }
3166 return $str;
3167 }
3168
3169 //////////////////////////////////////////////////////////////////////////////////
3170 function _getCoverage($convert2hex = true, $mode = 1)
3171 {
3172 $g = array();
3173 $ctr = 0;
3174 $CoverageFormat = $this->read_ushort();
3175 if ($CoverageFormat == 1) {
3176 $CoverageGlyphCount = $this->read_ushort();
3177 for ($gid = 0; $gid < $CoverageGlyphCount; $gid++) {
3178 $glyphID = $this->read_ushort();
3179 $uni = $this->glyphToChar[$glyphID][0];
3180 if ($convert2hex) {
3181 $g[] = unicode_hex($uni);
3182 } else if ($mode == 2) {
3183 $g[$uni] = $ctr;
3184 $ctr++;
3185 } else {
3186 $g[] = $glyphID;
3187 }
3188 }
3189 }
3190 if ($CoverageFormat == 2) {
3191 $RangeCount = $this->read_ushort();
3192 for ($r = 0; $r < $RangeCount; $r++) {
3193 $start = $this->read_ushort();
3194 $end = $this->read_ushort();
3195 $StartCoverageIndex = $this->read_ushort(); // n/a
3196 for ($glyphID = $start; $glyphID <= $end; $glyphID++) {
3197 $uni = $this->glyphToChar[$glyphID][0];
3198 if ($convert2hex) {
3199 $g[] = unicode_hex($uni);
3200 } else if ($mode == 2) {
3201 $uni = $g[$uni] = $ctr;
3202 $ctr++;
3203 } else {
3204 $g[] = $glyphID;
3205 }
3206 }
3207 }
3208 }
3209 return $g;
3210 }
3211
3212 //////////////////////////////////////////////////////////////////////////////////
3213 function _getClasses($offset)
3214 {
3215 $this->seek($offset);
3216 $ClassFormat = $this->read_ushort();
3217 $GlyphByClass = array();
3218 if ($ClassFormat == 1) {
3219 $StartGlyph = $this->read_ushort();
3220 $GlyphCount = $this->read_ushort();
3221 for ($i = 0; $i < $GlyphCount; $i++) {
3222 $startGlyphID = $StartGlyph + $i;
3223 $endGlyphID = $StartGlyph + $i;
3224 $class = $this->read_ushort();
3225 for ($g = $startGlyphID; $g <= $endGlyphID; $g++) {
3226 if (isset($this->glyphToChar[$g][0])) {
3227 $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]);
3228 }
3229 }
3230 }
3231 } else if ($ClassFormat == 2) {
3232 $tableCount = $this->read_ushort();
3233 for ($i = 0; $i < $tableCount; $i++) {
3234 $startGlyphID = $this->read_ushort();
3235 $endGlyphID = $this->read_ushort();
3236 $class = $this->read_ushort();
3237 for ($g = $startGlyphID; $g <= $endGlyphID; $g++) {
3238 if ($this->glyphToChar[$g][0]) {
3239 $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]);
3240 }
3241 }
3242 }
3243 }
3244 $gbc = array();
3245 foreach ($GlyphByClass AS $class => $garr) {
3246 $gbc[$class] = implode('|', $garr);
3247 }
3248 return $gbc;
3249 }
3250
3251 //////////////////////////////////////////////////////////////////////////////////
3252 //////////////////////////////////////////////////////////////////////////////////
3253 //////////////////////////////////////////////////////////////////////////////////
3254 //////////////////////////////////////////////////////////////////////////////////
3255 //////////////////////////////////////////////////////////////////////////////////
3256 function _getGPOStables()
3257 {
3258 ///////////////////////////////////
3259 // GPOS - Glyph Positioning
3260 ///////////////////////////////////
3261 if (isset($this->tables["GPOS"])) {
3262 $ffeats = array();
3263 $gpos_offset = $this->seek_table("GPOS");
3264 $this->skip(4);
3265 $ScriptList_offset = $gpos_offset + $this->read_ushort();
3266 $FeatureList_offset = $gpos_offset + $this->read_ushort();
3267 $LookupList_offset = $gpos_offset + $this->read_ushort();
3268
3269 // ScriptList
3270 $this->seek($ScriptList_offset);
3271 $ScriptCount = $this->read_ushort();
3272 for ($i = 0; $i < $ScriptCount; $i++) {
3273 $ScriptTag = $this->read_tag(); // = "beng", "deva" etc.
3274 $ScriptTableOffset = $this->read_ushort();
3275 $ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset;
3276 }
3277
3278 // Script Table
3279 foreach ($ffeats AS $t => $o) {
3280 $ls = array();
3281 $this->seek($o);
3282 $DefLangSys_offset = $this->read_ushort();
3283 if ($DefLangSys_offset > 0) {
3284 $ls['DFLT'] = $DefLangSys_offset + $o;
3285 }
3286 $LangSysCount = $this->read_ushort();
3287 for ($i = 0; $i < $LangSysCount; $i++) {
3288 $LangTag = $this->read_tag(); // =
3289 $LangTableOffset = $this->read_ushort();
3290 $ls[$LangTag] = $o + $LangTableOffset;
3291 }
3292 $ffeats[$t] = $ls;
3293 }
3294
3295
3296 // Get FeatureIndexList
3297 // LangSys Table - from first listed langsys
3298 foreach ($ffeats AS $st => $scripts) {
3299 foreach ($scripts AS $t => $o) {
3300 $FeatureIndex = array();
3301 $langsystable_offset = $o;
3302 $this->seek($langsystable_offset);
3303 $LookUpOrder = $this->read_ushort(); //==NULL
3304 $ReqFeatureIndex = $this->read_ushort();
3305 if ($ReqFeatureIndex != 0xFFFF) {
3306 $FeatureIndex[] = $ReqFeatureIndex;
3307 }
3308 $FeatureCount = $this->read_ushort();
3309 for ($i = 0; $i < $FeatureCount; $i++) {
3310 $FeatureIndex[] = $this->read_ushort(); // = index of feature
3311 }
3312 $ffeats[$st][$t] = $FeatureIndex;
3313 }
3314 }
3315 //print_r($ffeats); exit;
3316 // Feauture List => LookupListIndex es
3317 $this->seek($FeatureList_offset);
3318 $FeatureCount = $this->read_ushort();
3319 $Feature = array();
3320 for ($i = 0; $i < $FeatureCount; $i++) {
3321 $tag = $this->read_tag();
3322 if ($tag == 'kern') {
3323 $this->haskernGPOS = true;
3324 }
3325 $Feature[$i] = array('tag' => $tag);
3326 $Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort();
3327 }
3328 for ($i = 0; $i < $FeatureCount; $i++) {
3329 $this->seek($Feature[$i]['offset']);
3330 $this->read_ushort(); // null
3331 $Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort();
3332 $Feature[$i]['LookupListIndex'] = array();
3333 for ($c = 0; $c < $Lookupcount; $c++) {
3334 $Feature[$i]['LookupListIndex'][] = $this->read_ushort();
3335 }
3336 }
3337
3338
3339 foreach ($ffeats AS $st => $scripts) {
3340 foreach ($scripts AS $t => $o) {
3341 $FeatureIndex = $ffeats[$st][$t];
3342 foreach ($FeatureIndex AS $k => $fi) {
3343 $ffeats[$st][$t][$k] = $Feature[$fi];
3344 }
3345 }
3346 }
3347 //print_r($ffeats); exit;
3348 //=====================================================================================
3349 $gpos = array();
3350 $GPOSScriptLang = array();
3351 foreach ($ffeats AS $st => $scripts) {
3352 foreach ($scripts AS $t => $langsys) {
3353 $lg = array();
3354 foreach ($langsys AS $ft) {
3355 $lg[$ft['LookupListIndex'][0]] = $ft;
3356 }
3357 // list of Lookups in order they need to be run i.e. order listed in Lookup table
3358 ksort($lg);
3359 foreach ($lg AS $ft) {
3360 $gpos[$st][$t][$ft['tag']] = $ft['LookupListIndex'];
3361 }
3362 if (!isset($GPOSScriptLang[$st])) {
3363 $GPOSScriptLang[$st] = '';
3364 }
3365 $GPOSScriptLang[$st] .= $t . ' ';
3366 }
3367 }
3368
3369 //=====================================================================================
3370 // Get metadata and offsets for whole Lookup List table
3371 $this->seek($LookupList_offset);
3372 $LookupCount = $this->read_ushort();
3373 $Lookup = array();
3374 $Offsets = array();
3375 $SubtableCount = array();
3376 for ($i = 0; $i < $LookupCount; $i++) {
3377 $Offsets[$i] = $LookupList_offset + $this->read_ushort();
3378 }
3379 for ($i = 0; $i < $LookupCount; $i++) {
3380 $this->seek($Offsets[$i]);
3381 $Lookup[$i]['Type'] = $this->read_ushort();
3382 $Lookup[$i]['Flag'] = $flag = $this->read_ushort();
3383 $Lookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort();
3384 for ($c = 0; $c < $SubtableCount[$i]; $c++) {
3385 $Lookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort();
3386 }
3387 // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure
3388 if (($flag & 0x0010) == 0x0010) {
3389 $Lookup[$i]['MarkFilteringSet'] = $this->read_ushort();
3390 } else {
3391 $Lookup[$i]['MarkFilteringSet'] = '';
3392 }
3393
3394 // Lookup Type 9: Extension
3395 if ($Lookup[$i]['Type'] == 9) {
3396 // Overwrites new offset (32-bit) for each subtable, and a new lookup Type
3397 for ($c = 0; $c < $SubtableCount[$i]; $c++) {
3398 $this->seek($Lookup[$i]['Subtables'][$c]);
3399 $ExtensionPosFormat = $this->read_ushort();
3400 $type = $this->read_ushort();
3401 $Lookup[$i]['Subtables'][$c] = $Lookup[$i]['Subtables'][$c] + $this->read_ulong();
3402 }
3403 $Lookup[$i]['Type'] = $type;
3404 }
3405 }
3406
3407
3408 //=====================================================================================
3409 // Process Whole LookupList - Get LuCoverage = Lookup coverage just for first glyph
3410 $this->LuCoverage = array();
3411 for ($i = 0; $i < $LookupCount; $i++) {
3412 for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
3413
3414 $this->seek($Lookup[$i]['Subtables'][$c]);
3415 $PosFormat = $this->read_ushort();
3416
3417 if ($Lookup[$i]['Type'] == 7 && $PosFormat == 3) {
3418 $this->skip(4);
3419 } else if ($Lookup[$i]['Type'] == 8 && $PosFormat == 3) {
3420 $BacktrackGlyphCount = $this->read_ushort();
3421 $this->skip(2 * $BacktrackGlyphCount + 2);
3422 }
3423 // NB Coverage only looks at glyphs for position 1 (i.e. 7.3 and 8.3) // NEEDS TO READ ALL ********************
3424 // NB For e.g. Type 4, this may be the Coverage for the Mark
3425 $Coverage = $Lookup[$i]['Subtables'][$c] + $this->read_ushort();
3426 $this->seek($Coverage);
3427 $glyphs = $this->_getCoverage(false, 2);
3428 $this->LuCoverage[$i][$c] = $glyphs;
3429 }
3430 }
3431
3432
3433
3434 //=====================================================================================
3435 //print_r($GPOSScriptLang); exit;
3436 //print_r($gpos); exit;
3437 //print_r($Lookup); exit;
3438
3439
3440
3441
3442 $s = '<?php
3443 $LuCoverage = ' . var_export($this->LuCoverage, true) . ';
3444 ?>';
3445
3446
3447 file_put_contents(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GPOSdata.php', $s);
3448
3449
3450
3451 return array($GPOSScriptLang, $gpos, $Lookup);
3452 } // end if GPOS
3453 }
3454
3455 //////////////////////////////////////////////////////////////////////////////////
3456 //=====================================================================================
3457 //=====================================================================================
3458 //=====================================================================================
3459 //=====================================================================================
3460 //=====================================================================================
3461 //=====================================================================================
3462
3463 function makeSubset($file, &$subset, $TTCfontID = 0, $debug = false, $useOTL = false)
3464 { // mPDF 5.7.1
3465 $this->useOTL = $useOTL; // mPDF 5.7.1
3466 $this->filename = $file;
3467 $this->fh = fopen($file, 'rb');
3468
3469 if (!$this->fh) {
3470 throw new MpdfException('Can\'t open file ' . $file);
3471 }
3472
3473 $this->_pos = 0;
3474 $this->charWidths = '';
3475 $this->glyphPos = array();
3476 $this->charToGlyph = array();
3477 $this->tables = array();
3478 $this->otables = array();
3479 $this->ascent = 0;
3480 $this->descent = 0;
3481 $this->strikeoutSize = 0;
3482 $this->strikeoutPosition = 0;
3483 $this->numTTCFonts = 0;
3484 $this->TTCFonts = array();
3485 $this->skip(4);
3486 $this->maxUni = 0;
3487 if ($TTCfontID > 0) {
3488 $this->version = $version = $this->read_ulong(); // TTC Header version now
3489 if (!in_array($version, array(0x00010000, 0x00020000))) {
3490 throw new MpdfException("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
3491 }
3492 $this->numTTCFonts = $this->read_ulong();
3493 for ($i = 1; $i <= $this->numTTCFonts; $i++) {
3494 $this->TTCFonts[$i]['offset'] = $this->read_ulong();
3495 }
3496 $this->seek($this->TTCFonts[$TTCfontID]['offset']);
3497 $this->version = $version = $this->read_ulong(); // TTFont version again now
3498 }
3499 $this->readTableDirectory($debug);
3500
3501 ///////////////////////////////////
3502 // head - Font header table
3503 ///////////////////////////////////
3504 $this->seek_table("head");
3505 $this->skip(50);
3506 $indexToLocFormat = $this->read_ushort();
3507 $glyphDataFormat = $this->read_ushort();
3508
3509 ///////////////////////////////////
3510 // hhea - Horizontal header table
3511 ///////////////////////////////////
3512 $this->seek_table("hhea");
3513 $this->skip(32);
3514 $metricDataFormat = $this->read_ushort();
3515 $orignHmetrics = $numberOfHMetrics = $this->read_ushort();
3516
3517 ///////////////////////////////////
3518 // maxp - Maximum profile table
3519 ///////////////////////////////////
3520 $this->seek_table("maxp");
3521 $this->skip(4);
3522 $numGlyphs = $this->read_ushort();
3523
3524
3525 ///////////////////////////////////
3526 // cmap - Character to glyph index mapping table
3527 ///////////////////////////////////
3528 $cmap_offset = $this->seek_table("cmap");
3529 $this->skip(2);
3530 $cmapTableCount = $this->read_ushort();
3531 $unicode_cmap_offset = 0;
3532 for ($i = 0; $i < $cmapTableCount; $i++) {
3533 $platformID = $this->read_ushort();
3534 $encodingID = $this->read_ushort();
3535 $offset = $this->read_ulong();
3536 $save_pos = $this->_pos;
3537 if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
3538 $format = $this->get_ushort($cmap_offset + $offset);
3539 if ($format == 4) {
3540 $unicode_cmap_offset = $cmap_offset + $offset;
3541 break;
3542 }
3543 }
3544 $this->seek($save_pos);
3545 }
3546
3547 if (!$unicode_cmap_offset) {
3548 throw new MpdfException('Font (' . $this->filename . ') does not have Unicode cmap (platform 3, encoding 1, format 4, or platform 0 [any encoding] format 4)');
3549 }
3550
3551
3552 $glyphToChar = array();
3553 $charToGlyph = array();
3554 $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph);
3555
3556 ///////////////////////////////////
3557 // mPDF 5.7.1
3558 // Map Unmapped glyphs - from $numGlyphs
3559 if ($useOTL) {
3560 $bctr = 0xE000;
3561 for ($gid = 1; $gid < $numGlyphs; $gid++) {
3562 if (!isset($glyphToChar[$gid])) {
3563 while (isset($charToGlyph[$bctr])) {
3564 $bctr++;
3565 } // Avoid overwriting a glyph already mapped in PUA
3566 if ($bctr > 0xF8FF) {
3567 throw new MpdfException($file . " : WARNING - Font cannot map all included glyphs into Private Use Area U+E000 - U+F8FF; cannot use useOTL on this font");
3568 }
3569 $glyphToChar[$gid][] = $bctr;
3570 $charToGlyph[$bctr] = $gid;
3571 $bctr++;
3572 }
3573 }
3574 }
3575 ///////////////////////////////////
3576
3577 $this->charToGlyph = $charToGlyph;
3578 $this->glyphToChar = $glyphToChar;
3579
3580
3581 ///////////////////////////////////
3582 // hmtx - Horizontal metrics table
3583 ///////////////////////////////////
3584 $scale = 1; // not used
3585 $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
3586
3587 ///////////////////////////////////
3588 // loca - Index to location
3589 ///////////////////////////////////
3590 $this->getLOCA($indexToLocFormat, $numGlyphs);
3591
3592 $subsetglyphs = array(0 => 0, 1 => 1, 2 => 2);
3593 $subsetCharToGlyph = array();
3594 foreach ($subset AS $code) {
3595 if (isset($this->charToGlyph[$code])) {
3596 $subsetglyphs[$this->charToGlyph[$code]] = $code; // Old Glyph ID => Unicode
3597 $subsetCharToGlyph[$code] = $this->charToGlyph[$code]; // Unicode to old GlyphID
3598 }
3599 $this->maxUni = max($this->maxUni, $code);
3600 }
3601
3602 list($start, $dummy) = $this->get_table_pos('glyf');
3603
3604 $glyphSet = array();
3605 ksort($subsetglyphs);
3606 $n = 0;
3607 $fsLastCharIndex = 0; // maximum Unicode index (character code) in this font, according to the cmap subtable for platform ID 3 and platform- specific encoding ID 0 or 1.
3608 foreach ($subsetglyphs AS $originalGlyphIdx => $uni) {
3609 $fsLastCharIndex = max($fsLastCharIndex, $uni);
3610 $glyphSet[$originalGlyphIdx] = $n; // old glyphID to new glyphID
3611 $n++;
3612 }
3613
3614 ksort($subsetCharToGlyph);
3615 foreach ($subsetCharToGlyph AS $uni => $originalGlyphIdx) {
3616 $codeToGlyph[$uni] = $glyphSet[$originalGlyphIdx];
3617 }
3618 $this->codeToGlyph = $codeToGlyph;
3619
3620 ksort($subsetglyphs);
3621 foreach ($subsetglyphs AS $originalGlyphIdx => $uni) {
3622 $this->getGlyphs($originalGlyphIdx, $start, $glyphSet, $subsetglyphs);
3623 }
3624
3625 $numGlyphs = $numberOfHMetrics = count($subsetglyphs);
3626
3627 ///////////////////////////////////
3628 // name - table copied from the original
3629 ///////////////////////////////////
3630 // MS spec says that "Platform and encoding ID's in the name table should be consistent with those in the cmap table.
3631 // If they are not, the font will not load in Windows"
3632 // Doesn't seem to be a problem?
3633 ///////////////////////////////////
3634 $this->add('name', $this->get_table('name'));
3635
3636 ///////////////////////////////////
3637 //tables copied from the original
3638 ///////////////////////////////////
3639 $tags = array('cvt ', 'fpgm', 'prep', 'gasp');
3640 foreach ($tags AS $tag) {
3641 if (isset($this->tables[$tag])) {
3642 $this->add($tag, $this->get_table($tag));
3643 }
3644 }
3645
3646 ///////////////////////////////////
3647 // post - PostScript
3648 ///////////////////////////////////
3649 if (isset($this->tables['post'])) {
3650 $opost = $this->get_table('post');
3651 $post = "\x00\x03\x00\x00" . substr($opost, 4, 12) . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
3652 $this->add('post', $post);
3653 }
3654
3655 ///////////////////////////////////
3656 // Sort CID2GID map into segments of contiguous codes
3657 ///////////////////////////////////
3658 ksort($codeToGlyph);
3659 unset($codeToGlyph[0]);
3660 //unset($codeToGlyph[65535]);
3661 $rangeid = 0;
3662 $range = array();
3663 $prevcid = -2;
3664 $prevglidx = -1;
3665 // for each character
3666 foreach ($codeToGlyph as $cid => $glidx) {
3667 if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) {
3668 $range[$rangeid][] = $glidx;
3669 } else {
3670 // new range
3671 $rangeid = $cid;
3672 $range[$rangeid] = array();
3673 $range[$rangeid][] = $glidx;
3674 }
3675 $prevcid = $cid;
3676 $prevglidx = $glidx;
3677 }
3678
3679
3680
3681 ///////////////////////////////////
3682 // CMap table
3683 ///////////////////////////////////
3684 // cmap - Character to glyph mapping
3685 $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF
3686 $searchRange = 1;
3687 $entrySelector = 0;
3688 while ($searchRange * 2 <= $segCount) {
3689 $searchRange = $searchRange * 2;
3690 $entrySelector = $entrySelector + 1;
3691 }
3692 $searchRange = $searchRange * 2;
3693 $rangeShift = $segCount * 2 - $searchRange;
3694 $length = 16 + (8 * $segCount ) + ($numGlyphs + 1);
3695 $cmap = array(0, 3, // Index : version, number of encoding subtables
3696 0, 0, // Encoding Subtable : platform (UNI=0), encoding 0
3697 0, 28, // Encoding Subtable : offset (hi,lo)
3698 0, 3, // Encoding Subtable : platform (UNI=0), encoding 3
3699 0, 28, // Encoding Subtable : offset (hi,lo)
3700 3, 1, // Encoding Subtable : platform (MS=3), encoding 1
3701 0, 28, // Encoding Subtable : offset (hi,lo)
3702 4, $length, 0, // Format 4 Mapping subtable: format, length, language
3703 $segCount * 2,
3704 $searchRange,
3705 $entrySelector,
3706 $rangeShift);
3707
3708 // endCode(s)
3709 foreach ($range AS $start => $subrange) {
3710 $endCode = $start + (count($subrange) - 1);
3711 $cmap[] = $endCode; // endCode(s)
3712 }
3713 $cmap[] = 0xFFFF; // endCode of last Segment
3714 $cmap[] = 0; // reservedPad
3715 // startCode(s)
3716 foreach ($range AS $start => $subrange) {
3717 $cmap[] = $start; // startCode(s)
3718 }
3719 $cmap[] = 0xFFFF; // startCode of last Segment
3720 // idDelta(s)
3721 foreach ($range AS $start => $subrange) {
3722 $idDelta = -($start - $subrange[0]);
3723 $n += count($subrange);
3724 $cmap[] = $idDelta; // idDelta(s)
3725 }
3726 $cmap[] = 1; // idDelta of last Segment
3727 // idRangeOffset(s)
3728 foreach ($range AS $subrange) {
3729 $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
3730 }
3731 $cmap[] = 0; // idRangeOffset of last Segment
3732 foreach ($range AS $subrange) {
3733 foreach ($subrange AS $glidx) {
3734 $cmap[] = $glidx;
3735 }
3736 }
3737 $cmap[] = 0; // Mapping for last character
3738 $cmapstr = '';
3739 foreach ($cmap AS $cm) {
3740 $cmapstr .= pack("n", $cm);
3741 }
3742 $this->add('cmap', $cmapstr);
3743
3744
3745 ///////////////////////////////////
3746 // glyf - Glyph data
3747 ///////////////////////////////////
3748 list($glyfOffset, $glyfLength) = $this->get_table_pos('glyf');
3749 if ($glyfLength < $this->maxStrLenRead) {
3750 $glyphData = $this->get_table('glyf');
3751 }
3752
3753 $offsets = array();
3754 $glyf = '';
3755 $pos = 0;
3756 $hmtxstr = '';
3757 $xMinT = 0;
3758 $yMinT = 0;
3759 $xMaxT = 0;
3760 $yMaxT = 0;
3761 $advanceWidthMax = 0;
3762 $minLeftSideBearing = 0;
3763 $minRightSideBearing = 0;
3764 $xMaxExtent = 0;
3765 $maxPoints = 0; // points in non-compound glyph
3766 $maxContours = 0; // contours in non-compound glyph
3767 $maxComponentPoints = 0; // points in compound glyph
3768 $maxComponentContours = 0; // contours in compound glyph
3769 $maxComponentElements = 0; // number of glyphs referenced at top level
3770 $maxComponentDepth = 0; // levels of recursion, set to 0 if font has only simple glyphs
3771 $this->glyphdata = array();
3772
3773 foreach ($subsetglyphs AS $originalGlyphIdx => $uni) {
3774 // hmtx - Horizontal Metrics
3775 $hm = $this->getHMetric($orignHmetrics, $originalGlyphIdx);
3776 $hmtxstr .= $hm;
3777
3778 $offsets[] = $pos;
3779 $glyphPos = $this->glyphPos[$originalGlyphIdx];
3780 $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
3781 if ($glyfLength < $this->maxStrLenRead) {
3782 $data = substr($glyphData, $glyphPos, $glyphLen);
3783 } else {
3784 if ($glyphLen > 0)
3785 $data = $this->get_chunk($glyfOffset + $glyphPos, $glyphLen);
3786 else
3787 $data = '';
3788 }
3789
3790 if ($glyphLen > 0) {
3791 if (_RECALC_PROFILE) {
3792 $xMin = $this->unpack_short(substr($data, 2, 2));
3793 $yMin = $this->unpack_short(substr($data, 4, 2));
3794 $xMax = $this->unpack_short(substr($data, 6, 2));
3795 $yMax = $this->unpack_short(substr($data, 8, 2));
3796 $xMinT = min($xMinT, $xMin);
3797 $yMinT = min($yMinT, $yMin);
3798 $xMaxT = max($xMaxT, $xMax);
3799 $yMaxT = max($yMaxT, $yMax);
3800 $aw = $this->unpack_short(substr($hm, 0, 2));
3801 $lsb = $this->unpack_short(substr($hm, 2, 2));
3802 $advanceWidthMax = max($advanceWidthMax, $aw);
3803 $minLeftSideBearing = min($minLeftSideBearing, $lsb);
3804 $minRightSideBearing = min($minRightSideBearing, ($aw - $lsb - ($xMax - $xMin)));
3805 $xMaxExtent = max($xMaxExtent, ($lsb + ($xMax - $xMin)));
3806 }
3807 $up = unpack("n", substr($data, 0, 2));
3808 }
3809 if ($glyphLen > 2 && ($up[1] & (1 << 15))) { // If number of contours <= -1 i.e. composiste glyph
3810 $pos_in_glyph = 10;
3811 $flags = GF_MORE;
3812 $nComponentElements = 0;
3813 while ($flags & GF_MORE) {
3814 $nComponentElements += 1; // number of glyphs referenced at top level
3815 $up = unpack("n", substr($data, $pos_in_glyph, 2));
3816 $flags = $up[1];
3817 $up = unpack("n", substr($data, $pos_in_glyph + 2, 2));
3818 $glyphIdx = $up[1];
3819 $this->glyphdata[$originalGlyphIdx]['compGlyphs'][] = $glyphIdx;
3820 $data = $this->_set_ushort($data, $pos_in_glyph + 2, $glyphSet[$glyphIdx]);
3821 $pos_in_glyph += 4;
3822 if ($flags & GF_WORDS) {
3823 $pos_in_glyph += 4;
3824 } else {
3825 $pos_in_glyph += 2;
3826 }
3827 if ($flags & GF_SCALE) {
3828 $pos_in_glyph += 2;
3829 } else if ($flags & GF_XYSCALE) {
3830 $pos_in_glyph += 4;
3831 } else if ($flags & GF_TWOBYTWO) {
3832 $pos_in_glyph += 8;
3833 }
3834 }
3835 $maxComponentElements = max($maxComponentElements, $nComponentElements);
3836 }
3837 // Simple Glyph
3838 else if (_RECALC_PROFILE && $glyphLen > 2 && $up[1] < (1 << 15) && $up[1] > 0) { // Number of contours > 0 simple glyph
3839 $nContours = $up[1];
3840 $this->glyphdata[$originalGlyphIdx]['nContours'] = $nContours;
3841 $maxContours = max($maxContours, $nContours);
3842
3843 // Count number of points in simple glyph
3844 $pos_in_glyph = 10 + ($nContours * 2) - 2; // Last endContourPoint
3845 $up = unpack("n", substr($data, $pos_in_glyph, 2));
3846 $points = $up[1] + 1;
3847 $this->glyphdata[$originalGlyphIdx]['nPoints'] = $points;
3848 $maxPoints = max($maxPoints, $points);
3849 }
3850
3851 $glyf .= $data;
3852 $pos += $glyphLen;
3853 if ($pos % 4 != 0) {
3854 $padding = 4 - ($pos % 4);
3855 $glyf .= str_repeat("\0", $padding);
3856 $pos += $padding;
3857 }
3858 }
3859
3860 if (_RECALC_PROFILE) {
3861 foreach ($this->glyphdata AS $originalGlyphIdx => $val) {
3862 $maxdepth = $depth = -1;
3863 $points = 0;
3864 $contours = 0;
3865 $this->getGlyphData($originalGlyphIdx, $maxdepth, $depth, $points, $contours);
3866 $maxComponentDepth = max($maxComponentDepth, $maxdepth);
3867 $maxComponentPoints = max($maxComponentPoints, $points);
3868 $maxComponentContours = max($maxComponentContours, $contours);
3869 }
3870 }
3871
3872
3873 $offsets[] = $pos;
3874 $this->add('glyf', $glyf);
3875
3876 ///////////////////////////////////
3877 // hmtx - Horizontal Metrics
3878 ///////////////////////////////////
3879 $this->add('hmtx', $hmtxstr);
3880
3881
3882 ///////////////////////////////////
3883 // loca - Index to location
3884 ///////////////////////////////////
3885 $locastr = '';
3886 if ((($pos + 1) >> 1) > 0xFFFF) {
3887 $indexToLocFormat = 1; // long format
3888 foreach ($offsets AS $offset) {
3889 $locastr .= pack("N", $offset);
3890 }
3891 } else {
3892 $indexToLocFormat = 0; // short format
3893 foreach ($offsets AS $offset) {
3894 $locastr .= pack("n", ($offset / 2));
3895 }
3896 }
3897 $this->add('loca', $locastr);
3898
3899 ///////////////////////////////////
3900 // head - Font header
3901 ///////////////////////////////////
3902 $head = $this->get_table('head');
3903 $head = $this->_set_ushort($head, 50, $indexToLocFormat);
3904 if (_RECALC_PROFILE) {
3905 $head = $this->_set_short($head, 36, $xMinT); // for all glyph bounding boxes
3906 $head = $this->_set_short($head, 38, $yMinT); // for all glyph bounding boxes
3907 $head = $this->_set_short($head, 40, $xMaxT); // for all glyph bounding boxes
3908 $head = $this->_set_short($head, 42, $yMaxT); // for all glyph bounding boxes
3909 $head[17] = chr($head[17] & ~(1 << 4)); // Unset Bit 4 (as hdmx/LTSH tables not included)
3910 }
3911 $this->add('head', $head);
3912
3913
3914 ///////////////////////////////////
3915 // hhea - Horizontal Header
3916 ///////////////////////////////////
3917 $hhea = $this->get_table('hhea');
3918 $hhea = $this->_set_ushort($hhea, 34, $numberOfHMetrics);
3919 if (_RECALC_PROFILE) {
3920 $hhea = $this->_set_ushort($hhea, 10, $advanceWidthMax);
3921 $hhea = $this->_set_short($hhea, 12, $minLeftSideBearing);
3922 $hhea = $this->_set_short($hhea, 14, $minRightSideBearing);
3923 $hhea = $this->_set_short($hhea, 16, $xMaxExtent);
3924 }
3925 $this->add('hhea', $hhea);
3926
3927 ///////////////////////////////////
3928 // maxp - Maximum Profile
3929 ///////////////////////////////////
3930 $maxp = $this->get_table('maxp');
3931 $maxp = $this->_set_ushort($maxp, 4, $numGlyphs);
3932 if (_RECALC_PROFILE) {
3933 $maxp = $this->_set_ushort($maxp, 6, $maxPoints); // points in non-compound glyph
3934 $maxp = $this->_set_ushort($maxp, 8, $maxContours); // contours in non-compound glyph
3935 $maxp = $this->_set_ushort($maxp, 10, $maxComponentPoints); // points in compound glyph
3936 $maxp = $this->_set_ushort($maxp, 12, $maxComponentContours); // contours in compound glyph
3937 $maxp = $this->_set_ushort($maxp, 28, $maxComponentElements); // number of glyphs referenced at top level
3938 $maxp = $this->_set_ushort($maxp, 30, $maxComponentDepth); // levels of recursion, set to 0 if font has only simple glyphs
3939 }
3940 $this->add('maxp', $maxp);
3941
3942
3943 ///////////////////////////////////
3944 // OS/2 - OS/2
3945 ///////////////////////////////////
3946 if (isset($this->tables['OS/2'])) {
3947 $os2_offset = $this->seek_table("OS/2");
3948 if (_RECALC_PROFILE) {
3949 $fsSelection = $this->get_ushort($os2_offset + 62);
3950 $fsSelection = ($fsSelection & ~(1 << 6)); // 2-byte bit field containing information concerning the nature of the font patterns
3951 // bit#0 = Italic; bit#5=Bold
3952 // Match name table's font subfamily string
3953 // Clear bit#6 used for 'Regular' and optional
3954 }
3955
3956 // NB Currently this method never subsets characters above BMP
3957 // Could set nonBMP bit according to $this->maxUni
3958 $nonBMP = $this->get_ushort($os2_offset + 46);
3959 $nonBMP = ($nonBMP & ~(1 << 9)); // Unset Bit 57 (indicates non-BMP) - for interactive forms
3960
3961 $os2 = $this->get_table('OS/2');
3962 if (_RECALC_PROFILE) {
3963 $os2 = $this->_set_ushort($os2, 62, $fsSelection);
3964 $os2 = $this->_set_ushort($os2, 66, $fsLastCharIndex);
3965 $os2 = $this->_set_ushort($os2, 42, 0x0000); // ulCharRange (ulUnicodeRange) bits 24-31 | 16-23
3966 $os2 = $this->_set_ushort($os2, 44, 0x0000); // ulCharRange (Unicode ranges) bits 8-15 | 0-7
3967 $os2 = $this->_set_ushort($os2, 46, $nonBMP); // ulCharRange (Unicode ranges) bits 56-63 | 48-55
3968 $os2 = $this->_set_ushort($os2, 48, 0x0000); // ulCharRange (Unicode ranges) bits 40-47 | 32-39
3969 $os2 = $this->_set_ushort($os2, 50, 0x0000); // ulCharRange (Unicode ranges) bits 88-95 | 80-87
3970 $os2 = $this->_set_ushort($os2, 52, 0x0000); // ulCharRange (Unicode ranges) bits 72-79 | 64-71
3971 $os2 = $this->_set_ushort($os2, 54, 0x0000); // ulCharRange (Unicode ranges) bits 120-127 | 112-119
3972 $os2 = $this->_set_ushort($os2, 56, 0x0000); // ulCharRange (Unicode ranges) bits 104-111 | 96-103
3973 }
3974 $os2 = $this->_set_ushort($os2, 46, $nonBMP); // Unset Bit 57 (indicates non-BMP) - for interactive forms
3975
3976 $this->add('OS/2', $os2);
3977 }
3978
3979 fclose($this->fh);
3980 // Put the TTF file together
3981 $stm = '';
3982 $this->endTTFile($stm);
3983 //file_put_contents('testfont.ttf', $stm); exit;
3984 return $stm;
3985 }
3986
3987 //================================================================================
3988 // Also does SMP
3989 function makeSubsetSIP($file, &$subset, $TTCfontID = 0, $debug = false, $useOTL = 0)
3990 { // mPDF 5.7.1
3991 $this->fh = fopen($file, 'rb');
3992
3993 if (!$this->fh) {
3994 throw new MpdfException('Can\'t open file ' . $file);
3995 }
3996
3997 $this->filename = $file;
3998 $this->_pos = 0;
3999 $this->useOTL = $useOTL; // mPDF 5.7.1
4000 $this->charWidths = '';
4001 $this->glyphPos = array();
4002 $this->charToGlyph = array();
4003 $this->tables = array();
4004 $this->otables = array();
4005 $this->ascent = 0;
4006 $this->descent = 0;
4007 $this->strikeoutSize = 0;
4008 $this->strikeoutPosition = 0;
4009 $this->numTTCFonts = 0;
4010 $this->TTCFonts = array();
4011 $this->skip(4);
4012 if ($TTCfontID > 0) {
4013 $this->version = $version = $this->read_ulong(); // TTC Header version now
4014 if (!in_array($version, array(0x00010000, 0x00020000))) {
4015 throw new MpdfException("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
4016 }
4017 $this->numTTCFonts = $this->read_ulong();
4018 for ($i = 1; $i <= $this->numTTCFonts; $i++) {
4019 $this->TTCFonts[$i]['offset'] = $this->read_ulong();
4020 }
4021 $this->seek($this->TTCFonts[$TTCfontID]['offset']);
4022 $this->version = $version = $this->read_ulong(); // TTFont version again now
4023 }
4024 $this->readTableDirectory($debug);
4025
4026
4027 ///////////////////////////////////
4028 // head - Font header table
4029 ///////////////////////////////////
4030 $this->seek_table("head");
4031 $this->skip(50);
4032 $indexToLocFormat = $this->read_ushort();
4033 $glyphDataFormat = $this->read_ushort();
4034
4035 ///////////////////////////////////
4036 // hhea - Horizontal header table
4037 ///////////////////////////////////
4038 $this->seek_table("hhea");
4039 $this->skip(32);
4040 $metricDataFormat = $this->read_ushort();
4041 $orignHmetrics = $numberOfHMetrics = $this->read_ushort();
4042
4043 ///////////////////////////////////
4044 // maxp - Maximum profile table
4045 ///////////////////////////////////
4046 $this->seek_table("maxp");
4047 $this->skip(4);
4048 $numGlyphs = $this->read_ushort();
4049
4050
4051 ///////////////////////////////////
4052 // cmap - Character to glyph index mapping table
4053 ///////////////////////////////////
4054
4055 $cmap_offset = $this->seek_table("cmap");
4056 $this->skip(2);
4057 $cmapTableCount = $this->read_ushort();
4058 $unicode_cmap_offset = 0;
4059 for ($i = 0; $i < $cmapTableCount; $i++) {
4060 $platformID = $this->read_ushort();
4061 $encodingID = $this->read_ushort();
4062 $offset = $this->read_ulong();
4063 $save_pos = $this->_pos;
4064 if (($platformID == 3 && $encodingID == 10) || $platformID == 0) { // Microsoft, Unicode Format 12 table HKCS
4065 $format = $this->get_ushort($cmap_offset + $offset);
4066 if ($format == 12) {
4067 $unicode_cmap_offset = $cmap_offset + $offset;
4068 break;
4069 }
4070 }
4071 // mPDF 5.7.1
4072 if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
4073 $format = $this->get_ushort($cmap_offset + $offset);
4074 if ($format == 4) {
4075 $unicode_cmap_offset = $cmap_offset + $offset;
4076 }
4077 }
4078 $this->seek($save_pos);
4079 }
4080
4081 if (!$unicode_cmap_offset) {
4082 throw new MpdfException('Font does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
4083 }
4084
4085
4086 // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
4087 if ($format == 12) {
4088 $this->maxUniChar = 0;
4089 $this->seek($unicode_cmap_offset + 4);
4090 $length = $this->read_ulong();
4091 $limit = $unicode_cmap_offset + $length;
4092 $this->skip(4);
4093
4094 $nGroups = $this->read_ulong();
4095
4096 $glyphToChar = array();
4097 $charToGlyph = array();
4098 for ($i = 0; $i < $nGroups; $i++) {
4099 $startCharCode = $this->read_ulong();
4100 $endCharCode = $this->read_ulong();
4101 $startGlyphCode = $this->read_ulong();
4102 $offset = 0;
4103 for ($unichar = $startCharCode; $unichar <= $endCharCode; $unichar++) {
4104 $glyph = $startGlyphCode + $offset;
4105 $offset++;
4106 // ZZZ98
4107 if ($unichar < 0x30000) {
4108 $charToGlyph[$unichar] = $glyph;
4109 $this->maxUniChar = max($unichar, $this->maxUniChar);
4110 $glyphToChar[$glyph][] = $unichar;
4111 }
4112 }
4113 }
4114 }
4115 // mPDF 5.7.1
4116 else {
4117 $glyphToChar = array();
4118 $charToGlyph = array();
4119 $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph);
4120 }
4121
4122 ///////////////////////////////////
4123 // mPDF 5.7.1
4124 // Map Unmapped glyphs - from $numGlyphs
4125 if ($useOTL) {
4126 $bctr = 0xE000;
4127 for ($gid = 1; $gid < $numGlyphs; $gid++) {
4128 if (!isset($glyphToChar[$gid])) {
4129 while (isset($charToGlyph[$bctr])) {
4130 $bctr++;
4131 } // Avoid overwriting a glyph already mapped in PUA
4132 // ZZZ98
4133 if ($bctr > 0xF8FF && $bctr < 0x2CEB0) {
4134 $bctr = 0x2CEB0;
4135 while (isset($charToGlyph[$bctr])) {
4136 $bctr++;
4137 }
4138 }
4139 $glyphToChar[$gid][] = $bctr;
4140 $charToGlyph[$bctr] = $gid;
4141 $this->maxUniChar = max($bctr, $this->maxUniChar);
4142 $bctr++;
4143 }
4144 }
4145 }
4146 ///////////////////////////////////
4147 ///////////////////////////////////
4148 // hmtx - Horizontal metrics table
4149 ///////////////////////////////////
4150 $scale = 1; // not used here
4151 $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
4152
4153 ///////////////////////////////////
4154 // loca - Index to location
4155 ///////////////////////////////////
4156 $this->getLOCA($indexToLocFormat, $numGlyphs);
4157
4158 ///////////////////////////////////////////////////////////////////
4159
4160 $glyphMap = array(0 => 0);
4161 $glyphSet = array(0 => 0);
4162 $codeToGlyph = array();
4163 // Set a substitute if ASCII characters do not have glyphs
4164 if (isset($charToGlyph[0x3F])) {
4165 $subs = $charToGlyph[0x3F];
4166 } // Question mark
4167 else {
4168 $subs = $charToGlyph[32];
4169 }
4170 foreach ($subset AS $code) {
4171 if (isset($charToGlyph[$code]))
4172 $originalGlyphIdx = $charToGlyph[$code];
4173 else if ($code < 128) {
4174 $originalGlyphIdx = $subs;
4175 } else {
4176 $originalGlyphIdx = 0;
4177 }
4178 if (!isset($glyphSet[$originalGlyphIdx])) {
4179 $glyphSet[$originalGlyphIdx] = count($glyphMap);
4180 $glyphMap[] = $originalGlyphIdx;
4181 }
4182 $codeToGlyph[$code] = $glyphSet[$originalGlyphIdx];
4183 }
4184
4185 list($start, $dummy) = $this->get_table_pos('glyf');
4186
4187 $n = 0;
4188 while ($n < count($glyphMap)) {
4189 $originalGlyphIdx = $glyphMap[$n];
4190 $glyphPos = $this->glyphPos[$originalGlyphIdx];
4191 $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
4192 $n += 1;
4193 if (!$glyphLen)
4194 continue;
4195 $this->seek($start + $glyphPos);
4196 $numberOfContours = $this->read_short();
4197 if ($numberOfContours < 0) {
4198 $this->skip(8);
4199 $flags = GF_MORE;
4200 while ($flags & GF_MORE) {
4201 $flags = $this->read_ushort();
4202 $glyphIdx = $this->read_ushort();
4203 if (!isset($glyphSet[$glyphIdx])) {
4204 $glyphSet[$glyphIdx] = count($glyphMap);
4205 $glyphMap[] = $glyphIdx;
4206 }
4207 if ($flags & GF_WORDS)
4208 $this->skip(4);
4209 else
4210 $this->skip(2);
4211 if ($flags & GF_SCALE)
4212 $this->skip(2);
4213 else if ($flags & GF_XYSCALE)
4214 $this->skip(4);
4215 else if ($flags & GF_TWOBYTWO)
4216 $this->skip(8);
4217 }
4218 }
4219 }
4220
4221 $numGlyphs = $n = count($glyphMap);
4222 $numberOfHMetrics = $n;
4223
4224 ///////////////////////////////////
4225 // name
4226 ///////////////////////////////////
4227 // MS spec says that "Platform and encoding ID's in the name table should be consistent with those in the cmap table.
4228 // If they are not, the font will not load in Windows"
4229 // Doesn't seem to be a problem?
4230 ///////////////////////////////////
4231 // Needs to have a name entry in 3,0 (e.g. symbol) - original font will be 3,1 (i.e. Unicode)
4232 $name = $this->get_table('name');
4233 $name_offset = $this->seek_table("name");
4234 $format = $this->read_ushort();
4235 $numRecords = $this->read_ushort();
4236 $string_data_offset = $name_offset + $this->read_ushort();
4237 for ($i = 0; $i < $numRecords; $i++) {
4238 $platformId = $this->read_ushort();
4239 $encodingId = $this->read_ushort();
4240 if ($platformId == 3 && $encodingId == 1) {
4241 $pos = 6 + ($i * 12) + 2;
4242 $name = $this->_set_ushort($name, $pos, 0x00); // Change encoding to 3,0 rather than 3,1
4243 }
4244 $this->skip(8);
4245 }
4246 $this->add('name', $name);
4247
4248 ///////////////////////////////////
4249 // OS/2
4250 ///////////////////////////////////
4251 if (isset($this->tables['OS/2'])) {
4252 $os2 = $this->get_table('OS/2');
4253 $os2 = $this->_set_ushort($os2, 42, 0x00); // ulCharRange (Unicode ranges)
4254 $os2 = $this->_set_ushort($os2, 44, 0x00); // ulCharRange (Unicode ranges)
4255 $os2 = $this->_set_ushort($os2, 46, 0x00); // ulCharRange (Unicode ranges)
4256 $os2 = $this->_set_ushort($os2, 48, 0x00); // ulCharRange (Unicode ranges)
4257
4258 $os2 = $this->_set_ushort($os2, 50, 0x00); // ulCharRange (Unicode ranges)
4259 $os2 = $this->_set_ushort($os2, 52, 0x00); // ulCharRange (Unicode ranges)
4260 $os2 = $this->_set_ushort($os2, 54, 0x00); // ulCharRange (Unicode ranges)
4261 $os2 = $this->_set_ushort($os2, 56, 0x00); // ulCharRange (Unicode ranges)
4262 // Set Symbol character only in ulCodePageRange
4263 $os2 = $this->_set_ushort($os2, 78, 0x8000); // ulCodePageRange = Bit #31 Symbol **** 78 = Bit 16-31
4264 $os2 = $this->_set_ushort($os2, 80, 0x0000); // ulCodePageRange = Bit #31 Symbol **** 80 = Bit 0-15
4265 $os2 = $this->_set_ushort($os2, 82, 0x0000); // ulCodePageRange = Bit #32- Symbol **** 82 = Bits 48-63
4266 $os2 = $this->_set_ushort($os2, 84, 0x0000); // ulCodePageRange = Bit #32- Symbol **** 84 = Bits 32-47
4267
4268 $os2 = $this->_set_ushort($os2, 64, 0x01); // FirstCharIndex
4269 $os2 = $this->_set_ushort($os2, 66, count($subset)); // LastCharIndex
4270 // Set PANOSE first bit to 5 for Symbol
4271 $os2 = $this->splice($os2, 32, chr(5) . chr(0) . chr(1) . chr(0) . chr(1) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0));
4272 $this->add('OS/2', $os2);
4273 }
4274
4275
4276 ///////////////////////////////////
4277 //tables copied from the original
4278 ///////////////////////////////////
4279 $tags = array('cvt ', 'fpgm', 'prep', 'gasp');
4280 foreach ($tags AS $tag) { // 1.02
4281 if (isset($this->tables[$tag])) {
4282 $this->add($tag, $this->get_table($tag));
4283 }
4284 }
4285
4286 ///////////////////////////////////
4287 // post - PostScript
4288 ///////////////////////////////////
4289 if (isset($this->tables['post'])) {
4290 $opost = $this->get_table('post');
4291 $post = "\x00\x03\x00\x00" . substr($opost, 4, 12) . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
4292 }
4293 $this->add('post', $post);
4294
4295 ///////////////////////////////////
4296 // hhea - Horizontal Header
4297 ///////////////////////////////////
4298 $hhea = $this->get_table('hhea');
4299 $hhea = $this->_set_ushort($hhea, 34, $numberOfHMetrics);
4300 $this->add('hhea', $hhea);
4301
4302 ///////////////////////////////////
4303 // maxp - Maximum Profile
4304 ///////////////////////////////////
4305 $maxp = $this->get_table('maxp');
4306 $maxp = $this->_set_ushort($maxp, 4, $numGlyphs);
4307 $this->add('maxp', $maxp);
4308
4309
4310 ///////////////////////////////////
4311 // CMap table Formats [1,0,]6 and [3,0,]4
4312 ///////////////////////////////////
4313 ///////////////////////////////////
4314 // Sort CID2GID map into segments of contiguous codes
4315 ///////////////////////////////////
4316 $rangeid = 0;
4317 $range = array();
4318 $prevcid = -2;
4319 $prevglidx = -1;
4320 // for each character
4321 foreach ($subset as $cid => $code) {
4322 $glidx = $codeToGlyph[$code];
4323 if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) {
4324 $range[$rangeid][] = $glidx;
4325 } else {
4326 // new range
4327 $rangeid = $cid;
4328 $range[$rangeid] = array();
4329 $range[$rangeid][] = $glidx;
4330 }
4331 $prevcid = $cid;
4332 $prevglidx = $glidx;
4333 }
4334 // cmap - Character to glyph mapping
4335 $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF
4336 $searchRange = 1;
4337 $entrySelector = 0;
4338 while ($searchRange * 2 <= $segCount) {
4339 $searchRange = $searchRange * 2;
4340 $entrySelector = $entrySelector + 1;
4341 }
4342 $searchRange = $searchRange * 2;
4343 $rangeShift = $segCount * 2 - $searchRange;
4344 $length = 16 + (8 * $segCount ) + ($numGlyphs + 1);
4345 $cmap = array(
4346 4, $length, 0, // Format 4 Mapping subtable: format, length, language
4347 $segCount * 2,
4348 $searchRange,
4349 $entrySelector,
4350 $rangeShift);
4351
4352 // endCode(s)
4353 foreach ($range AS $start => $subrange) {
4354 $endCode = $start + (count($subrange) - 1);
4355 $cmap[] = $endCode; // endCode(s)
4356 }
4357 $cmap[] = 0xFFFF; // endCode of last Segment
4358 $cmap[] = 0; // reservedPad
4359 // startCode(s)
4360 foreach ($range AS $start => $subrange) {
4361 $cmap[] = $start; // startCode(s)
4362 }
4363 $cmap[] = 0xFFFF; // startCode of last Segment
4364 // idDelta(s)
4365 foreach ($range AS $start => $subrange) {
4366 $idDelta = -($start - $subrange[0]);
4367 $n += count($subrange);
4368 $cmap[] = $idDelta; // idDelta(s)
4369 }
4370 $cmap[] = 1; // idDelta of last Segment
4371 // idRangeOffset(s)
4372 foreach ($range AS $subrange) {
4373 $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
4374 }
4375 $cmap[] = 0; // idRangeOffset of last Segment
4376 foreach ($range AS $subrange) {
4377 foreach ($subrange AS $glidx) {
4378 $cmap[] = $glidx;
4379 }
4380 }
4381 $cmap[] = 0; // Mapping for last character
4382 $cmapstr4 = '';
4383 foreach ($cmap AS $cm) {
4384 $cmapstr4 .= pack("n", $cm);
4385 }
4386
4387 ///////////////////////////////////
4388 // cmap - Character to glyph mapping
4389 ///////////////////////////////////
4390 $entryCount = count($subset);
4391 $length = 10 + $entryCount * 2;
4392
4393 $off = 20 + $length;
4394 $hoff = $off >> 16;
4395 $loff = $off & 0xFFFF;
4396
4397 $cmap = array(0, 2, // Index : version, number of subtables
4398 1, 0, // Subtable : platform, encoding
4399 0, 20, // offset (hi,lo)
4400 3, 0, // Subtable : platform, encoding // See note above for 'name'
4401 $hoff, $loff, // offset (hi,lo)
4402 6, $length, // Format 6 Mapping table: format, length
4403 0, 1, // language, First char code
4404 $entryCount
4405 );
4406 $cmapstr = '';
4407 foreach ($subset AS $code) {
4408 $cmap[] = $codeToGlyph[$code];
4409 }
4410 foreach ($cmap AS $cm) {
4411 $cmapstr .= pack("n", $cm);
4412 }
4413 $cmapstr .= $cmapstr4;
4414 $this->add('cmap', $cmapstr);
4415
4416 ///////////////////////////////////
4417 // hmtx - Horizontal Metrics
4418 ///////////////////////////////////
4419 $hmtxstr = '';
4420 for ($n = 0; $n < $numGlyphs; $n++) {
4421 $originalGlyphIdx = $glyphMap[$n];
4422 $hm = $this->getHMetric($orignHmetrics, $originalGlyphIdx);
4423 $hmtxstr .= $hm;
4424 }
4425 $this->add('hmtx', $hmtxstr);
4426
4427 ///////////////////////////////////
4428 // glyf - Glyph data
4429 ///////////////////////////////////
4430 list($glyfOffset, $glyfLength) = $this->get_table_pos('glyf');
4431 if ($glyfLength < $this->maxStrLenRead) {
4432 $glyphData = $this->get_table('glyf');
4433 }
4434
4435 $offsets = array();
4436 $glyf = '';
4437 $pos = 0;
4438 for ($n = 0; $n < $numGlyphs; $n++) {
4439 $offsets[] = $pos;
4440 $originalGlyphIdx = $glyphMap[$n];
4441 $glyphPos = $this->glyphPos[$originalGlyphIdx];
4442 $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
4443 if ($glyfLength < $this->maxStrLenRead) {
4444 $data = substr($glyphData, $glyphPos, $glyphLen);
4445 } else {
4446 if ($glyphLen > 0)
4447 $data = $this->get_chunk($glyfOffset + $glyphPos, $glyphLen);
4448 else
4449 $data = '';
4450 }
4451 if ($glyphLen > 0)
4452 $up = unpack("n", substr($data, 0, 2));
4453 if ($glyphLen > 2 && ($up[1] & (1 << 15))) {
4454 $pos_in_glyph = 10;
4455 $flags = GF_MORE;
4456 while ($flags & GF_MORE) {
4457 $up = unpack("n", substr($data, $pos_in_glyph, 2));
4458 $flags = $up[1];
4459 $up = unpack("n", substr($data, $pos_in_glyph + 2, 2));
4460 $glyphIdx = $up[1];
4461 $data = $this->_set_ushort($data, $pos_in_glyph + 2, $glyphSet[$glyphIdx]);
4462 $pos_in_glyph += 4;
4463 if ($flags & GF_WORDS) {
4464 $pos_in_glyph += 4;
4465 } else {
4466 $pos_in_glyph += 2;
4467 }
4468 if ($flags & GF_SCALE) {
4469 $pos_in_glyph += 2;
4470 } else if ($flags & GF_XYSCALE) {
4471 $pos_in_glyph += 4;
4472 } else if ($flags & GF_TWOBYTWO) {
4473 $pos_in_glyph += 8;
4474 }
4475 }
4476 }
4477 $glyf .= $data;
4478 $pos += $glyphLen;
4479 if ($pos % 4 != 0) {
4480 $padding = 4 - ($pos % 4);
4481 $glyf .= str_repeat("\0", $padding);
4482 $pos += $padding;
4483 }
4484 }
4485 $offsets[] = $pos;
4486 $this->add('glyf', $glyf);
4487
4488 ///////////////////////////////////
4489 // loca - Index to location
4490 ///////////////////////////////////
4491 $locastr = '';
4492 if ((($pos + 1) >> 1) > 0xFFFF) {
4493 $indexToLocFormat = 1; // long format
4494 foreach ($offsets AS $offset) {
4495 $locastr .= pack("N", $offset);
4496 }
4497 } else {
4498 $indexToLocFormat = 0; // short format
4499 foreach ($offsets AS $offset) {
4500 $locastr .= pack("n", ($offset / 2));
4501 }
4502 }
4503 $this->add('loca', $locastr);
4504
4505 ///////////////////////////////////
4506 // head - Font header
4507 ///////////////////////////////////
4508 $head = $this->get_table('head');
4509 $head = $this->_set_ushort($head, 50, $indexToLocFormat);
4510 $this->add('head', $head);
4511
4512 fclose($this->fh);
4513
4514 // Put the TTF file together
4515 $stm = '';
4516 $this->endTTFile($stm);
4517 //file_put_contents('testfont.ttf', $stm); exit;
4518 return $stm;
4519 }
4520
4521 //////////////////////////////////////////////////////////////////////////////////
4522 // Recursively get composite glyph data
4523 function getGlyphData($originalGlyphIdx, &$maxdepth, &$depth, &$points, &$contours)
4524 {
4525 $depth++;
4526 $maxdepth = max($maxdepth, $depth);
4527 if (count($this->glyphdata[$originalGlyphIdx]['compGlyphs'])) {
4528 foreach ($this->glyphdata[$originalGlyphIdx]['compGlyphs'] AS $glyphIdx) {
4529 $this->getGlyphData($glyphIdx, $maxdepth, $depth, $points, $contours);
4530 }
4531 } else if (($this->glyphdata[$originalGlyphIdx]['nContours'] > 0) && $depth > 0) { // simple
4532 $contours += $this->glyphdata[$originalGlyphIdx]['nContours'];
4533 $points += $this->glyphdata[$originalGlyphIdx]['nPoints'];
4534 }
4535 $depth--;
4536 }
4537
4538 //////////////////////////////////////////////////////////////////////////////////
4539 // Recursively get composite glyphs
4540 function getGlyphs($originalGlyphIdx, &$start, &$glyphSet, &$subsetglyphs)
4541 {
4542 $glyphPos = $this->glyphPos[$originalGlyphIdx];
4543 $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
4544 if (!$glyphLen) {
4545 return;
4546 }
4547 $this->seek($start + $glyphPos);
4548 $numberOfContours = $this->read_short();
4549 if ($numberOfContours < 0) {
4550 $this->skip(8);
4551 $flags = GF_MORE;
4552 while ($flags & GF_MORE) {
4553 $flags = $this->read_ushort();
4554 $glyphIdx = $this->read_ushort();
4555 if (!isset($glyphSet[$glyphIdx])) {
4556 $glyphSet[$glyphIdx] = count($subsetglyphs); // old glyphID to new glyphID
4557 $subsetglyphs[$glyphIdx] = true;
4558 }
4559 $savepos = ftell($this->fh);
4560 $this->getGlyphs($glyphIdx, $start, $glyphSet, $subsetglyphs);
4561 $this->seek($savepos);
4562 if ($flags & GF_WORDS)
4563 $this->skip(4);
4564 else
4565 $this->skip(2);
4566 if ($flags & GF_SCALE)
4567 $this->skip(2);
4568 else if ($flags & GF_XYSCALE)
4569 $this->skip(4);
4570 else if ($flags & GF_TWOBYTWO)
4571 $this->skip(8);
4572 }
4573 }
4574 }
4575
4576 //////////////////////////////////////////////////////////////////////////////////
4577
4578 function getHMTX($numberOfHMetrics, $numGlyphs, &$glyphToChar, $scale)
4579 {
4580 $start = $this->seek_table("hmtx");
4581 $aw = 0;
4582 $this->charWidths = str_pad('', 256 * 256 * 2, "\x00");
4583 if ($this->maxUniChar > 65536) {
4584 $this->charWidths .= str_pad('', 256 * 256 * 2, "\x00");
4585 } // Plane 1 SMP
4586 if ($this->maxUniChar > 131072) {
4587 $this->charWidths .= str_pad('', 256 * 256 * 2, "\x00");
4588 } // Plane 2 SMP
4589 $nCharWidths = 0;
4590 if (($numberOfHMetrics * 4) < $this->maxStrLenRead) {
4591 $data = $this->get_chunk($start, ($numberOfHMetrics * 4));
4592 $arr = unpack("n*", $data);
4593 } else {
4594 $this->seek($start);
4595 }
4596 for ($glyph = 0; $glyph < $numberOfHMetrics; $glyph++) {
4597 if (($numberOfHMetrics * 4) < $this->maxStrLenRead) {
4598 $aw = $arr[($glyph * 2) + 1];
4599 } else {
4600 $aw = $this->read_ushort();
4601 $lsb = $this->read_ushort();
4602 }
4603 if (isset($glyphToChar[$glyph]) || $glyph == 0) {
4604 if ($aw >= (1 << 15)) {
4605 $aw = 0;
4606 } // 1.03 Some (arabic) fonts have -ve values for width
4607 // although should be unsigned value - comes out as e.g. 65108 (intended -50)
4608 if ($glyph == 0) {
4609 $this->defaultWidth = $scale * $aw;
4610 continue;
4611 }
4612 foreach ($glyphToChar[$glyph] AS $char) {
4613 if ($char != 0 && $char != 65535) {
4614 $w = intval(round($scale * $aw));
4615 if ($w == 0) {
4616 $w = 65535;
4617 }
4618 if ($char < 196608) {
4619 $this->charWidths[$char * 2] = chr($w >> 8);
4620 $this->charWidths[$char * 2 + 1] = chr($w & 0xFF);
4621 $nCharWidths++;
4622 }
4623 }
4624 }
4625 }
4626 }
4627
4628 $data = $this->get_chunk(($start + $numberOfHMetrics * 4), ($numGlyphs * 2));
4629 $arr = unpack("n*", $data);
4630 $diff = $numGlyphs - $numberOfHMetrics;
4631 $w = intval(round($scale * $aw));
4632 if ($w == 0) {
4633 $w = 65535;
4634 }
4635 for ($pos = 0; $pos < $diff; $pos++) {
4636 $glyph = $pos + $numberOfHMetrics;
4637 if (isset($glyphToChar[$glyph])) {
4638 foreach ($glyphToChar[$glyph] AS $char) {
4639 if ($char != 0 && $char != 65535) {
4640 if ($char < 196608) {
4641 $this->charWidths[$char * 2] = chr($w >> 8);
4642 $this->charWidths[$char * 2 + 1] = chr($w & 0xFF);
4643 $nCharWidths++;
4644 }
4645 }
4646 }
4647 }
4648 }
4649 // NB 65535 is a set width of 0
4650 // First bytes define number of chars in font
4651 $this->charWidths[0] = chr($nCharWidths >> 8);
4652 $this->charWidths[1] = chr($nCharWidths & 0xFF);
4653 }
4654
4655 function getHMetric($numberOfHMetrics, $gid)
4656 {
4657 $start = $this->seek_table("hmtx");
4658 if ($gid < $numberOfHMetrics) {
4659 $this->seek($start + ($gid * 4));
4660 $hm = fread($this->fh, 4);
4661 } else {
4662 $this->seek($start + (($numberOfHMetrics - 1) * 4));
4663 $hm = fread($this->fh, 2);
4664 $this->seek($start + ($numberOfHMetrics * 2) + ($gid * 2));
4665 $hm .= fread($this->fh, 2);
4666 }
4667 return $hm;
4668 }
4669
4670 function getLOCA($indexToLocFormat, $numGlyphs)
4671 {
4672 $start = $this->seek_table('loca');
4673 $this->glyphPos = array();
4674 if ($indexToLocFormat == 0) {
4675 $data = $this->get_chunk($start, ($numGlyphs * 2) + 2);
4676 $arr = unpack("n*", $data);
4677 for ($n = 0; $n <= $numGlyphs; $n++) {
4678 $this->glyphPos[] = ($arr[$n + 1] * 2);
4679 }
4680 } else if ($indexToLocFormat == 1) {
4681 $data = $this->get_chunk($start, ($numGlyphs * 4) + 4);
4682 $arr = unpack("N*", $data);
4683 for ($n = 0; $n <= $numGlyphs; $n++) {
4684 $this->glyphPos[] = ($arr[$n + 1]);
4685 }
4686 } else {
4687 throw new MpdfException('Unknown location table format ' . $indexToLocFormat);
4688 }
4689 }
4690
4691 // CMAP Format 4
4692 function getCMAP4($unicode_cmap_offset, &$glyphToChar, &$charToGlyph)
4693 {
4694 $this->maxUniChar = 0;
4695 $this->seek($unicode_cmap_offset + 2);
4696 $length = $this->read_ushort();
4697 $limit = $unicode_cmap_offset + $length;
4698 $this->skip(2);
4699
4700 $segCount = $this->read_ushort() / 2;
4701 $this->skip(6);
4702 $endCount = array();
4703 for ($i = 0; $i < $segCount; $i++) {
4704 $endCount[] = $this->read_ushort();
4705 }
4706 $this->skip(2);
4707 $startCount = array();
4708 for ($i = 0; $i < $segCount; $i++) {
4709 $startCount[] = $this->read_ushort();
4710 }
4711 $idDelta = array();
4712 for ($i = 0; $i < $segCount; $i++) {
4713 $idDelta[] = $this->read_short();
4714 } // ???? was unsigned short
4715 $idRangeOffset_start = $this->_pos;
4716 $idRangeOffset = array();
4717 for ($i = 0; $i < $segCount; $i++) {
4718 $idRangeOffset[] = $this->read_ushort();
4719 }
4720
4721 for ($n = 0; $n < $segCount; $n++) {
4722 $endpoint = ($endCount[$n] + 1);
4723 for ($unichar = $startCount[$n]; $unichar < $endpoint; $unichar++) {
4724 if ($idRangeOffset[$n] == 0)
4725 $glyph = ($unichar + $idDelta[$n]) & 0xFFFF;
4726 else {
4727 $offset = ($unichar - $startCount[$n]) * 2 + $idRangeOffset[$n];
4728 $offset = $idRangeOffset_start + 2 * $n + $offset;
4729 if ($offset >= $limit)
4730 $glyph = 0;
4731 else {
4732 $glyph = $this->get_ushort($offset);
4733 if ($glyph != 0)
4734 $glyph = ($glyph + $idDelta[$n]) & 0xFFFF;
4735 }
4736 }
4737 $charToGlyph[$unichar] = $glyph;
4738 if ($unichar < 196608) {
4739 $this->maxUniChar = max($unichar, $this->maxUniChar);
4740 }
4741 $glyphToChar[$glyph][] = $unichar;
4742 }
4743 }
4744 }
4745
4746 // Put the TTF file together
4747 function endTTFile(&$stm)
4748 {
4749 $stm = '';
4750 $numTables = count($this->otables);
4751 $searchRange = 1;
4752 $entrySelector = 0;
4753 while ($searchRange * 2 <= $numTables) {
4754 $searchRange = $searchRange * 2;
4755 $entrySelector = $entrySelector + 1;
4756 }
4757 $searchRange = $searchRange * 16;
4758 $rangeShift = $numTables * 16 - $searchRange;
4759
4760 // Header
4761 if (_TTF_MAC_HEADER) {
4762 $stm .= (pack("Nnnnn", 0x74727565, $numTables, $searchRange, $entrySelector, $rangeShift)); // Mac
4763 } else {
4764 $stm .= (pack("Nnnnn", 0x00010000, $numTables, $searchRange, $entrySelector, $rangeShift)); // Windows
4765 }
4766
4767 // Table directory
4768 $tables = $this->otables;
4769 ksort($tables);
4770 $offset = 12 + $numTables * 16;
4771 foreach ($tables AS $tag => $data) {
4772 if ($tag == 'head') {
4773 $head_start = $offset;
4774 }
4775 $stm .= $tag;
4776 $checksum = $this->calcChecksum($data);
4777 $stm .= pack("nn", $checksum[0], $checksum[1]);
4778 $stm .= pack("NN", $offset, strlen($data));
4779 $paddedLength = (strlen($data) + 3) & ~3;
4780 $offset = $offset + $paddedLength;
4781 }
4782
4783 // Table data
4784 foreach ($tables AS $tag => $data) {
4785 $data .= "\0\0\0";
4786 $stm .= substr($data, 0, (strlen($data) & ~3));
4787 }
4788
4789 $checksum = $this->calcChecksum($stm);
4790 $checksum = $this->sub32(array(0xB1B0, 0xAFBA), $checksum);
4791 $chk = pack("nn", $checksum[0], $checksum[1]);
4792 $stm = $this->splice($stm, ($head_start + 8), $chk);
4793 return $stm;
4794 }
4795
4796 function repackageTTF($file, $TTCfontID = 0, $debug = false, $useOTL = false)
4797 { // mPDF 5.7.1
4798 // (Does not called for subsets)
4799 $this->useOTL = $useOTL; // mPDF 5.7.1
4800 $this->filename = $file;
4801 $this->fh = fopen($file, 'rb');
4802
4803 if (!$this->fh) {
4804 throw new MpdfException('Can\'t open file ' . $file);
4805 }
4806
4807 $this->_pos = 0;
4808 $this->charWidths = '';
4809 $this->glyphPos = array();
4810 $this->charToGlyph = array();
4811 $this->tables = array();
4812 $this->otables = array();
4813 $this->ascent = 0;
4814 $this->descent = 0;
4815 $this->strikeoutSize = 0;
4816 $this->strikeoutPosition = 0;
4817 $this->numTTCFonts = 0;
4818 $this->TTCFonts = array();
4819 $this->skip(4);
4820 $this->maxUni = 0;
4821 if ($TTCfontID > 0) {
4822 $this->version = $version = $this->read_ulong(); // TTC Header version now
4823 if (!in_array($version, array(0x00010000, 0x00020000))) {
4824 throw new MpdfException("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
4825 }
4826 $this->numTTCFonts = $this->read_ulong();
4827 for ($i = 1; $i <= $this->numTTCFonts; $i++) {
4828 $this->TTCFonts[$i]['offset'] = $this->read_ulong();
4829 }
4830 $this->seek($this->TTCFonts[$TTCfontID]['offset']);
4831 $this->version = $version = $this->read_ulong(); // TTFont version again now
4832 }
4833 $this->readTableDirectory($debug);
4834 $tags = array('OS/2', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'name', 'post', 'cvt ', 'fpgm', 'gasp', 'prep');
4835
4836 foreach ($tags AS $tag) {
4837 if (isset($this->tables[$tag])) {
4838 $this->add($tag, $this->get_table($tag));
4839 }
4840 }
4841
4842 // mPDF 5.7.1
4843 if ($useOTL) {
4844 ///////////////////////////////////
4845 // maxp - Maximum profile table
4846 ///////////////////////////////////
4847 $this->seek_table("maxp");
4848 $this->skip(4);
4849 $numGlyphs = $this->read_ushort();
4850
4851
4852 ///////////////////////////////////
4853 // cmap - Character to glyph index mapping table
4854 ///////////////////////////////////
4855 $cmap_offset = $this->seek_table("cmap");
4856 $this->skip(2);
4857 $cmapTableCount = $this->read_ushort();
4858 $unicode_cmap_offset = 0;
4859 for ($i = 0; $i < $cmapTableCount; $i++) {
4860 $platformID = $this->read_ushort();
4861 $encodingID = $this->read_ushort();
4862 $offset = $this->read_ulong();
4863 $save_pos = $this->_pos;
4864 if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
4865 $format = $this->get_ushort($cmap_offset + $offset);
4866 if ($format == 4) {
4867 $unicode_cmap_offset = $cmap_offset + $offset;
4868 break;
4869 }
4870 }
4871 $this->seek($save_pos);
4872 }
4873
4874 if (!$unicode_cmap_offset) {
4875 throw new MpdfException('Font (' . $this->filename . ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
4876 }
4877
4878 $glyphToChar = array();
4879 $charToGlyph = array();
4880 $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph);
4881
4882 ///////////////////////////////////
4883 // Map Unmapped glyphs - from $numGlyphs
4884 $bctr = 0xE000;
4885 for ($gid = 1; $gid < $numGlyphs; $gid++) {
4886 if (!isset($glyphToChar[$gid])) {
4887 while (isset($charToGlyph[$bctr])) {
4888 $bctr++;
4889 } // Avoid overwriting a glyph already mapped in PUA (6,400)
4890 if ($bctr > 0xF8FF) {
4891 throw new MpdfException("Problem. Trying to repackage TF file; not enough space for unmapped glyphs");
4892 }
4893 $glyphToChar[$gid][] = $bctr;
4894 $charToGlyph[$bctr] = $gid;
4895 $bctr++;
4896 }
4897 }
4898 ///////////////////////////////////
4899 ///////////////////////////////////
4900 // Sort CID2GID map into segments of contiguous codes
4901 ///////////////////////////////////
4902 unset($charToGlyph[65535]);
4903 unset($charToGlyph[0]);
4904 ksort($charToGlyph);
4905 $rangeid = 0;
4906 $range = array();
4907 $prevcid = -2;
4908 $prevglidx = -1;
4909 // for each character
4910 foreach ($charToGlyph as $cid => $glidx) {
4911 if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) {
4912 $range[$rangeid][] = $glidx;
4913 } else {
4914 // new range
4915 $rangeid = $cid;
4916 $range[$rangeid] = array();
4917 $range[$rangeid][] = $glidx;
4918 }
4919 $prevcid = $cid;
4920 $prevglidx = $glidx;
4921 }
4922
4923
4924 ///////////////////////////////////
4925 // CMap table
4926 ///////////////////////////////////
4927 // cmap - Character to glyph mapping
4928 $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF
4929 $searchRange = 1;
4930 $entrySelector = 0;
4931 while ($searchRange * 2 <= $segCount) {
4932 $searchRange = $searchRange * 2;
4933 $entrySelector = $entrySelector + 1;
4934 }
4935 $searchRange = $searchRange * 2;
4936 $rangeShift = $segCount * 2 - $searchRange;
4937 $length = 16 + (8 * $segCount ) + ($numGlyphs + 1);
4938 $cmap = array(0, 3, // Index : version, number of encoding subtables
4939 0, 0, // Encoding Subtable : platform (UNI=0), encoding 0
4940 0, 28, // Encoding Subtable : offset (hi,lo)
4941 0, 3, // Encoding Subtable : platform (UNI=0), encoding 3
4942 0, 28, // Encoding Subtable : offset (hi,lo)
4943 3, 1, // Encoding Subtable : platform (MS=3), encoding 1
4944 0, 28, // Encoding Subtable : offset (hi,lo)
4945 4, $length, 0, // Format 4 Mapping subtable: format, length, language
4946 $segCount * 2,
4947 $searchRange,
4948 $entrySelector,
4949 $rangeShift);
4950
4951 // endCode(s)
4952 foreach ($range AS $start => $subrange) {
4953 $endCode = $start + (count($subrange) - 1);
4954 $cmap[] = $endCode; // endCode(s)
4955 }
4956 $cmap[] = 0xFFFF; // endCode of last Segment
4957 $cmap[] = 0; // reservedPad
4958 // startCode(s)
4959 foreach ($range AS $start => $subrange) {
4960 $cmap[] = $start; // startCode(s)
4961 }
4962 $cmap[] = 0xFFFF; // startCode of last Segment
4963 // idDelta(s)
4964 foreach ($range AS $start => $subrange) {
4965 $idDelta = -($start - $subrange[0]);
4966 //$n += count($subrange); // ?? Line not required
4967 $cmap[] = $idDelta; // idDelta(s)
4968 }
4969 $cmap[] = 1; // idDelta of last Segment
4970 // idRangeOffset(s)
4971 foreach ($range AS $subrange) {
4972 $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
4973 }
4974 $cmap[] = 0; // idRangeOffset of last Segment
4975 foreach ($range AS $subrange) {
4976 foreach ($subrange AS $glidx) {
4977 $cmap[] = $glidx;
4978 }
4979 }
4980 $cmap[] = 0; // Mapping for last character
4981 $cmapstr = '';
4982 foreach ($cmap AS $cm) {
4983 $cmapstr .= pack("n", $cm);
4984 }
4985 $this->add('cmap', $cmapstr);
4986 } else {
4987 $this->add('cmap', $this->get_table('cmap'));
4988 }
4989
4990
4991 fclose($this->fh);
4992 $stm = '';
4993 $this->endTTFile($stm);
4994 return $stm;
4995 }
4996
4997 }
4998