PluginProbe
PDF & Print by BestWebSoft – WordPress Posts and Pages PDF Generator Plugin / 2.4.2
PDF & Print by BestWebSoft – WordPress Posts and Pages PDF Generator Plugin v2.4.2
trunk 1.5 1.6 1.7 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 All 71 releases
← All changes | mpdf/classes/ttfontsuni.php +4997 -2373 1.52.4.2 View file →
@@ -1,2373 +1,4997 @@
1 -<?php
2 -
3 -/*******************************************************************************
4 -* TTFontFile class *
5 -* *
6 -* Version: 1.05 *
7 -* Date: 2011-07-21 *
8 -* Author: Ian Back <ianb@bpm1.com> *
9 -* License: LGPL *
10 -* Copyright (c) Ian Back, 2010 *
11 -* This class is based on The ReportLab Open Source PDF library *
12 -* written in Python - http://www.reportlab.com/software/opensource/ *
13 -* together with ideas from the OpenOffice source code and others. *
14 -* This header must be retained in any redistribution or *
15 -* modification of the file. *
16 -* *
17 -*******************************************************************************/
18 -
19 -// Define the value used in the "head" table of a created TTF file
20 -// 0x74727565 "true" for Mac
21 -// 0x00010000 for Windows
22 -// Either seems to work for a font embedded in a PDF file
23 -// when read by Adobe Reader on a Windows PC(!)
24 -if (!defined('_TTF_MAC_HEADER')) define("_TTF_MAC_HEADER", false);
25 -
26 -// Recalculate correct metadata/profiles when making subset fonts (not SIP/SMP)
27 -// e.g. xMin, xMax, maxNContours
28 -if (!defined('_RECALC_PROFILE')) define("_RECALC_PROFILE", false);
29 -
30 -// TrueType Font Glyph operators
31 -define("GF_WORDS",(1 << 0));
32 -define("GF_SCALE",(1 << 3));
33 -define("GF_MORE",(1 << 5));
34 -define("GF_XYSCALE",(1 << 6));
35 -define("GF_TWOBYTWO",(1 << 7));
36 -
37 -
38 -
39 -class TTFontFile {
40 -
41 -var $panose;
42 -var $maxUni;
43 -var $sFamilyClass;
44 -var $sFamilySubClass;
45 -var $sipset;
46 -var $smpset;
47 -var $_pos;
48 -var $numTables;
49 -var $searchRange;
50 -var $entrySelector;
51 -var $rangeShift;
52 -var $tables;
53 -var $otables;
54 -var $filename;
55 -var $fh;
56 -var $glyphPos;
57 -var $charToGlyph;
58 -var $ascent;
59 -var $descent;
60 -var $name;
61 -var $familyName;
62 -var $styleName;
63 -var $fullName;
64 -var $uniqueFontID;
65 -var $unitsPerEm;
66 -var $bbox;
67 -var $capHeight;
68 -var $stemV;
69 -var $italicAngle;
70 -var $flags;
71 -var $underlinePosition;
72 -var $underlineThickness;
73 -var $charWidths;
74 -var $defaultWidth;
75 -var $maxStrLenRead;
76 -var $numTTCFonts;
77 -var $TTCFonts;
78 -var $maxUniChar;
79 -var $kerninfo;
80 -
81 - function TTFontFile() {
82 - $this->maxStrLenRead = 200000; // Maximum size of glyf table to read in as string (otherwise reads each glyph from file)
83 - }
84 -
85 -
86 - function getMetrics($file, $TTCfontID=0, $debug=false, $BMPonly=false, $kerninfo=false) {
87 - $this->filename = $file;
88 - $this->fh = fopen($file,'rb') or die('Can\'t open file ' . $file);
89 - $this->_pos = 0;
90 - $this->charWidths = '';
91 - $this->glyphPos = array();
92 - $this->charToGlyph = array();
93 - $this->tables = array();
94 - $this->otables = array();
95 - $this->kerninfo = array();
96 - $this->ascent = 0;
97 - $this->descent = 0;
98 - $this->numTTCFonts = 0;
99 - $this->TTCFonts = array();
100 - $this->version = $version = $this->read_ulong();
101 - $this->panose = array();
102 - if ($version==0x4F54544F)
103 - die("Postscript outlines are not supported");
104 - if ($version==0x74746366 && !$TTCfontID)
105 - die("ERROR - You must define the TTCfontID for a TrueType Collection in config_fonts.php (". $file.")");
106 - if (!in_array($version, array(0x00010000,0x74727565)) && !$TTCfontID)
107 - die("Not a TrueType font: version=".$version);
108 - if ($TTCfontID > 0) {
109 - $this->version = $version = $this->read_ulong(); // TTC Header version now
110 - if (!in_array($version, array(0x00010000,0x00020000)))
111 - die("ERROR - Error parsing TrueType Collection: version=".$version." - " . $file);
112 - $this->numTTCFonts = $this->read_ulong();
113 - for ($i=1; $i<=$this->numTTCFonts; $i++) {
114 - $this->TTCFonts[$i]['offset'] = $this->read_ulong();
115 - }
116 - $this->seek($this->TTCFonts[$TTCfontID]['offset']);
117 - $this->version = $version = $this->read_ulong(); // TTFont version again now
118 - }
119 - $this->readTableDirectory($debug);
120 - $this->extractInfo($debug, $BMPonly, $kerninfo);
121 - fclose($this->fh);
122 - }
123 -
124 -
125 - function readTableDirectory($debug=false) {
126 - $this->numTables = $this->read_ushort();
127 - $this->searchRange = $this->read_ushort();
128 - $this->entrySelector = $this->read_ushort();
129 - $this->rangeShift = $this->read_ushort();
130 - $this->tables = array();
131 - for ($i=0;$i<$this->numTables;$i++) {
132 - $record = array();
133 - $record['tag'] = $this->read_tag();
134 - $record['checksum'] = array($this->read_ushort(),$this->read_ushort());
135 - $record['offset'] = $this->read_ulong();
136 - $record['length'] = $this->read_ulong();
137 - $this->tables[$record['tag']] = $record;
138 - }
139 - if ($debug) $this->checksumTables();
140 - }
141 -
142 - function checksumTables() {
143 - // Check the checksums for all tables
144 - foreach($this->tables AS $t) {
145 - if ($t['length'] > 0 && $t['length'] < $this->maxStrLenRead) { // 1.02
146 - $table = $this->get_chunk($t['offset'], $t['length']);
147 - $checksum = $this->calcChecksum($table);
148 - if ($t['tag'] == 'head') {
149 - $up = unpack('n*', substr($table,8,4));
150 - $adjustment[0] = $up[1];
151 - $adjustment[1] = $up[2];
152 - $checksum = $this->sub32($checksum, $adjustment);
153 - }
154 - $xchecksum = $t['checksum'];
155 - if ($xchecksum != $checksum)
156 - die(sprintf('TTF file "%s": invalid checksum %s table: %s (expected %s)', $this->filename,dechex($checksum[0]).dechex($checksum[1]),$t['tag'],dechex($xchecksum[0]).dechex($xchecksum[1])));
157 - }
158 - }
159 - }
160 -
161 - function sub32($x, $y) {
162 - $xlo = $x[1];
163 - $xhi = $x[0];
164 - $ylo = $y[1];
165 - $yhi = $y[0];
166 - if ($ylo > $xlo) { $xlo += 1 << 16; $yhi += 1; }
167 - $reslo = $xlo-$ylo;
168 - if ($yhi > $xhi) { $xhi += 1 << 16; }
169 - $reshi = $xhi-$yhi;
170 - $reshi = $reshi & 0xFFFF;
171 - return array($reshi, $reslo);
172 - }
173 -
174 - function calcChecksum($data) {
175 - if (strlen($data) % 4) { $data .= str_repeat("\0",(4-(strlen($data) % 4))); }
176 - $len = strlen($data);
177 - $hi=0x0000;
178 - $lo=0x0000;
179 - for($i=0;$i<$len;$i+=4) {
180 - $hi += (ord($data[$i])<<8) + ord($data[$i+1]);
181 - $lo += (ord($data[$i+2])<<8) + ord($data[$i+3]);
182 - $hi += ($lo >> 16) & 0xFFFF; // mPDF 5.3.07
183 - $lo = $lo & 0xFFFF;
184 - }
185 - return array($hi, $lo);
186 - }
187 -
188 - function get_table_pos($tag) {
189 - $offset = $this->tables[$tag]['offset'];
190 - $length = $this->tables[$tag]['length'];
191 - return array($offset, $length);
192 - }
193 -
194 - function seek($pos) {
195 - $this->_pos = $pos;
196 - fseek($this->fh,$this->_pos);
197 - }
198 -
199 - function skip($delta) {
200 - $this->_pos = $this->_pos + $delta;
201 - // fseek($this->fh,$this->_pos);
202 - fseek($this->fh,$delta,SEEK_CUR); // mPDF 5.3.19
203 - }
204 -
205 - function seek_table($tag, $offset_in_table = 0) {
206 - $tpos = $this->get_table_pos($tag);
207 - $this->_pos = $tpos[0] + $offset_in_table;
208 - fseek($this->fh, $this->_pos);
209 - return $this->_pos;
210 - }
211 -
212 - function read_tag() {
213 - $this->_pos += 4;
214 - return fread($this->fh,4);
215 - }
216 -
217 - function read_short() {
218 - $this->_pos += 2;
219 - $s = fread($this->fh,2);
220 - $a = (ord($s[0])<<8) + ord($s[1]);
221 - if ($a & (1 << 15) ) {
222 - $a = ($a - (1 << 16));
223 - }
224 - return $a;
225 - }
226 -
227 - function unpack_short($s) {
228 - $a = (ord($s[0])<<8) + ord($s[1]);
229 - if ($a & (1 << 15) ) {
230 - $a = ($a - (1 << 16));
231 - }
232 - return $a;
233 - }
234 -
235 - function read_ushort() {
236 - $this->_pos += 2;
237 - $s = fread($this->fh,2);
238 - return (ord($s[0])<<8) + ord($s[1]);
239 - }
240 -
241 - function read_ulong() {
242 - $this->_pos += 4;
243 - $s = fread($this->fh,4);
244 - // if large uInt32 as an integer, PHP converts it to -ve
245 - return (ord($s[0])*16777216) + (ord($s[1])<<16) + (ord($s[2])<<8) + ord($s[3]); // 16777216 = 1<<24
246 - }
247 -
248 - function get_ushort($pos) {
249 - fseek($this->fh,$pos);
250 - $s = fread($this->fh,2);
251 - return (ord($s[0])<<8) + ord($s[1]);
252 - }
253 -
254 - function get_ulong($pos) {
255 - fseek($this->fh,$pos);
256 - $s = fread($this->fh,4);
257 - // iF large uInt32 as an integer, PHP converts it to -ve
258 - return (ord($s[0])*16777216) + (ord($s[1])<<16) + (ord($s[2])<<8) + ord($s[3]); // 16777216 = 1<<24
259 - }
260 -
261 - function pack_short($val) {
262 - if ($val<0) {
263 - $val = abs($val);
264 - $val = ~$val;
265 - $val += 1;
266 - }
267 - return pack("n",$val);
268 - }
269 -
270 - function splice($stream, $offset, $value) {
271 - return substr($stream,0,$offset) . $value . substr($stream,$offset+strlen($value));
272 - }
273 -
274 - function _set_ushort($stream, $offset, $value) {
275 - $up = pack("n", $value);
276 - return $this->splice($stream, $offset, $up);
277 - }
278 -
279 - // mPDF 5.0
280 - function _set_short($stream, $offset, $val) {
281 - if ($val<0) {
282 - $val = abs($val);
283 - $val = ~$val;
284 - $val += 1;
285 - }
286 - $up = pack("n",$val);
287 - return $this->splice($stream, $offset, $up);
288 - }
289 -
290 - function get_chunk($pos, $length) {
291 - fseek($this->fh,$pos);
292 - if ($length <1) { return ''; }
293 - return (fread($this->fh,$length));
294 - }
295 -
296 - function get_table($tag) {
297 - list($pos, $length) = $this->get_table_pos($tag);
298 - if ($length == 0) { return ''; }
299 - fseek($this->fh,$pos);
300 - return (fread($this->fh,$length));
301 - }
302 -
303 - function add($tag, $data) {
304 - if ($tag == 'head') {
305 - $data = $this->splice($data, 8, "\0\0\0\0");
306 - }
307 - $this->otables[$tag] = $data;
308 - }
309 -
310 -
311 -
312 -/////////////////////////////////////////////////////////////////////////////////////////
313 - function getCTG($file, $TTCfontID=0, $debug=false) {
314 - $this->filename = $file;
315 - $this->fh = fopen($file,'rb') or die('Can\'t open file ' . $file);
316 - $this->_pos = 0;
317 - $this->charWidths = '';
318 - $this->glyphPos = array();
319 - $this->charToGlyph = array();
320 - $this->tables = array();
321 - $this->numTTCFonts = 0;
322 - $this->TTCFonts = array();
323 - $this->skip(4);
324 - if ($TTCfontID > 0) {
325 - $this->version = $version = $this->read_ulong(); // TTC Header version now
326 - if (!in_array($version, array(0x00010000,0x00020000)))
327 - die("ERROR - Error parsing TrueType Collection: version=".$version." - " . $file);
328 - $this->numTTCFonts = $this->read_ulong();
329 - for ($i=1; $i<=$this->numTTCFonts; $i++) {
330 - $this->TTCFonts[$i]['offset'] = $this->read_ulong();
331 - }
332 - $this->seek($this->TTCFonts[$TTCfontID]['offset']);
333 - $this->version = $version = $this->read_ulong(); // TTFont version again now
334 - }
335 - $this->readTableDirectory($debug);
336 -
337 -
338 - // cmap - Character to glyph index mapping table
339 - $cmap_offset = $this->seek_table("cmap");
340 - $this->skip(2);
341 - $cmapTableCount = $this->read_ushort();
342 - $unicode_cmap_offset = 0;
343 - for ($i=0;$i<$cmapTableCount;$i++) {
344 - $platformID = $this->read_ushort();
345 - $encodingID = $this->read_ushort();
346 - $offset = $this->read_ulong();
347 - $save_pos = $this->_pos;
348 - if ($platformID == 3 && $encodingID == 1) { // Microsoft, Unicode
349 - $format = $this->get_ushort($cmap_offset + $offset);
350 - if ($format == 4) {
351 - $unicode_cmap_offset = $cmap_offset + $offset;
352 - break;
353 - }
354 - }
355 - else if ($platformID == 0) { // Unicode -- assume all encodings are compatible
356 - $format = $this->get_ushort($cmap_offset + $offset);
357 - if ($format == 4) {
358 - $unicode_cmap_offset = $cmap_offset + $offset;
359 - break;
360 - }
361 - }
362 - $this->seek($save_pos );
363 - }
364 -
365 - $glyphToChar = array();
366 - $charToGlyph = array();
367 - $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph );
368 -
369 - fclose($this->fh);
370 - return ($charToGlyph);
371 - }
372 -
373 -/////////////////////////////////////////////////////////////////////////////////////////
374 - function getTTCFonts($file) {
375 - $this->filename = $file;
376 - $this->fh = fopen($file,'rb');
377 - if (!$this->fh) { return ('ERROR - Can\'t open file ' . $file); }
378 - $this->numTTCFonts = 0;
379 - $this->TTCFonts = array();
380 - $this->version = $version = $this->read_ulong();
381 - if ($version==0x74746366) {
382 - $this->version = $version = $this->read_ulong(); // TTC Header version now
383 - if (!in_array($version, array(0x00010000,0x00020000)))
384 - return("ERROR - Error parsing TrueType Collection: version=".$version." - " . $file);
385 - }
386 - else {
387 - return("ERROR - Not a TrueType Collection: version=".$version." - " . $file);
388 - }
389 - $this->numTTCFonts = $this->read_ulong();
390 - for ($i=1; $i<=$this->numTTCFonts; $i++) {
391 - $this->TTCFonts[$i]['offset'] = $this->read_ulong();
392 - }
393 - }
394 -
395 -
396 - // Used to get font information from files in directory for mPDF config
397 - function extractCoreInfo($file, $TTCfontID=0) {
398 - $this->filename = $file;
399 - $this->fh = fopen($file,'rb');
400 - if (!$this->fh) { return ('ERROR - Can\'t open file ' . $file); }
401 - $this->_pos = 0;
402 - $this->charWidths = '';
403 - $this->glyphPos = array();
404 - $this->charToGlyph = array();
405 - $this->tables = array();
406 - $this->otables = array();
407 - $this->ascent = 0;
408 - $this->descent = 0;
409 - $this->numTTCFonts = 0;
410 - $this->TTCFonts = array();
411 - $this->version = $version = $this->read_ulong();
412 - $this->panose = array(); // mPDF 5.0
413 - if ($version==0x4F54544F)
414 - return("ERROR - NOT ADDED as Postscript outlines are not supported - " . $file);
415 - if ($version==0x74746366) {
416 - if ($TTCfontID > 0) {
417 - $this->version = $version = $this->read_ulong(); // TTC Header version now
418 - if (!in_array($version, array(0x00010000,0x00020000)))
419 - return("ERROR - NOT ADDED as Error parsing TrueType Collection: version=".$version." - " . $file);
420 - }
421 - else return("ERROR - Error parsing TrueType Collection - " . $file);
422 - $this->numTTCFonts = $this->read_ulong();
423 - for ($i=1; $i<=$this->numTTCFonts; $i++) {
424 - $this->TTCFonts[$i]['offset'] = $this->read_ulong();
425 - }
426 - $this->seek($this->TTCFonts[$TTCfontID]['offset']);
427 - $this->version = $version = $this->read_ulong(); // TTFont version again now
428 - $this->readTableDirectory(false);
429 - }
430 - else {
431 - if (!in_array($version, array(0x00010000,0x74727565)))
432 - return("ERROR - NOT ADDED as Not a TrueType font: version=".$version." - " . $file);
433 - $this->readTableDirectory(false);
434 - }
435 -
436 -/* Included for testing...
437 - $cmap_offset = $this->seek_table("cmap");
438 - $this->skip(2);
439 - $cmapTableCount = $this->read_ushort();
440 - $unicode_cmap_offset = 0;
441 - for ($i=0;$i<$cmapTableCount;$i++) {
442 - $x[$i]['platformId'] = $this->read_ushort();
443 - $x[$i]['encodingId'] = $this->read_ushort();
444 - $x[$i]['offset'] = $this->read_ulong();
445 - $save_pos = $this->_pos;
446 - $x[$i]['format'] = $this->get_ushort($cmap_offset + $x[$i]['offset'] );
447 - $this->seek($save_pos );
448 - }
449 - print_r($x); exit;
450 -*/
451 - ///////////////////////////////////
452 - // name - Naming table
453 - ///////////////////////////////////
454 - $name_offset = $this->seek_table("name");
455 - $format = $this->read_ushort();
456 - if ($format != 0 && $format != 1) // mPDF 5.3.73
457 - return("ERROR - NOT ADDED as Unknown name table format ".$format." - " . $file);
458 - $numRecords = $this->read_ushort();
459 - $string_data_offset = $name_offset + $this->read_ushort();
460 - $names = array(1=>'',2=>'',3=>'',4=>'',6=>'');
461 - $K = array_keys($names);
462 - $nameCount = count($names);
463 - for ($i=0;$i<$numRecords; $i++) {
464 - $platformId = $this->read_ushort();
465 - $encodingId = $this->read_ushort();
466 - $languageId = $this->read_ushort();
467 - $nameId = $this->read_ushort();
468 - $length = $this->read_ushort();
469 - $offset = $this->read_ushort();
470 - if (!in_array($nameId,$K)) continue;
471 - $N = '';
472 - if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name
473 - $opos = $this->_pos;
474 - $this->seek($string_data_offset + $offset);
475 - if ($length % 2 != 0)
476 - $length += 1;
477 - $length /= 2;
478 - $N = '';
479 - while ($length > 0) {
480 - $char = $this->read_ushort();
481 - $N .= (chr($char));
482 - $length -= 1;
483 - }
484 - $this->_pos = $opos;
485 - $this->seek($opos);
486 - }
487 - else if ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name
488 - $opos = $this->_pos;
489 - $N = $this->get_chunk($string_data_offset + $offset, $length);
490 - $this->_pos = $opos;
491 - $this->seek($opos);
492 - }
493 - if ($N && $names[$nameId]=='') {
494 - $names[$nameId] = $N;
495 - $nameCount -= 1;
496 - if ($nameCount==0) break;
497 - }
498 - }
499 - if ($names[6])
500 - $psName = preg_replace('/ /','-',$names[6]);
501 - else if ($names[4])
502 - $psName = preg_replace('/ /','-',$names[4]);
503 - else if ($names[1])
504 - $psName = preg_replace('/ /','-',$names[1]);
505 - else
506 - $psName = '';
507 - if (!$names[1] && !$psName)
508 - return("ERROR - NOT ADDED as Could not find valid font name - " . $file);
509 - $this->name = $psName;
510 - if ($names[1]) { $this->familyName = $names[1]; } else { $this->familyName = $psName; }
511 - if ($names[2]) { $this->styleName = $names[2]; } else { $this->styleName = 'Regular'; }
512 -
513 - ///////////////////////////////////
514 - // head - Font header table
515 - ///////////////////////////////////
516 - $this->seek_table("head");
517 - $ver_maj = $this->read_ushort();
518 - $ver_min = $this->read_ushort();
519 - if ($ver_maj != 1)
520 - return('ERROR - NOT ADDED as Unknown head table version '. $ver_maj .'.'. $ver_min." - " . $file);
521 - $this->fontRevision = $this->read_ushort() . $this->read_ushort();
522 - $this->skip(4);
523 - $magic = $this->read_ulong();
524 - if ($magic != 0x5F0F3CF5)
525 - return('ERROR - NOT ADDED as Invalid head table magic ' .$magic." - " . $file);
526 - $this->skip(2);
527 - $this->unitsPerEm = $unitsPerEm = $this->read_ushort();
528 - $scale = 1000 / $unitsPerEm;
529 - $this->skip(24);
530 - $macStyle = $this->read_short();
531 - $this->skip(4);
532 - $indexLocFormat = $this->read_short();
533 -
534 - ///////////////////////////////////
535 - // OS/2 - OS/2 and Windows metrics table
536 - ///////////////////////////////////
537 - $sFamily = '';
538 - $panose = '';
539 - $fsSelection = '';
540 - if (isset($this->tables["OS/2"])) {
541 - $this->seek_table("OS/2");
542 - $this->skip(30);
543 - $sF = $this->read_short();
544 - $sFamily = ($sF >> 8);
545 - $this->_pos += 10; //PANOSE = 10 byte length
546 - $panose = fread($this->fh,10);
547 - $this->panose = array();
548 - for ($p=0;$p<strlen($panose);$p++) { $this->panose[] = ord($panose[$p]); }
549 - $this->skip(20);
550 - $fsSelection = $this->read_short();
551 - }
552 -
553 - ///////////////////////////////////
554 - // post - PostScript table
555 - ///////////////////////////////////
556 - $this->seek_table("post");
557 - $this->skip(4);
558 - $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0;
559 - $this->skip(4);
560 - $isFixedPitch = $this->read_ulong();
561 -
562 -
563 -
564 - ///////////////////////////////////
565 - // cmap - Character to glyph index mapping table
566 - ///////////////////////////////////
567 - $cmap_offset = $this->seek_table("cmap");
568 - $this->skip(2);
569 - $cmapTableCount = $this->read_ushort();
570 - $unicode_cmap_offset = 0;
571 - for ($i=0;$i<$cmapTableCount;$i++) {
572 - $platformID = $this->read_ushort();
573 - $encodingID = $this->read_ushort();
574 - $offset = $this->read_ulong();
575 - $save_pos = $this->_pos;
576 - if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
577 - $format = $this->get_ushort($cmap_offset + $offset);
578 - if ($format == 4) {
579 - if (!$unicode_cmap_offset) $unicode_cmap_offset = $cmap_offset + $offset;
580 - }
581 - }
582 - else if ((($platformID == 3 && $encodingID == 10) || $platformID == 0)) { // Microsoft, Unicode Format 12 table HKCS
583 - $format = $this->get_ushort($cmap_offset + $offset);
584 - if ($format == 12) {
585 - $unicode_cmap_offset = $cmap_offset + $offset;
586 - break;
587 - }
588 - }
589 - $this->seek($save_pos );
590 - }
591 -
592 - if (!$unicode_cmap_offset)
593 - return('ERROR - Font ('.$this->filename .') NOT ADDED as it is not Unicode encoded, and cannot be used by mPDF');
594 -
595 - $rtl = false;
596 - $indic = false;
597 - $cjk = false;
598 - $sip = false;
599 - $smp = false;
600 -
601 - // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
602 - if ($format == 12) {
603 - $this->seek($unicode_cmap_offset + 4);
604 - $length = $this->read_ulong();
605 - $limit = $unicode_cmap_offset + $length;
606 - $this->skip(4);
607 - $nGroups = $this->read_ulong();
608 - for($i=0; $i<$nGroups ; $i++) {
609 - $startCharCode = $this->read_ulong();
610 - $endCharCode = $this->read_ulong();
611 - $startGlyphCode = $this->read_ulong();
612 - if (($endCharCode > 0x20000 && $endCharCode < 0x2A6DF) || ($endCharCode > 0x2F800 && $endCharCode < 0x2FA1F)) {
613 - $sip = true;
614 - }
615 - else if ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) {
616 - $smp = true;
617 - }
618 - else if (($endCharCode > 0x0590 && $endCharCode < 0x077F) || ($endCharCode > 0xFE70 && $endCharCode < 0xFEFF) || ($endCharCode > 0xFB50 && $endCharCode < 0xFDFF)) {
619 - $rtl = true;
620 - }
621 - else if ($endCharCode > 0x0900 && $endCharCode < 0x0DFF) {
622 - $indic = true;
623 - }
624 - else if (($endCharCode > 0x2E80 && $endCharCode < 0x4DC0) || ($endCharCode > 0x4E00 && $endCharCode < 0xA4CF) || ($endCharCode > 0xAC00 && $endCharCode < 0xD7AF) || ($endCharCode > 0xF900 && $endCharCode < 0xFAFF) || ($endCharCode > 0xFE30 && $endCharCode < 0xFE4F)) {
625 - $cjk = true;
626 - }
627 - }
628 - }
629 -
630 - else { // Format 4 CMap
631 - $this->seek($unicode_cmap_offset + 2);
632 - $length = $this->read_ushort();
633 - $limit = $unicode_cmap_offset + $length;
634 - $this->skip(2);
635 -
636 - $segCount = $this->read_ushort() / 2;
637 - $this->skip(6);
638 - $endCount = array();
639 - for($i=0; $i<$segCount; $i++) { $endCount[] = $this->read_ushort(); }
640 - $this->skip(2);
641 - $startCount = array();
642 - for($i=0; $i<$segCount; $i++) { $startCount[] = $this->read_ushort(); }
643 - $idDelta = array();
644 - for($i=0; $i<$segCount; $i++) { $idDelta[] = $this->read_short(); } // ???? was unsigned short
645 - $idRangeOffset_start = $this->_pos;
646 - $idRangeOffset = array();
647 - for($i=0; $i<$segCount; $i++) { $idRangeOffset[] = $this->read_ushort(); }
648 -
649 - for ($n=0;$n<$segCount;$n++) {
650 - if (($endCount[$n] > 0x0590 && $endCount[$n] < 0x077F) || ($endCount[$n] > 0xFE70 && $endCount[$n] < 0xFEFF) || ($endCount[$n] > 0xFB50 && $endCount[$n] < 0xFDFF)) {
651 - $rtl = true;
652 - }
653 - else if ($endCount[$n] > 0x0900 && $endCount[$n] < 0x0DFF) {
654 - $indic = true;
655 - }
656 - else if (($endCount[$n] > 0x2E80 && $endCount[$n] < 0x4DC0) || ($endCount[$n] > 0x4E00 && $endCount[$n] < 0xA4CF) || ($endCount[$n] > 0xAC00 && $endCount[$n] < 0xD7AF) || ($endCount[$n] > 0xF900 && $endCount[$n] < 0xFAFF) || ($endCount[$n] > 0xFE30 && $endCount[$n] < 0xFE4F)) {
657 - $cjk = true;
658 - }
659 - }
660 - }
661 -
662 -
663 -
664 - $bold = false;
665 - $italic = false;
666 - $ftype = '';
667 - if ($macStyle & (1 << 0)) { $bold = true; } // bit 0 bold
668 - else if ($fsSelection & (1 << 5)) { $bold = true; } // 5 BOLD Characters are emboldened
669 -
670 - if ($macStyle & (1 << 1)) { $italic = true; } // bit 1 italic
671 - else if ($fsSelection & (1 << 0)) { $italic = true; } // 0 ITALIC Font contains Italic characters, otherwise they are upright
672 - else if ($this->italicAngle <> 0) { $italic = true; }
673 -
674 - if ($isFixedPitch ) { $ftype = 'mono'; }
675 - else if ($sFamily >0 && $sFamily <8) { $ftype = 'serif'; }
676 - else if ($sFamily ==8) { $ftype = 'sans'; }
677 - else if ($sFamily ==10) { $ftype = 'cursive'; }
678 - // Use PANOSE
679 - if ($panose) {
680 - $bFamilyType=ord($panose[0]);
681 - if ($bFamilyType==2) {
682 - $bSerifStyle=ord($panose[1]);
683 - if (!$ftype) {
684 - if ($bSerifStyle>1 && $bSerifStyle<11) { $ftype = 'serif'; }
685 - else if ($bSerifStyle>10) { $ftype = 'sans'; }
686 - }
687 - $bProportion=ord($panose[3]);
688 - if ($bProportion==9 || $bProportion==1) { $ftype = 'mono'; } // ==1 i.e. No Fit needed for OCR-a and -b
689 - }
690 - else if ($bFamilyType==3) {
691 - $ftype = 'cursive';
692 - }
693 - }
694 -
695 - fclose($this->fh);
696 - return array($this->familyName, $bold, $italic, $ftype, $TTCfontID, $rtl, $indic, $cjk, $sip, $smp);
697 - }
698 -
699 -
700 -/////////////////////////////////////////////////////////////////////////////////////////
701 -
702 -/////////////////////////////////////////////////////////////////////////////////////////
703 -
704 - function extractInfo($debug=false, $BMPonly=false, $kerninfo=false) {
705 - ///////////////////////////////////
706 - // name - Naming table
707 - ///////////////////////////////////
708 - $this->panose = array();
709 - $this->sFamilyClass = 0;
710 - $this->sFamilySubClass = 0;
711 -
712 -/* Test purposes - displays table of names
713 - $name_offset = $this->seek_table("name");
714 - $format = $this->read_ushort();
715 - if ($format != 0 && $format != 1) // mPDF 5.3.73
716 - die("Unknown name table format ".$format);
717 - $numRecords = $this->read_ushort();
718 - $string_data_offset = $name_offset + $this->read_ushort();
719 - for ($i=0;$i<$numRecords; $i++) {
720 - $x[$i]['platformId'] = $this->read_ushort();
721 - $x[$i]['encodingId'] = $this->read_ushort();
722 - $x[$i]['languageId'] = $this->read_ushort();
723 - $x[$i]['nameId'] = $this->read_ushort();
724 - $x[$i]['length'] = $this->read_ushort();
725 - $x[$i]['offset'] = $this->read_ushort();
726 -
727 - $N = '';
728 - if ($x[$i]['platformId'] == 1 && $x[$i]['encodingId'] == 0 && $x[$i]['languageId'] == 0) { // Roman
729 - $opos = $this->_pos;
730 - $N = $this->get_chunk($string_data_offset + $x[$i]['offset'] , $x[$i]['length'] );
731 - $this->_pos = $opos;
732 - $this->seek($opos);
733 - }
734 - else { // Unicode
735 - $opos = $this->_pos;
736 - $this->seek($string_data_offset + $x[$i]['offset'] );
737 - $length = $x[$i]['length'] ;
738 - if ($length % 2 != 0)
739 - $length -= 1;
740 - // die("PostScript name is UTF-16BE string of odd length");
741 - $length /= 2;
742 - $N = '';
743 - while ($length > 0) {
744 - $char = $this->read_ushort();
745 - $N .= (chr($char));
746 - $length -= 1;
747 - }
748 - $this->_pos = $opos;
749 - $this->seek($opos);
750 - }
751 - $x[$i]['names'][$nameId] = $N;
752 - }
753 - print_r($x); exit;
754 -*/
755 -
756 - $name_offset = $this->seek_table("name");
757 - $format = $this->read_ushort();
758 - if ($format != 0 && $format != 1) // mPDF 5.3.73
759 - die("Unknown name table format ".$format);
760 - $numRecords = $this->read_ushort();
761 - $string_data_offset = $name_offset + $this->read_ushort();
762 - $names = array(1=>'',2=>'',3=>'',4=>'',6=>'');
763 - $K = array_keys($names);
764 - $nameCount = count($names);
765 - for ($i=0;$i<$numRecords; $i++) {
766 - $platformId = $this->read_ushort();
767 - $encodingId = $this->read_ushort();
768 - $languageId = $this->read_ushort();
769 - $nameId = $this->read_ushort();
770 - $length = $this->read_ushort();
771 - $offset = $this->read_ushort();
772 - if (!in_array($nameId,$K)) continue;
773 - $N = '';
774 - if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name
775 - $opos = $this->_pos;
776 - $this->seek($string_data_offset + $offset);
777 - if ($length % 2 != 0)
778 - die("PostScript name is UTF-16BE string of odd length");
779 - $length /= 2;
780 - $N = '';
781 - while ($length > 0) {
782 - $char = $this->read_ushort();
783 - $N .= (chr($char));
784 - $length -= 1;
785 - }
786 - $this->_pos = $opos;
787 - $this->seek($opos);
788 - }
789 - else if ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name
790 - $opos = $this->_pos;
791 - $N = $this->get_chunk($string_data_offset + $offset, $length);
792 - $this->_pos = $opos;
793 - $this->seek($opos);
794 - }
795 - if ($N && $names[$nameId]=='') {
796 - $names[$nameId] = $N;
797 - $nameCount -= 1;
798 - if ($nameCount==0) break;
799 - }
800 - }
801 - if ($names[6])
802 - // $psName = preg_replace('/ /','-',$names[6]); // mPDF 5.2.03
803 - $psName = $names[6];
804 - else if ($names[4])
805 - $psName = preg_replace('/ /','-',$names[4]);
806 - else if ($names[1])
807 - $psName = preg_replace('/ /','-',$names[1]);
808 - else
809 - $psName = '';
810 - if (!$psName)
811 - die("Could not find PostScript font name: ".$this->filename);
812 - if ($debug) {
813 - for ($i=0;$i<count($psName);$i++) { // mPDF 5.3.07
814 - $c = $psName[$i];
815 - $oc = ord($c);
816 - if ($oc>126 || strpos(' [](){}<>/%',$c)!==false)
817 - die("psName=".$psName." contains invalid character ".$c." ie U+".ord(c));
818 - }
819 - }
820 - $this->name = $psName;
821 - if ($names[1]) { $this->familyName = $names[1]; } else { $this->familyName = $psName; }
822 - if ($names[2]) { $this->styleName = $names[2]; } else { $this->styleName = 'Regular'; }
823 - if ($names[4]) { $this->fullName = $names[4]; } else { $this->fullName = $psName; }
824 - if ($names[3]) { $this->uniqueFontID = $names[3]; } else { $this->uniqueFontID = $psName; }
825 -
826 - if ($names[6]) { $this->fullName = $names[6]; } // mPDF 5.2.03
827 -
828 - ///////////////////////////////////
829 - // head - Font header table
830 - ///////////////////////////////////
831 - $this->seek_table("head");
832 - if ($debug) {
833 - $ver_maj = $this->read_ushort();
834 - $ver_min = $this->read_ushort();
835 - if ($ver_maj != 1)
836 - die('Unknown head table version '. $ver_maj .'.'. $ver_min);
837 - $this->fontRevision = $this->read_ushort() . $this->read_ushort();
838 -
839 - $this->skip(4);
840 - $magic = $this->read_ulong();
841 - if ($magic != 0x5F0F3CF5)
842 - die('Invalid head table magic ' .$magic);
843 - $this->skip(2);
844 - }
845 - else {
846 - $this->skip(18);
847 - }
848 - $this->unitsPerEm = $unitsPerEm = $this->read_ushort();
849 - $scale = 1000 / $unitsPerEm;
850 - $this->skip(16);
851 - $xMin = $this->read_short();
852 - $yMin = $this->read_short();
853 - $xMax = $this->read_short();
854 - $yMax = $this->read_short();
855 - $this->bbox = array(($xMin*$scale), ($yMin*$scale), ($xMax*$scale), ($yMax*$scale));
856 - $this->skip(3*2);
857 - $indexToLocFormat = $this->read_ushort();
858 - $glyphDataFormat = $this->read_ushort();
859 - if ($glyphDataFormat != 0)
860 - die('Unknown glyph data format '.$glyphDataFormat);
861 -
862 - ///////////////////////////////////
863 - // hhea metrics table
864 - ///////////////////////////////////
865 - // ttf2t1 seems to use this value rather than the one in OS/2 - so put in for compatibility
866 - if (isset($this->tables["hhea"])) {
867 - $this->seek_table("hhea");
868 - $this->skip(4);
869 - $hheaAscender = $this->read_short();
870 - $hheaDescender = $this->read_short();
871 - $this->ascent = ($hheaAscender *$scale);
872 - $this->descent = ($hheaDescender *$scale);
873 - }
874 -
875 - ///////////////////////////////////
876 - // OS/2 - OS/2 and Windows metrics table
877 - ///////////////////////////////////
878 - if (isset($this->tables["OS/2"])) {
879 - $this->seek_table("OS/2");
880 - $version = $this->read_ushort();
881 - $this->skip(2);
882 - $usWeightClass = $this->read_ushort();
883 - $this->skip(2);
884 - $fsType = $this->read_ushort();
885 - if ($fsType == 0x0002 || ($fsType & 0x0300) != 0) {
886 - global $overrideTTFFontRestriction;
887 - if (!$overrideTTFFontRestriction) die('ERROR - Font file '.$this->filename.' cannot be embedded due to copyright restrictions.');
888 - $this->restrictedUse = true;
889 - }
890 - $this->skip(20);
891 - $sF = $this->read_short();
892 - $this->sFamilyClass = ($sF >> 8);
893 - $this->sFamilySubClass = ($sF & 0xFF);
894 - $this->_pos += 10; //PANOSE = 10 byte length
895 - $panose = fread($this->fh,10);
896 - $this->panose = array();
897 - for ($p=0;$p<strlen($panose);$p++) { $this->panose[] = ord($panose[$p]); }
898 - $this->skip(26);
899 - $sTypoAscender = $this->read_short();
900 - $sTypoDescender = $this->read_short();
901 - if (!$this->ascent) $this->ascent = ($sTypoAscender*$scale);
902 - if (!$this->descent) $this->descent = ($sTypoDescender*$scale);
903 - if ($version > 1) {
904 - $this->skip(16);
905 - $sCapHeight = $this->read_short();
906 - $this->capHeight = ($sCapHeight*$scale);
907 - }
908 - else {
909 - $this->capHeight = $this->ascent;
910 - }
911 - }
912 - else {
913 - $usWeightClass = 500;
914 - if (!$this->ascent) $this->ascent = ($yMax*$scale);
915 - if (!$this->descent) $this->descent = ($yMin*$scale);
916 - $this->capHeight = $this->ascent;
917 - }
918 - $this->stemV = 50 + intval(pow(($usWeightClass / 65.0),2));
919 -
920 - ///////////////////////////////////
921 - // post - PostScript table
922 - ///////////////////////////////////
923 - $this->seek_table("post");
924 - if ($debug) {
925 - $ver_maj = $this->read_ushort();
926 - $ver_min = $this->read_ushort();
927 - if ($ver_maj <1 || $ver_maj >4)
928 - die('Unknown post table version '.$ver_maj);
929 - }
930 - else {
931 - $this->skip(4);
932 - }
933 - $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0;
934 - $this->underlinePosition = $this->read_short() * $scale;
935 - $this->underlineThickness = $this->read_short() * $scale;
936 - $isFixedPitch = $this->read_ulong();
937 -
938 - $this->flags = 4;
939 -
940 - if ($this->italicAngle!= 0)
941 - $this->flags = $this->flags | 64;
942 - if ($usWeightClass >= 600)
943 - $this->flags = $this->flags | 262144;
944 - if ($isFixedPitch)
945 - $this->flags = $this->flags | 1;
946 -
947 - ///////////////////////////////////
948 - // hhea - Horizontal header table
949 - ///////////////////////////////////
950 - $this->seek_table("hhea");
951 - if ($debug) {
952 - $ver_maj = $this->read_ushort();
953 - $ver_min = $this->read_ushort();
954 - if ($ver_maj != 1)
955 - die('Unknown hhea table version '.$ver_maj);
956 - $this->skip(28);
957 - }
958 - else {
959 - $this->skip(32);
960 - }
961 - $metricDataFormat = $this->read_ushort();
962 - if ($metricDataFormat != 0)
963 - die('Unknown horizontal metric data format '.$metricDataFormat);
964 - $numberOfHMetrics = $this->read_ushort();
965 - if ($numberOfHMetrics == 0)
966 - die('Number of horizontal metrics is 0');
967 -
968 - ///////////////////////////////////
969 - // maxp - Maximum profile table
970 - ///////////////////////////////////
971 - $this->seek_table("maxp");
972 - if ($debug) {
973 - $ver_maj = $this->read_ushort();
974 - $ver_min = $this->read_ushort();
975 - if ($ver_maj != 1)
976 - die('Unknown maxp table version '.$ver_maj);
977 - }
978 - else {
979 - $this->skip(4);
980 - }
981 - $numGlyphs = $this->read_ushort();
982 -
983 -
984 - ///////////////////////////////////
985 - // cmap - Character to glyph index mapping table
986 - ///////////////////////////////////
987 -/* Test purposes - displays list of cmaps
988 - $cmap_offset = $this->seek_table("cmap");
989 - $this->skip(2);
990 - $cmapTableCount = $this->read_ushort();
991 - $unicode_cmap_offset = 0;
992 - for ($i=0;$i<$cmapTableCount;$i++) {
993 - $x[$i]['platformId'] = $this->read_ushort();
994 - $x[$i]['encodingId'] = $this->read_ushort();
995 - $x[$i]['offset'] = $this->read_ulong();
996 - $save_pos = $this->_pos;
997 - $x[$i]['format'] = $this->get_ushort($cmap_offset + $x[$i]['offset'] );
998 - $this->seek($save_pos );
999 - }
1000 - print_r($x); exit;
1001 -*/
1002 -
1003 - $cmap_offset = $this->seek_table("cmap");
1004 - $this->skip(2);
1005 - $cmapTableCount = $this->read_ushort();
1006 - $unicode_cmap_offset = 0;
1007 - for ($i=0;$i<$cmapTableCount;$i++) {
1008 - $platformID = $this->read_ushort();
1009 - $encodingID = $this->read_ushort();
1010 - $offset = $this->read_ulong();
1011 - $save_pos = $this->_pos;
1012 - if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
1013 - $format = $this->get_ushort($cmap_offset + $offset);
1014 - if ($format == 4) {
1015 - if (!$unicode_cmap_offset) $unicode_cmap_offset = $cmap_offset + $offset;
1016 - if ($BMPonly) break; // mPDF 5.0
1017 - }
1018 - }
1019 - // Microsoft, Unicode Format 12 table HKCS
1020 - else if ((($platformID == 3 && $encodingID == 10) || $platformID == 0) && !$BMPonly) {
1021 - $format = $this->get_ushort($cmap_offset + $offset);
1022 - if ($format == 12) {
1023 - $unicode_cmap_offset = $cmap_offset + $offset;
1024 - break;
1025 - }
1026 - }
1027 - $this->seek($save_pos );
1028 - }
1029 - if (!$unicode_cmap_offset)
1030 - die('Font ('.$this->filename .') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
1031 -
1032 -
1033 - $sipset = false;
1034 - $smpset = false;
1035 - // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
1036 - if ($format == 12 && !$BMPonly) {
1037 - $this->maxUniChar = 0; // mPDF 5.0
1038 - $this->seek($unicode_cmap_offset + 4);
1039 - $length = $this->read_ulong();
1040 - $limit = $unicode_cmap_offset + $length;
1041 - $this->skip(4);
1042 -
1043 - $nGroups = $this->read_ulong();
1044 -
1045 - $glyphToChar = array();
1046 - $charToGlyph = array();
1047 - for($i=0; $i<$nGroups ; $i++) {
1048 - $startCharCode = $this->read_ulong();
1049 - $endCharCode = $this->read_ulong();
1050 - $startGlyphCode = $this->read_ulong();
1051 - if (($endCharCode > 0x20000 && $endCharCode < 0x2A6DF) || ($endCharCode > 0x2F800 && $endCharCode < 0x2FA1F)) {
1052 - $sipset = true;
1053 - }
1054 - else if ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) {
1055 - $smpset = true;
1056 - }
1057 - $offset = 0;
1058 - for ($unichar=$startCharCode;$unichar<=$endCharCode;$unichar++) {
1059 - $glyph = $startGlyphCode + $offset ;
1060 - $offset++;
1061 - $charToGlyph[$unichar] = $glyph;
1062 - if ($unichar < 196608) { $this->maxUniChar = max($unichar,$this->maxUniChar); }
1063 - $glyphToChar[$glyph][] = $unichar;
1064 - }
1065 - }
1066 - }
1067 - else {
1068 -
1069 - $glyphToChar = array();
1070 - $charToGlyph = array();
1071 - $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph );
1072 -
1073 - }
1074 - $this->sipset = $sipset ;
1075 - $this->smpset = $smpset ;
1076 -
1077 - ///////////////////////////////////
1078 - // hmtx - Horizontal metrics table
1079 - ///////////////////////////////////
1080 - $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
1081 -
1082 - ///////////////////////////////////
1083 - // kern - Kerning pair table mPDF 5.1
1084 - ///////////////////////////////////
1085 - if ($kerninfo) {
1086 - // Recognises old form of Kerning table - as required by Windows - Format 0 only
1087 - $kern_offset = $this->seek_table("kern");
1088 - $version = $this->read_ushort();
1089 - $nTables = $this->read_ushort();
1090 - // subtable header
1091 - $sversion = $this->read_ushort();
1092 - $slength = $this->read_ushort();
1093 - $scoverage = $this->read_ushort();
1094 - $format = $scoverage >> 8;
1095 - if ($kern_offset && $version==0 && $format==0) {
1096 - // Format 0
1097 - $nPairs = $this->read_ushort();
1098 - $this->skip(6);
1099 - for ($i=0; $i<$nPairs; $i++) {
1100 - $left = $this->read_ushort();
1101 - $right = $this->read_ushort();
1102 - $val = $this->read_short();
1103 - if (count($glyphToChar[$left])==1 && count($glyphToChar[$right])==1) {
1104 - if ($left != 32 && $right != 32) {
1105 - $this->kerninfo[$glyphToChar[$left][0]][$glyphToChar[$right][0]] = intval($val*$scale);
1106 - }
1107 - }
1108 - }
1109 - }
1110 - }
1111 - }
1112 -
1113 -
1114 -/////////////////////////////////////////////////////////////////////////////////////////
1115 -
1116 -
1117 - function makeSubset($file, &$subset, $TTCfontID=0, $debug=false) {
1118 - $this->filename = $file;
1119 - $this->fh = fopen($file ,'rb') or die('Can\'t open file ' . $file);
1120 - $this->_pos = 0;
1121 - $this->charWidths = '';
1122 - $this->glyphPos = array();
1123 - $this->charToGlyph = array();
1124 - $this->tables = array();
1125 - $this->otables = array();
1126 - $this->ascent = 0;
1127 - $this->descent = 0;
1128 - $this->numTTCFonts = 0;
1129 - $this->TTCFonts = array();
1130 - $this->skip(4);
1131 - $this->maxUni = 0;
1132 - if ($TTCfontID > 0) {
1133 - $this->version = $version = $this->read_ulong(); // TTC Header version now
1134 - if (!in_array($version, array(0x00010000,0x00020000)))
1135 - die("ERROR - Error parsing TrueType Collection: version=".$version." - " . $file);
1136 - $this->numTTCFonts = $this->read_ulong();
1137 - for ($i=1; $i<=$this->numTTCFonts; $i++) {
1138 - $this->TTCFonts[$i]['offset'] = $this->read_ulong();
1139 - }
1140 - $this->seek($this->TTCFonts[$TTCfontID]['offset']);
1141 - $this->version = $version = $this->read_ulong(); // TTFont version again now
1142 - }
1143 - $this->readTableDirectory($debug);
1144 -
1145 -
1146 - ///////////////////////////////////
1147 - // head - Font header table
1148 - ///////////////////////////////////
1149 - $this->seek_table("head");
1150 - $this->skip(50);
1151 - $indexToLocFormat = $this->read_ushort();
1152 - $glyphDataFormat = $this->read_ushort();
1153 -
1154 - ///////////////////////////////////
1155 - // hhea - Horizontal header table
1156 - ///////////////////////////////////
1157 - $this->seek_table("hhea");
1158 - $this->skip(32);
1159 - $metricDataFormat = $this->read_ushort();
1160 - $orignHmetrics = $numberOfHMetrics = $this->read_ushort();
1161 -
1162 - ///////////////////////////////////
1163 - // maxp - Maximum profile table
1164 - ///////////////////////////////////
1165 - $this->seek_table("maxp");
1166 - $this->skip(4);
1167 - $numGlyphs = $this->read_ushort();
1168 -
1169 -
1170 - ///////////////////////////////////
1171 - // cmap - Character to glyph index mapping table
1172 - ///////////////////////////////////
1173 - $cmap_offset = $this->seek_table("cmap");
1174 - $this->skip(2);
1175 - $cmapTableCount = $this->read_ushort();
1176 - $unicode_cmap_offset = 0;
1177 - for ($i=0;$i<$cmapTableCount;$i++) {
1178 - $platformID = $this->read_ushort();
1179 - $encodingID = $this->read_ushort();
1180 - $offset = $this->read_ulong();
1181 - $save_pos = $this->_pos;
1182 - if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
1183 - $format = $this->get_ushort($cmap_offset + $offset);
1184 - if ($format == 4) {
1185 - $unicode_cmap_offset = $cmap_offset + $offset;
1186 - break;
1187 - }
1188 - }
1189 - $this->seek($save_pos );
1190 - }
1191 -
1192 - if (!$unicode_cmap_offset)
1193 - die('Font ('.$this->filename .') does not have Unicode cmap (platform 3, encoding 1, format 4, or platform 0 [any encoding] format 4)');
1194 -
1195 -
1196 - $glyphToChar = array();
1197 - $charToGlyph = array();
1198 - $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph );
1199 -
1200 - $this->charToGlyph = $charToGlyph;
1201 -
1202 - ///////////////////////////////////
1203 - // hmtx - Horizontal metrics table
1204 - ///////////////////////////////////
1205 - $scale = 1; // not used
1206 - $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
1207 -
1208 - ///////////////////////////////////
1209 - // loca - Index to location
1210 - ///////////////////////////////////
1211 - $this->getLOCA($indexToLocFormat, $numGlyphs);
1212 -
1213 - $subsetglyphs = array(0=>0, 1=>1, 2=>2); // mPDF 5.3.31
1214 - $subsetCharToGlyph = array();
1215 - foreach($subset AS $code) {
1216 - if (isset($this->charToGlyph[$code])) {
1217 - $subsetglyphs[$this->charToGlyph[$code]] = $code; // Old Glyph ID => Unicode
1218 - $subsetCharToGlyph[$code] = $this->charToGlyph[$code]; // Unicode to old GlyphID
1219 -
1220 - }
1221 - $this->maxUni = max($this->maxUni, $code);
1222 - }
1223 -
1224 - list($start,$dummy) = $this->get_table_pos('glyf');
1225 -
1226 - $glyphSet = array();
1227 - ksort($subsetglyphs);
1228 - $n = 0;
1229 - // mPDF 5.0
1230 - $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.
1231 - foreach($subsetglyphs AS $originalGlyphIdx => $uni) {
1232 - $fsLastCharIndex = max($fsLastCharIndex , $uni); // mPDF 5.0
1233 - $glyphSet[$originalGlyphIdx] = $n; // old glyphID to new glyphID
1234 - $n++;
1235 - }
1236 -
1237 - ksort($subsetCharToGlyph);
1238 - foreach($subsetCharToGlyph AS $uni => $originalGlyphIdx) {
1239 - $codeToGlyph[$uni] = $glyphSet[$originalGlyphIdx] ;
1240 - }
1241 - $this->codeToGlyph = $codeToGlyph;
1242 -
1243 - ksort($subsetglyphs);
1244 - foreach($subsetglyphs AS $originalGlyphIdx => $uni) {
1245 - $this->getGlyphs($originalGlyphIdx, $start, $glyphSet, $subsetglyphs);
1246 - }
1247 -
1248 - $numGlyphs = $numberOfHMetrics = count($subsetglyphs );
1249 -
1250 - ///////////////////////////////////
1251 - // name - table copied from the original
1252 - ///////////////////////////////////
1253 - $this->add('name', $this->get_table('name'));
1254 -
1255 - ///////////////////////////////////
1256 - //tables copied from the original
1257 - ///////////////////////////////////
1258 - // mPDF 5.0.067 // 5.1.020
1259 - $tags = array ('cvt ', 'fpgm', 'prep', 'gasp');
1260 - foreach($tags AS $tag) {
1261 - if (isset($this->tables[$tag])) { $this->add($tag, $this->get_table($tag)); }
1262 - }
1263 -
1264 - ///////////////////////////////////
1265 - // post - PostScript
1266 - ///////////////////////////////////
1267 - if (isset($this->tables['post'])) {
1268 - $opost = $this->get_table('post');
1269 - $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";
1270 - $this->add('post', $post);
1271 - }
1272 -
1273 - ///////////////////////////////////
1274 - // Sort CID2GID map into segments of contiguous codes
1275 - ///////////////////////////////////
1276 - ksort($codeToGlyph);
1277 - unset($codeToGlyph[0]);
1278 - //unset($codeToGlyph[65535]);
1279 - $rangeid = 0;
1280 - $range = array();
1281 - $prevcid = -2;
1282 - $prevglidx = -1;
1283 - // for each character
1284 - foreach ($codeToGlyph as $cid => $glidx) {
1285 - if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) {
1286 - $range[$rangeid][] = $glidx;
1287 - } else {
1288 - // new range
1289 - $rangeid = $cid;
1290 - $range[$rangeid] = array();
1291 - $range[$rangeid][] = $glidx;
1292 - }
1293 - $prevcid = $cid;
1294 - $prevglidx = $glidx;
1295 - }
1296 -
1297 -
1298 -
1299 - ///////////////////////////////////
1300 - // CMap table
1301 - ///////////////////////////////////
1302 - // cmap - Character to glyph mapping
1303 - $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF
1304 - $searchRange = 1;
1305 - $entrySelector = 0;
1306 - while ($searchRange * 2 <= $segCount ) {
1307 - $searchRange = $searchRange * 2;
1308 - $entrySelector = $entrySelector + 1;
1309 - }
1310 - $searchRange = $searchRange * 2;
1311 - $rangeShift = $segCount * 2 - $searchRange;
1312 - $length = 16 + (8*$segCount ) + ($numGlyphs+1);
1313 - // mPDF 5.3.30
1314 - $cmap = array(0, 3, // Index : version, number of encoding subtables
1315 - 0, 0, // Encoding Subtable : platform (UNI=0), encoding 0
1316 - 0, 28, // Encoding Subtable : offset (hi,lo)
1317 - 0, 3, // Encoding Subtable : platform (UNI=0), encoding 3
1318 - 0, 28, // Encoding Subtable : offset (hi,lo)
1319 - 3, 1, // Encoding Subtable : platform (MS=3), encoding 1
1320 - 0, 28, // Encoding Subtable : offset (hi,lo)
1321 - 4, $length, 0, // Format 4 Mapping subtable: format, length, language
1322 - $segCount*2,
1323 - $searchRange,
1324 - $entrySelector,
1325 - $rangeShift);
1326 -
1327 - // endCode(s)
1328 - foreach($range AS $start=>$subrange) {
1329 - $endCode = $start + (count($subrange)-1);
1330 - $cmap[] = $endCode; // endCode(s)
1331 - }
1332 - $cmap[] = 0xFFFF; // endCode of last Segment
1333 - $cmap[] = 0; // reservedPad
1334 -
1335 - // startCode(s)
1336 - foreach($range AS $start=>$subrange) {
1337 - $cmap[] = $start; // startCode(s)
1338 - }
1339 - $cmap[] = 0xFFFF; // startCode of last Segment
1340 - // idDelta(s)
1341 - foreach($range AS $start=>$subrange) {
1342 - $idDelta = -($start-$subrange[0]);
1343 - $n += count($subrange);
1344 - $cmap[] = $idDelta; // idDelta(s)
1345 - }
1346 - $cmap[] = 1; // idDelta of last Segment
1347 - // idRangeOffset(s)
1348 - foreach($range AS $subrange) {
1349 - $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
1350 -
1351 - }
1352 - $cmap[] = 0; // idRangeOffset of last Segment
1353 - foreach($range AS $subrange) {
1354 - foreach($subrange AS $glidx) {
1355 - $cmap[] = $glidx;
1356 - }
1357 - }
1358 - $cmap[] = 0; // Mapping for last character
1359 - $cmapstr = '';
1360 - foreach($cmap AS $cm) { $cmapstr .= pack("n",$cm); }
1361 - $this->add('cmap', $cmapstr);
1362 -
1363 -
1364 - ///////////////////////////////////
1365 - // glyf - Glyph data
1366 - ///////////////////////////////////
1367 - list($glyfOffset,$glyfLength) = $this->get_table_pos('glyf');
1368 - if ($glyfLength < $this->maxStrLenRead) {
1369 - $glyphData = $this->get_table('glyf');
1370 - }
1371 -
1372 - $offsets = array();
1373 - $glyf = '';
1374 - $pos = 0;
1375 - $hmtxstr = '';
1376 - $xMinT = 0;
1377 - $yMinT = 0;
1378 - $xMaxT = 0;
1379 - $yMaxT = 0;
1380 - $advanceWidthMax = 0;
1381 - $minLeftSideBearing = 0;
1382 - $minRightSideBearing = 0;
1383 - $xMaxExtent = 0;
1384 - $maxPoints = 0; // points in non-compound glyph
1385 - $maxContours = 0; // contours in non-compound glyph
1386 - $maxComponentPoints = 0; // points in compound glyph
1387 - $maxComponentContours = 0; // contours in compound glyph
1388 - $maxComponentElements = 0; // number of glyphs referenced at top level
1389 - $maxComponentDepth = 0; // levels of recursion, set to 0 if font has only simple glyphs
1390 - $this->glyphdata = array();
1391 -
1392 - foreach($subsetglyphs AS $originalGlyphIdx => $uni) {
1393 - // hmtx - Horizontal Metrics
1394 - $hm = $this->getHMetric($orignHmetrics, $originalGlyphIdx);
1395 - $hmtxstr .= $hm;
1396 -
1397 - $offsets[] = $pos;
1398 - $glyphPos = $this->glyphPos[$originalGlyphIdx];
1399 - $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
1400 - if ($glyfLength < $this->maxStrLenRead) {
1401 - $data = substr($glyphData,$glyphPos,$glyphLen);
1402 - }
1403 - else {
1404 - if ($glyphLen > 0) $data = $this->get_chunk($glyfOffset+$glyphPos,$glyphLen);
1405 - else $data = '';
1406 - }
1407 -
1408 - // mPDF 5.0
1409 - if ($glyphLen > 0) {
1410 - if (_RECALC_PROFILE) {
1411 - $xMin = $this->unpack_short(substr($data,2,2));
1412 - $yMin = $this->unpack_short(substr($data,4,2));
1413 - $xMax = $this->unpack_short(substr($data,6,2));
1414 - $yMax = $this->unpack_short(substr($data,8,2));
1415 - $xMinT = min($xMinT,$xMin);
1416 - $yMinT = min($yMinT,$yMin);
1417 - $xMaxT = max($xMaxT,$xMax);
1418 - $yMaxT = max($yMaxT,$yMax);
1419 - $aw = $this->unpack_short(substr($hm,0,2));
1420 - $lsb = $this->unpack_short(substr($hm,2,2));
1421 - $advanceWidthMax = max($advanceWidthMax,$aw);
1422 - $minLeftSideBearing = min($minLeftSideBearing,$lsb);
1423 - $minRightSideBearing = min($minRightSideBearing,($aw - $lsb - ($xMax - $xMin)));
1424 - $xMaxExtent = max($xMaxExtent,($lsb + ($xMax - $xMin)));
1425 - }
1426 - $up = unpack("n", substr($data,0,2));
1427 - }
1428 - if ($glyphLen > 2 && ($up[1] & (1 << 15)) ) { // If number of contours <= -1 i.e. composiste glyph
1429 - $pos_in_glyph = 10;
1430 - $flags = GF_MORE;
1431 - $nComponentElements = 0;
1432 - while ($flags & GF_MORE) {
1433 - $nComponentElements += 1; // number of glyphs referenced at top level
1434 - $up = unpack("n", substr($data,$pos_in_glyph,2));
1435 - $flags = $up[1];
1436 - $up = unpack("n", substr($data,$pos_in_glyph+2,2));
1437 - $glyphIdx = $up[1];
1438 - $this->glyphdata[$originalGlyphIdx]['compGlyphs'][] = $glyphIdx;
1439 - $data = $this->_set_ushort($data, $pos_in_glyph + 2, $glyphSet[$glyphIdx]);
1440 - $pos_in_glyph += 4;
1441 - if ($flags & GF_WORDS) { $pos_in_glyph += 4; }
1442 - else { $pos_in_glyph += 2; }
1443 - if ($flags & GF_SCALE) { $pos_in_glyph += 2; }
1444 - else if ($flags & GF_XYSCALE) { $pos_in_glyph += 4; }
1445 - else if ($flags & GF_TWOBYTWO) { $pos_in_glyph += 8; }
1446 - }
1447 - $maxComponentElements = max($maxComponentElements, $nComponentElements);
1448 - }
1449 - // mPDF 5.0 - Simple Glyph
1450 - else if (_RECALC_PROFILE && $glyphLen > 2 && $up[1] < (1 << 15) && $up[1] > 0) { // Number of contours > 0 simple glyph
1451 - $nContours = $up[1];
1452 - $this->glyphdata[$originalGlyphIdx]['nContours'] = $nContours;
1453 - $maxContours = max($maxContours, $nContours);
1454 -
1455 - // Count number of points in simple glyph
1456 - $pos_in_glyph = 10 + ($nContours * 2) - 2; // Last endContourPoint
1457 - $up = unpack("n", substr($data,$pos_in_glyph,2));
1458 - $points = $up[1]+1;
1459 - $this->glyphdata[$originalGlyphIdx]['nPoints'] = $points;
1460 - $maxPoints = max($maxPoints, $points);
1461 - }
1462 -
1463 - $glyf .= $data;
1464 - $pos += $glyphLen;
1465 - if ($pos % 4 != 0) {
1466 - $padding = 4 - ($pos % 4);
1467 - $glyf .= str_repeat("\0",$padding);
1468 - $pos += $padding;
1469 - }
1470 - }
1471 -
1472 - if (_RECALC_PROFILE) {
1473 - foreach($this->glyphdata AS $originalGlyphIdx => $val) {
1474 - $maxdepth = $depth = -1;
1475 - $points = 0;
1476 - $contours = 0;
1477 - $this->getGlyphData($originalGlyphIdx, $maxdepth, $depth, $points, $contours) ;
1478 - $maxComponentDepth = max($maxComponentDepth , $maxdepth);
1479 - $maxComponentPoints = max($maxComponentPoints , $points);
1480 - $maxComponentContours = max($maxComponentContours , $contours);
1481 - }
1482 - }
1483 -
1484 -
1485 - $offsets[] = $pos;
1486 - $this->add('glyf', $glyf);
1487 -
1488 - ///////////////////////////////////
1489 - // hmtx - Horizontal Metrics
1490 - ///////////////////////////////////
1491 - $this->add('hmtx', $hmtxstr);
1492 -
1493 -
1494 - ///////////////////////////////////
1495 - // loca - Index to location
1496 - ///////////////////////////////////
1497 - $locastr = '';
1498 - if ((($pos + 1) >> 1) > 0xFFFF) {
1499 - $indexToLocFormat = 1; // long format
1500 - foreach($offsets AS $offset) { $locastr .= pack("N",$offset); }
1501 - }
1502 - else {
1503 - $indexToLocFormat = 0; // short format
1504 - foreach($offsets AS $offset) { $locastr .= pack("n",($offset/2)); }
1505 - }
1506 - $this->add('loca', $locastr);
1507 -
1508 - ///////////////////////////////////
1509 - // head - Font header
1510 - ///////////////////////////////////
1511 - $head = $this->get_table('head');
1512 - $head = $this->_set_ushort($head, 50, $indexToLocFormat);
1513 - if (_RECALC_PROFILE) {
1514 - $head = $this->_set_short($head, 36, $xMinT); // for all glyph bounding boxes
1515 - $head = $this->_set_short($head, 38, $yMinT); // for all glyph bounding boxes
1516 - $head = $this->_set_short($head, 40, $xMaxT); // for all glyph bounding boxes
1517 - $head = $this->_set_short($head, 42, $yMaxT); // for all glyph bounding boxes
1518 - // mPDF 5.3.33
1519 - $head[17] = chr($head[17] & ~(1 << 4)); // Unset Bit 4 (as hdmx/LTSH tables not included)
1520 - }
1521 - $this->add('head', $head);
1522 -
1523 -
1524 - ///////////////////////////////////
1525 - // hhea - Horizontal Header
1526 - ///////////////////////////////////
1527 - $hhea = $this->get_table('hhea');
1528 - $hhea = $this->_set_ushort($hhea, 34, $numberOfHMetrics);
1529 - if (_RECALC_PROFILE) {
1530 - $hhea = $this->_set_ushort($hhea, 10, $advanceWidthMax);
1531 - $hhea = $this->_set_short($hhea, 12, $minLeftSideBearing);
1532 - $hhea = $this->_set_short($hhea, 14, $minRightSideBearing);
1533 - $hhea = $this->_set_short($hhea, 16, $xMaxExtent);
1534 - }
1535 - $this->add('hhea', $hhea);
1536 -
1537 - ///////////////////////////////////
1538 - // maxp - Maximum Profile
1539 - ///////////////////////////////////
1540 - $maxp = $this->get_table('maxp');
1541 - $maxp = $this->_set_ushort($maxp, 4, $numGlyphs);
1542 - if (_RECALC_PROFILE) {
1543 - $maxp = $this->_set_ushort($maxp, 6, $maxPoints); // points in non-compound glyph
1544 - $maxp = $this->_set_ushort($maxp, 8, $maxContours); // contours in non-compound glyph
1545 - $maxp = $this->_set_ushort($maxp, 10, $maxComponentPoints); // points in compound glyph
1546 - $maxp = $this->_set_ushort($maxp, 12, $maxComponentContours); // contours in compound glyph
1547 - $maxp = $this->_set_ushort($maxp, 28, $maxComponentElements); // number of glyphs referenced at top level
1548 - $maxp = $this->_set_ushort($maxp, 30, $maxComponentDepth); // levels of recursion, set to 0 if font has only simple glyphs
1549 - }
1550 - $this->add('maxp', $maxp);
1551 -
1552 -
1553 - ///////////////////////////////////
1554 - // OS/2 - OS/2
1555 - ///////////////////////////////////
1556 - if (isset($this->tables['OS/2'])) {
1557 - // mPDF 5.2.03
1558 - $os2_offset = $this->seek_table("OS/2");
1559 - // mPDF 5.0
1560 - if (_RECALC_PROFILE) {
1561 - $fsSelection = $this->get_ushort($os2_offset+62);
1562 - $fsSelection = ($fsSelection & ~(1 << 6)); // 2-byte bit field containing information concerning the nature of the font patterns
1563 - // bit#0 = Italic; bit#5=Bold
1564 - // Match name table's font subfamily string
1565 - // Clear bit#6 used for 'Regular' and optional
1566 - }
1567 -
1568 - // NB Currently this method never subsets characters above BMP
1569 - // Could set nonBMP bit according to $this->maxUni
1570 - $nonBMP = $this->get_ushort($os2_offset+46);
1571 - $nonBMP = ($nonBMP & ~(1 << 9)); // Unset Bit 57 (indicates non-BMP) - for interactive forms
1572 -
1573 - $os2 = $this->get_table('OS/2');
1574 - if (_RECALC_PROFILE) {
1575 - $os2 = $this->_set_ushort($os2, 62, $fsSelection);
1576 - // mPDF 5.2.03
1577 - $os2 = $this->_set_ushort($os2, 66, $fsLastCharIndex);
1578 - $os2 = $this->_set_ushort($os2, 42, 0x0000); // ulCharRange (ulUnicodeRange) bits 24-31 | 16-23
1579 - $os2 = $this->_set_ushort($os2, 44, 0x0000); // ulCharRange (Unicode ranges) bits 8-15 | 0-7
1580 - $os2 = $this->_set_ushort($os2, 46, $nonBMP); // ulCharRange (Unicode ranges) bits 56-63 | 48-55
1581 - $os2 = $this->_set_ushort($os2, 48, 0x0000); // ulCharRange (Unicode ranges) bits 40-47 | 32-39
1582 - $os2 = $this->_set_ushort($os2, 50, 0x0000); // ulCharRange (Unicode ranges) bits 88-95 | 80-87
1583 - $os2 = $this->_set_ushort($os2, 52, 0x0000); // ulCharRange (Unicode ranges) bits 72-79 | 64-71
1584 - $os2 = $this->_set_ushort($os2, 54, 0x0000); // ulCharRange (Unicode ranges) bits 120-127 | 112-119
1585 - $os2 = $this->_set_ushort($os2, 56, 0x0000); // ulCharRange (Unicode ranges) bits 104-111 | 96-103
1586 - }
1587 - $os2 = $this->_set_ushort($os2, 46, $nonBMP); // Unset Bit 57 (indicates non-BMP) - for interactive forms
1588 -
1589 - $this->add('OS/2', $os2 );
1590 - }
1591 -
1592 - fclose($this->fh);
1593 - // Put the TTF file together
1594 - $stm = '';
1595 - $this->endTTFile($stm);
1596 - //file_put_contents('testfont.ttf', $stm); exit;
1597 - return $stm ;
1598 - }
1599 -
1600 -//================================================================================
1601 -
1602 - // Also does SMP
1603 - function makeSubsetSIP($file, &$subset, $TTCfontID=0, $debug=false) {
1604 - $this->fh = fopen($file ,'rb') or die('Can\'t open file ' . $file);
1605 - $this->filename = $file;
1606 - $this->_pos = 0;
1607 - $this->charWidths = '';
1608 - $this->glyphPos = array();
1609 - $this->charToGlyph = array();
1610 - $this->tables = array();
1611 - $this->otables = array();
1612 - $this->ascent = 0;
1613 - $this->descent = 0;
1614 - $this->numTTCFonts = 0;
1615 - $this->TTCFonts = array();
1616 - $this->skip(4);
1617 - if ($TTCfontID > 0) {
1618 - $this->version = $version = $this->read_ulong(); // TTC Header version now
1619 - if (!in_array($version, array(0x00010000,0x00020000)))
1620 - die("ERROR - Error parsing TrueType Collection: version=".$version." - " . $file);
1621 - $this->numTTCFonts = $this->read_ulong();
1622 - for ($i=1; $i<=$this->numTTCFonts; $i++) {
1623 - $this->TTCFonts[$i]['offset'] = $this->read_ulong();
1624 - }
1625 - $this->seek($this->TTCFonts[$TTCfontID]['offset']);
1626 - $this->version = $version = $this->read_ulong(); // TTFont version again now
1627 - }
1628 - $this->readTableDirectory($debug);
1629 -
1630 -
1631 -
1632 - ///////////////////////////////////
1633 - // head - Font header table
1634 - ///////////////////////////////////
1635 - $this->seek_table("head");
1636 - $this->skip(50);
1637 - $indexToLocFormat = $this->read_ushort();
1638 - $glyphDataFormat = $this->read_ushort();
1639 -
1640 - ///////////////////////////////////
1641 - // hhea - Horizontal header table
1642 - ///////////////////////////////////
1643 - $this->seek_table("hhea");
1644 - $this->skip(32);
1645 - $metricDataFormat = $this->read_ushort();
1646 - $orignHmetrics = $numberOfHMetrics = $this->read_ushort();
1647 -
1648 - ///////////////////////////////////
1649 - // maxp - Maximum profile table
1650 - ///////////////////////////////////
1651 - $this->seek_table("maxp");
1652 - $this->skip(4);
1653 - $numGlyphs = $this->read_ushort();
1654 -
1655 -
1656 - ///////////////////////////////////
1657 - // cmap - Character to glyph index mapping table
1658 - ///////////////////////////////////
1659 -
1660 - $cmap_offset = $this->seek_table("cmap");
1661 - $this->skip(2);
1662 - $cmapTableCount = $this->read_ushort();
1663 - $unicode_cmap_offset = 0;
1664 - for ($i=0;$i<$cmapTableCount;$i++) {
1665 - $platformID = $this->read_ushort();
1666 - $encodingID = $this->read_ushort();
1667 - $offset = $this->read_ulong();
1668 - $save_pos = $this->_pos;
1669 - // mPDF 5.3.30
1670 - if (($platformID == 3 && $encodingID == 10) || $platformID == 0) { // Microsoft, Unicode Format 12 table HKCS
1671 - $format = $this->get_ushort($cmap_offset + $offset);
1672 - if ($format == 12) {
1673 - $unicode_cmap_offset = $cmap_offset + $offset;
1674 - break;
1675 - }
1676 - }
1677 - $this->seek($save_pos );
1678 - }
1679 -
1680 - if (!$unicode_cmap_offset)
1681 - die('Font does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
1682 - // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
1683 - if ($format == 12) {
1684 - $this->maxUniChar = 0;
1685 - $this->seek($unicode_cmap_offset + 4);
1686 - $length = $this->read_ulong();
1687 - $limit = $unicode_cmap_offset + $length;
1688 - $this->skip(4);
1689 -
1690 - $nGroups = $this->read_ulong();
1691 -
1692 - $glyphToChar = array();
1693 - $charToGlyph = array();
1694 - for($i=0; $i<$nGroups ; $i++) {
1695 - $startCharCode = $this->read_ulong();
1696 - $endCharCode = $this->read_ulong();
1697 - $startGlyphCode = $this->read_ulong();
1698 - $offset = 0;
1699 - for ($unichar=$startCharCode;$unichar<=$endCharCode;$unichar++) {
1700 - $glyph = $startGlyphCode + $offset ;
1701 - $offset++;
1702 - $charToGlyph[$unichar] = $glyph;
1703 - if ($unichar < 196608) { $this->maxUniChar = max($unichar,$this->maxUniChar); }
1704 - $glyphToChar[$glyph][] = $unichar;
1705 - }
1706 - }
1707 - }
1708 - else
1709 - die('Font does not have cmap for Unicode (format 12)');
1710 -
1711 -
1712 - ///////////////////////////////////
1713 - // hmtx - Horizontal metrics table
1714 - ///////////////////////////////////
1715 - $scale = 1; // not used here
1716 - $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
1717 -
1718 - ///////////////////////////////////
1719 - // loca - Index to location
1720 - ///////////////////////////////////
1721 - $this->getLOCA($indexToLocFormat, $numGlyphs);
1722 -
1723 - ///////////////////////////////////////////////////////////////////
1724 -
1725 - $glyphMap = array(0=>0);
1726 - $glyphSet = array(0=>0);
1727 - $codeToGlyph = array();
1728 - // mPDF 5.0.067 Set a substitute if ASCII characters do not have glyphs
1729 - if (isset($charToGlyph[0x3F])) { $subs = $charToGlyph[0x3F]; } // Question mark
1730 - else { $subs = $charToGlyph[32]; }
1731 - foreach($subset AS $code) {
1732 - if (isset($charToGlyph[$code]))
1733 - $originalGlyphIdx = $charToGlyph[$code];
1734 - else if ($code<128) { // mPDF 5.0.067
1735 - $originalGlyphIdx = $subs;
1736 - }
1737 - else { $originalGlyphIdx = 0; }
1738 - if (!isset($glyphSet[$originalGlyphIdx])) {
1739 - $glyphSet[$originalGlyphIdx] = count($glyphMap);
1740 - $glyphMap[] = $originalGlyphIdx;
1741 - }
1742 - $codeToGlyph[$code] = $glyphSet[$originalGlyphIdx];
1743 - }
1744 -
1745 - list($start,$dummy) = $this->get_table_pos('glyf');
1746 -
1747 - $n = 0;
1748 - while ($n < count($glyphMap)) {
1749 - $originalGlyphIdx = $glyphMap[$n];
1750 - $glyphPos = $this->glyphPos[$originalGlyphIdx];
1751 - $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
1752 - $n += 1;
1753 - if (!$glyphLen) continue;
1754 - $this->seek($start + $glyphPos);
1755 - $numberOfContours = $this->read_short();
1756 - if ($numberOfContours < 0) {
1757 - $this->skip(8);
1758 - $flags = GF_MORE;
1759 - while ($flags & GF_MORE) {
1760 - $flags = $this->read_ushort();
1761 - $glyphIdx = $this->read_ushort();
1762 - if (!isset($glyphSet[$glyphIdx])) {
1763 - $glyphSet[$glyphIdx] = count($glyphMap);
1764 - $glyphMap[] = $glyphIdx;
1765 - }
1766 - if ($flags & GF_WORDS)
1767 - $this->skip(4);
1768 - else
1769 - $this->skip(2);
1770 - if ($flags & GF_SCALE)
1771 - $this->skip(2);
1772 - else if ($flags & GF_XYSCALE)
1773 - $this->skip(4);
1774 - else if ($flags & GF_TWOBYTWO)
1775 - $this->skip(8);
1776 - }
1777 - }
1778 - }
1779 -
1780 - $numGlyphs = $n = count($glyphMap);
1781 - $numberOfHMetrics = $n;
1782 -
1783 - ///////////////////////////////////
1784 - // name
1785 - ///////////////////////////////////
1786 - // Needs to have a name entry in 3,0 (e.g. symbol) - original font will be 3,1 (i.e. Unicode)
1787 - $name = $this->get_table('name');
1788 - $name_offset = $this->seek_table("name");
1789 - $format = $this->read_ushort();
1790 - $numRecords = $this->read_ushort();
1791 - $string_data_offset = $name_offset + $this->read_ushort();
1792 - for ($i=0;$i<$numRecords; $i++) {
1793 - $platformId = $this->read_ushort();
1794 - $encodingId = $this->read_ushort();
1795 - if ($platformId == 3 && $encodingId == 1) {
1796 - $pos = 6 + ($i * 12) + 2;
1797 - $name = $this->_set_ushort($name, $pos, 0x00); // Change encoding to 3,0 rather than 3,1
1798 - }
1799 - $this->skip(8);
1800 - }
1801 - $this->add('name', $name);
1802 -
1803 - ///////////////////////////////////
1804 - // OS/2
1805 - ///////////////////////////////////
1806 - if (isset($this->tables['OS/2'])) {
1807 - $os2 = $this->get_table('OS/2');
1808 - $os2 = $this->_set_ushort($os2, 42, 0x00); // ulCharRange (Unicode ranges)
1809 - $os2 = $this->_set_ushort($os2, 44, 0x00); // ulCharRange (Unicode ranges)
1810 - $os2 = $this->_set_ushort($os2, 46, 0x00); // ulCharRange (Unicode ranges)
1811 - $os2 = $this->_set_ushort($os2, 48, 0x00); // ulCharRange (Unicode ranges)
1812 -
1813 - $os2 = $this->_set_ushort($os2, 50, 0x00); // ulCharRange (Unicode ranges)
1814 - $os2 = $this->_set_ushort($os2, 52, 0x00); // ulCharRange (Unicode ranges)
1815 - $os2 = $this->_set_ushort($os2, 54, 0x00); // ulCharRange (Unicode ranges)
1816 - $os2 = $this->_set_ushort($os2, 56, 0x00); // ulCharRange (Unicode ranges)
1817 - // Set Symbol character only in ulCodePageRange
1818 - $os2 = $this->_set_ushort($os2, 78, 0x8000); // ulCodePageRange = Bit #31 Symbol **** 78 = Bit 16-31
1819 - $os2 = $this->_set_ushort($os2, 80, 0x0000); // ulCodePageRange = Bit #31 Symbol **** 80 = Bit 0-15
1820 - $os2 = $this->_set_ushort($os2, 82, 0x0000); // ulCodePageRange = Bit #32- Symbol **** 82 = Bits 48-63
1821 - $os2 = $this->_set_ushort($os2, 84, 0x0000); // ulCodePageRange = Bit #32- Symbol **** 84 = Bits 32-47
1822 -
1823 - $os2 = $this->_set_ushort($os2, 64, 0x01); // FirstCharIndex
1824 - $os2 = $this->_set_ushort($os2, 66, count($subset)); // LastCharIndex
1825 - // Set PANOSE first bit to 5 for Symbol mPDF 5.0.017
1826 - $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));
1827 - $this->add('OS/2', $os2 );
1828 - }
1829 -
1830 -
1831 - ///////////////////////////////////
1832 - //tables copied from the original
1833 - ///////////////////////////////////
1834 - // mPDF 5.1.020 // mPDF 5.1.022
1835 - $tags = array ('cvt ', 'fpgm', 'prep', 'gasp');
1836 - foreach($tags AS $tag) { // 1.02
1837 - if (isset($this->tables[$tag])) { $this->add($tag, $this->get_table($tag)); }
1838 - }
1839 -
1840 - ///////////////////////////////////
1841 - // post - PostScript
1842 - ///////////////////////////////////
1843 - if (isset($this->tables['post'])) {
1844 - $opost = $this->get_table('post');
1845 - $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";
1846 - }
1847 - $this->add('post', $post);
1848 -
1849 - ///////////////////////////////////
1850 - // hhea - Horizontal Header
1851 - ///////////////////////////////////
1852 - $hhea = $this->get_table('hhea');
1853 - $hhea = $this->_set_ushort($hhea, 34, $numberOfHMetrics);
1854 - $this->add('hhea', $hhea);
1855 -
1856 - ///////////////////////////////////
1857 - // maxp - Maximum Profile
1858 - ///////////////////////////////////
1859 - $maxp = $this->get_table('maxp');
1860 - $maxp = $this->_set_ushort($maxp, 4, $numGlyphs);
1861 - $this->add('maxp', $maxp);
1862 -
1863 -
1864 - ///////////////////////////////////
1865 - // CMap table Formats [1,0,]6 and [3,0,]4
1866 - ///////////////////////////////////
1867 - ///////////////////////////////////
1868 - // Sort CID2GID map into segments of contiguous codes
1869 - ///////////////////////////////////
1870 - $rangeid = 0;
1871 - $range = array();
1872 - $prevcid = -2;
1873 - $prevglidx = -1;
1874 - // for each character
1875 - foreach ($subset as $cid => $code) {
1876 - $glidx = $codeToGlyph[$code];
1877 - if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) {
1878 - $range[$rangeid][] = $glidx;
1879 - } else {
1880 - // new range
1881 - $rangeid = $cid;
1882 - $range[$rangeid] = array();
1883 - $range[$rangeid][] = $glidx;
1884 - }
1885 - $prevcid = $cid;
1886 - $prevglidx = $glidx;
1887 - }
1888 - // cmap - Character to glyph mapping
1889 - $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF
1890 - $searchRange = 1;
1891 - $entrySelector = 0;
1892 - while ($searchRange * 2 <= $segCount ) {
1893 - $searchRange = $searchRange * 2;
1894 - $entrySelector = $entrySelector + 1;
1895 - }
1896 - $searchRange = $searchRange * 2;
1897 - $rangeShift = $segCount * 2 - $searchRange;
1898 - $length = 16 + (8*$segCount ) + ($numGlyphs+1);
1899 - $cmap = array(
1900 - 4, $length, 0, // Format 4 Mapping subtable: format, length, language
1901 - $segCount*2,
1902 - $searchRange,
1903 - $entrySelector,
1904 - $rangeShift);
1905 -
1906 - // endCode(s)
1907 - foreach($range AS $start=>$subrange) {
1908 - $endCode = $start + (count($subrange)-1);
1909 - $cmap[] = $endCode; // endCode(s)
1910 - }
1911 - $cmap[] = 0xFFFF; // endCode of last Segment
1912 - $cmap[] = 0; // reservedPad
1913 -
1914 - // startCode(s)
1915 - foreach($range AS $start=>$subrange) {
1916 - $cmap[] = $start; // startCode(s)
1917 - }
1918 - $cmap[] = 0xFFFF; // startCode of last Segment
1919 - // idDelta(s)
1920 - foreach($range AS $start=>$subrange) {
1921 - $idDelta = -($start-$subrange[0]);
1922 - $n += count($subrange);
1923 - $cmap[] = $idDelta; // idDelta(s)
1924 - }
1925 - $cmap[] = 1; // idDelta of last Segment
1926 - // idRangeOffset(s)
1927 - foreach($range AS $subrange) {
1928 - $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
1929 -
1930 - }
1931 - $cmap[] = 0; // idRangeOffset of last Segment
1932 - foreach($range AS $subrange) {
1933 - foreach($subrange AS $glidx) {
1934 - $cmap[] = $glidx;
1935 - }
1936 - }
1937 - $cmap[] = 0; // Mapping for last character
1938 - $cmapstr4 = '';
1939 - foreach($cmap AS $cm) { $cmapstr4 .= pack("n",$cm); }
1940 -
1941 - ///////////////////////////////////
1942 - // cmap - Character to glyph mapping
1943 - ///////////////////////////////////
1944 - $entryCount = count($subset);
1945 - $length = 10 + $entryCount * 2;
1946 -
1947 - $off = 20 + $length;
1948 - $hoff = $off >> 16;
1949 - $loff = $off & 0xFFFF;
1950 -
1951 - $cmap = array(0, 2, // Index : version, number of subtables
1952 - 1, 0, // Subtable : platform, encoding
1953 - 0, 20, // offset (hi,lo)
1954 - 3, 0, // Subtable : platform, encoding
1955 - $hoff, $loff, // offset (hi,lo)
1956 - 6, $length, // Format 6 Mapping table: format, length
1957 - 0, 1, // language, First char code
1958 - $entryCount
1959 - );
1960 - $cmapstr = '';
1961 - foreach($subset AS $code) { $cmap[] = $codeToGlyph[$code]; }
1962 - foreach($cmap AS $cm) { $cmapstr .= pack("n",$cm); }
1963 - $cmapstr .= $cmapstr4;
1964 - $this->add('cmap', $cmapstr);
1965 -
1966 - ///////////////////////////////////
1967 - // hmtx - Horizontal Metrics
1968 - ///////////////////////////////////
1969 - $hmtxstr = '';
1970 - for($n=0;$n<$numGlyphs;$n++) {
1971 - $originalGlyphIdx = $glyphMap[$n];
1972 - $hm = $this->getHMetric($orignHmetrics, $originalGlyphIdx);
1973 - $hmtxstr .= $hm;
1974 - }
1975 - $this->add('hmtx', $hmtxstr);
1976 -
1977 - ///////////////////////////////////
1978 - // glyf - Glyph data
1979 - ///////////////////////////////////
1980 - list($glyfOffset,$glyfLength) = $this->get_table_pos('glyf');
1981 - if ($glyfLength < $this->maxStrLenRead) {
1982 - $glyphData = $this->get_table('glyf');
1983 - }
1984 -
1985 - $offsets = array();
1986 - $glyf = '';
1987 - $pos = 0;
1988 - for ($n=0;$n<$numGlyphs;$n++) {
1989 - $offsets[] = $pos;
1990 - $originalGlyphIdx = $glyphMap[$n];
1991 - $glyphPos = $this->glyphPos[$originalGlyphIdx];
1992 - $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
1993 - if ($glyfLength < $this->maxStrLenRead) {
1994 - $data = substr($glyphData,$glyphPos,$glyphLen);
1995 - }
1996 - else {
1997 - if ($glyphLen > 0) $data = $this->get_chunk($glyfOffset+$glyphPos,$glyphLen);
1998 - else $data = '';
1999 - }
2000 - if ($glyphLen > 0) $up = unpack("n", substr($data,0,2));
2001 - if ($glyphLen > 2 && ($up[1] & (1 << 15)) ) {
2002 - $pos_in_glyph = 10;
2003 - $flags = GF_MORE;
2004 - while ($flags & GF_MORE) {
2005 - $up = unpack("n", substr($data,$pos_in_glyph,2));
2006 - $flags = $up[1];
2007 - $up = unpack("n", substr($data,$pos_in_glyph+2,2));
2008 - $glyphIdx = $up[1];
2009 - $data = $this->_set_ushort($data, $pos_in_glyph + 2, $glyphSet[$glyphIdx]);
2010 - $pos_in_glyph += 4;
2011 - if ($flags & GF_WORDS) { $pos_in_glyph += 4; }
2012 - else { $pos_in_glyph += 2; }
2013 - if ($flags & GF_SCALE) { $pos_in_glyph += 2; }
2014 - else if ($flags & GF_XYSCALE) { $pos_in_glyph += 4; }
2015 - else if ($flags & GF_TWOBYTWO) { $pos_in_glyph += 8; }
2016 - }
2017 - }
2018 - $glyf .= $data;
2019 - $pos += $glyphLen;
2020 - if ($pos % 4 != 0) {
2021 - $padding = 4 - ($pos % 4);
2022 - $glyf .= str_repeat("\0",$padding);
2023 - $pos += $padding;
2024 - }
2025 - }
2026 - $offsets[] = $pos;
2027 - $this->add('glyf', $glyf);
2028 -
2029 - ///////////////////////////////////
2030 - // loca - Index to location
2031 - ///////////////////////////////////
2032 - $locastr = '';
2033 - if ((($pos + 1) >> 1) > 0xFFFF) {
2034 - $indexToLocFormat = 1; // long format
2035 - foreach($offsets AS $offset) { $locastr .= pack("N",$offset); }
2036 - }
2037 - else {
2038 - $indexToLocFormat = 0; // short format
2039 - foreach($offsets AS $offset) { $locastr .= pack("n",($offset/2)); }
2040 - }
2041 - $this->add('loca', $locastr);
2042 -
2043 - ///////////////////////////////////
2044 - // head - Font header
2045 - ///////////////////////////////////
2046 - $head = $this->get_table('head');
2047 - $head = $this->_set_ushort($head, 50, $indexToLocFormat);
2048 - $this->add('head', $head);
2049 -
2050 - fclose($this->fh);
2051 -
2052 - // Put the TTF file together
2053 - $stm = '';
2054 - $this->endTTFile($stm);
2055 - //file_put_contents('testfont.ttf', $stm); exit;
2056 - return $stm ;
2057 - }
2058 -
2059 - //////////////////////////////////////////////////////////////////////////////////
2060 - // Recursively get composite glyph data
2061 - function getGlyphData($originalGlyphIdx, &$maxdepth, &$depth, &$points, &$contours) {
2062 - $depth++;
2063 - $maxdepth = max($maxdepth, $depth);
2064 - if (count($this->glyphdata[$originalGlyphIdx]['compGlyphs'])) {
2065 - foreach($this->glyphdata[$originalGlyphIdx]['compGlyphs'] AS $glyphIdx) {
2066 - $this->getGlyphData($glyphIdx, $maxdepth, $depth, $points, $contours);
2067 - }
2068 - }
2069 - else if (($this->glyphdata[$originalGlyphIdx]['nContours'] > 0) && $depth > 0) { // simple
2070 - $contours += $this->glyphdata[$originalGlyphIdx]['nContours'];
2071 - $points += $this->glyphdata[$originalGlyphIdx]['nPoints'];
2072 - }
2073 - $depth--;
2074 - }
2075 -
2076 -
2077 - //////////////////////////////////////////////////////////////////////////////////
2078 - // Recursively get composite glyphs
2079 - function getGlyphs($originalGlyphIdx, &$start, &$glyphSet, &$subsetglyphs) {
2080 - $glyphPos = $this->glyphPos[$originalGlyphIdx];
2081 - $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
2082 - if (!$glyphLen) {
2083 - return;
2084 - }
2085 - $this->seek($start + $glyphPos);
2086 - $numberOfContours = $this->read_short();
2087 - if ($numberOfContours < 0) {
2088 - $this->skip(8);
2089 - $flags = GF_MORE;
2090 - while ($flags & GF_MORE) {
2091 - $flags = $this->read_ushort();
2092 - $glyphIdx = $this->read_ushort();
2093 - if (!isset($glyphSet[$glyphIdx])) {
2094 - $glyphSet[$glyphIdx] = count($subsetglyphs); // old glyphID to new glyphID
2095 - $subsetglyphs[$glyphIdx] = true;
2096 - }
2097 - $savepos = ftell($this->fh);
2098 - $this->getGlyphs($glyphIdx, $start, $glyphSet, $subsetglyphs);
2099 - $this->seek($savepos);
2100 - if ($flags & GF_WORDS)
2101 - $this->skip(4);
2102 - else
2103 - $this->skip(2);
2104 - if ($flags & GF_SCALE)
2105 - $this->skip(2);
2106 - else if ($flags & GF_XYSCALE)
2107 - $this->skip(4);
2108 - else if ($flags & GF_TWOBYTWO)
2109 - $this->skip(8);
2110 - }
2111 - }
2112 - }
2113 -
2114 - //////////////////////////////////////////////////////////////////////////////////
2115 -
2116 - function getHMTX($numberOfHMetrics, $numGlyphs, &$glyphToChar, $scale) {
2117 - $start = $this->seek_table("hmtx");
2118 - $aw = 0;
2119 - $this->charWidths = str_pad('', 256*256*2, "\x00");
2120 - if ($this->maxUniChar > 65536) { $this->charWidths .= str_pad('', 256*256*2, "\x00"); } // Plane 1 SMP
2121 - if ($this->maxUniChar > 131072) { $this->charWidths .= str_pad('', 256*256*2, "\x00"); } // Plane 2 SMP
2122 - $nCharWidths = 0;
2123 - if (($numberOfHMetrics*4) < $this->maxStrLenRead) {
2124 - $data = $this->get_chunk($start,($numberOfHMetrics*4));
2125 - $arr = unpack("n*", $data);
2126 - }
2127 - else { $this->seek($start); }
2128 - for( $glyph=0; $glyph<$numberOfHMetrics; $glyph++) {
2129 - if (($numberOfHMetrics*4) < $this->maxStrLenRead) {
2130 - $aw = $arr[($glyph*2)+1];
2131 - }
2132 - else {
2133 - $aw = $this->read_ushort();
2134 - $lsb = $this->read_ushort();
2135 - }
2136 - if (isset($glyphToChar[$glyph]) || $glyph == 0) {
2137 -
2138 - if ($aw >= (1 << 15) ) { $aw = 0; } // 1.03 Some (arabic) fonts have -ve values for width
2139 - // although should be unsigned value - comes out as e.g. 65108 (intended -50)
2140 - if ($glyph == 0) {
2141 - $this->defaultWidth = $scale*$aw;
2142 - continue;
2143 - }
2144 - foreach($glyphToChar[$glyph] AS $char) {
2145 - //$this->charWidths[$char] = intval(round($scale*$aw));
2146 - if ($char != 0 && $char != 65535) {
2147 - $w = intval(round($scale*$aw));
2148 - if ($w == 0) { $w = 65535; }
2149 - if ($char < 196608) {
2150 - $this->charWidths[$char*2] = chr($w >> 8);
2151 - $this->charWidths[$char*2 + 1] = chr($w & 0xFF);
2152 - $nCharWidths++;
2153 - }
2154 - }
2155 - }
2156 - }
2157 - }
2158 - $data = $this->get_chunk(($start+$numberOfHMetrics*4),($numGlyphs*2));
2159 - $arr = unpack("n*", $data);
2160 - $diff = $numGlyphs-$numberOfHMetrics;
2161 - $w = intval(round($scale*$aw)); // mPDF 5.3.32
2162 - if ($w == 0) { $w = 65535; } // mPDF 5.3.32
2163 - for( $pos=0; $pos<$diff; $pos++) {
2164 - $glyph = $pos + $numberOfHMetrics;
2165 - if (isset($glyphToChar[$glyph])) {
2166 - foreach($glyphToChar[$glyph] AS $char) {
2167 - if ($char != 0 && $char != 65535) {
2168 - if ($char < 196608) {
2169 - $this->charWidths[$char*2] = chr($w >> 8);
2170 - $this->charWidths[$char*2 + 1] = chr($w & 0xFF);
2171 - $nCharWidths++;
2172 - }
2173 - }
2174 - }
2175 - }
2176 - }
2177 - // NB 65535 is a set width of 0
2178 - // First bytes define number of chars in font
2179 - $this->charWidths[0] = chr($nCharWidths >> 8);
2180 - $this->charWidths[1] = chr($nCharWidths & 0xFF);
2181 - }
2182 -
2183 - function getHMetric($numberOfHMetrics, $gid) {
2184 - $start = $this->seek_table("hmtx");
2185 - if ($gid < $numberOfHMetrics) {
2186 - $this->seek($start+($gid*4));
2187 - $hm = fread($this->fh,4);
2188 - }
2189 - else {
2190 - $this->seek($start+(($numberOfHMetrics-1)*4));
2191 - $hm = fread($this->fh,2);
2192 - $this->seek($start+($numberOfHMetrics*2)+($gid*2));
2193 - $hm .= fread($this->fh,2);
2194 - }
2195 - return $hm;
2196 - }
2197 -
2198 - function getLOCA($indexToLocFormat, $numGlyphs) {
2199 - $start = $this->seek_table('loca');
2200 - $this->glyphPos = array();
2201 - if ($indexToLocFormat == 0) {
2202 - $data = $this->get_chunk($start,($numGlyphs*2)+2);
2203 - $arr = unpack("n*", $data);
2204 - for ($n=0; $n<=$numGlyphs; $n++) {
2205 - $this->glyphPos[] = ($arr[$n+1] * 2);
2206 - }
2207 - }
2208 - else if ($indexToLocFormat == 1) {
2209 - $data = $this->get_chunk($start,($numGlyphs*4)+4);
2210 - $arr = unpack("N*", $data);
2211 - for ($n=0; $n<=$numGlyphs; $n++) {
2212 - $this->glyphPos[] = ($arr[$n+1]);
2213 - }
2214 - }
2215 - else
2216 - die('Unknown location table format '.$indexToLocFormat);
2217 - }
2218 -
2219 -
2220 - // CMAP Format 4
2221 - function getCMAP4($unicode_cmap_offset, &$glyphToChar, &$charToGlyph ) {
2222 - $this->maxUniChar = 0;
2223 - $this->seek($unicode_cmap_offset + 2);
2224 - $length = $this->read_ushort();
2225 - $limit = $unicode_cmap_offset + $length;
2226 - $this->skip(2);
2227 -
2228 - $segCount = $this->read_ushort() / 2;
2229 - $this->skip(6);
2230 - $endCount = array();
2231 - for($i=0; $i<$segCount; $i++) { $endCount[] = $this->read_ushort(); }
2232 - $this->skip(2);
2233 - $startCount = array();
2234 - for($i=0; $i<$segCount; $i++) { $startCount[] = $this->read_ushort(); }
2235 - $idDelta = array();
2236 - for($i=0; $i<$segCount; $i++) { $idDelta[] = $this->read_short(); } // ???? was unsigned short
2237 - $idRangeOffset_start = $this->_pos;
2238 - $idRangeOffset = array();
2239 - for($i=0; $i<$segCount; $i++) { $idRangeOffset[] = $this->read_ushort(); }
2240 -
2241 - for ($n=0;$n<$segCount;$n++) {
2242 - $endpoint = ($endCount[$n] + 1);
2243 - for ($unichar=$startCount[$n];$unichar<$endpoint;$unichar++) {
2244 - if ($idRangeOffset[$n] == 0)
2245 - $glyph = ($unichar + $idDelta[$n]) & 0xFFFF;
2246 - else {
2247 - $offset = ($unichar - $startCount[$n]) * 2 + $idRangeOffset[$n];
2248 - $offset = $idRangeOffset_start + 2 * $n + $offset;
2249 - if ($offset >= $limit)
2250 - $glyph = 0;
2251 - else {
2252 - $glyph = $this->get_ushort($offset);
2253 - if ($glyph != 0)
2254 - $glyph = ($glyph + $idDelta[$n]) & 0xFFFF;
2255 - }
2256 - }
2257 - $charToGlyph[$unichar] = $glyph;
2258 - if ($unichar < 196608) { $this->maxUniChar = max($unichar,$this->maxUniChar); }
2259 - $glyphToChar[$glyph][] = $unichar;
2260 - }
2261 - }
2262 - }
2263 -
2264 -
2265 - // Put the TTF file together
2266 - function endTTFile(&$stm) {
2267 - $stm = '';
2268 - $numTables = count($this->otables);
2269 - $searchRange = 1;
2270 - $entrySelector = 0;
2271 - while ($searchRange * 2 <= $numTables) {
2272 - $searchRange = $searchRange * 2;
2273 - $entrySelector = $entrySelector + 1;
2274 - }
2275 - $searchRange = $searchRange * 16;
2276 - $rangeShift = $numTables * 16 - $searchRange;
2277 -
2278 - // Header
2279 - if (_TTF_MAC_HEADER) {
2280 - $stm .= (pack("Nnnnn", 0x74727565, $numTables, $searchRange, $entrySelector, $rangeShift)); // Mac
2281 - }
2282 - else {
2283 - $stm .= (pack("Nnnnn", 0x00010000 , $numTables, $searchRange, $entrySelector, $rangeShift)); // Windows
2284 - }
2285 -
2286 - // Table directory
2287 - $tables = $this->otables;
2288 - ksort ($tables);
2289 - $offset = 12 + $numTables * 16;
2290 - foreach ($tables AS $tag=>$data) {
2291 - if ($tag == 'head') { $head_start = $offset; }
2292 - $stm .= $tag;
2293 - $checksum = $this->calcChecksum($data);
2294 - $stm .= pack("nn", $checksum[0],$checksum[1]);
2295 - $stm .= pack("NN", $offset, strlen($data));
2296 - $paddedLength = (strlen($data)+3)&~3;
2297 - $offset = $offset + $paddedLength;
2298 - }
2299 -
2300 - // Table data
2301 - foreach ($tables AS $tag=>$data) {
2302 - $data .= "\0\0\0";
2303 - $stm .= substr($data,0,(strlen($data)&~3));
2304 - }
2305 -
2306 - $checksum = $this->calcChecksum($stm);
2307 - $checksum = $this->sub32(array(0xB1B0,0xAFBA), $checksum);
2308 - $chk = pack("nn", $checksum[0],$checksum[1]);
2309 - $stm = $this->splice($stm,($head_start + 8),$chk);
2310 - return $stm ;
2311 - }
2312 -
2313 -
2314 - function repackageTTF($file, $TTCfontID=0, $debug=false) {
2315 - $this->filename = $file;
2316 - $this->fh = fopen($file ,'rb') or die('Can\'t open file ' . $file);
2317 - $this->_pos = 0;
2318 - $this->charWidths = '';
2319 - $this->glyphPos = array();
2320 - $this->charToGlyph = array();
2321 - $this->tables = array();
2322 - $this->otables = array();
2323 - $this->ascent = 0;
2324 - $this->descent = 0;
2325 - $this->numTTCFonts = 0;
2326 - $this->TTCFonts = array();
2327 - $this->skip(4);
2328 - $this->maxUni = 0;
2329 - if ($TTCfontID > 0) {
2330 - $this->version = $version = $this->read_ulong(); // TTC Header version now
2331 - if (!in_array($version, array(0x00010000,0x00020000)))
2332 - die("ERROR - Error parsing TrueType Collection: version=".$version." - " . $file);
2333 - $this->numTTCFonts = $this->read_ulong();
2334 - for ($i=1; $i<=$this->numTTCFonts; $i++) {
2335 - $this->TTCFonts[$i]['offset'] = $this->read_ulong();
2336 - }
2337 - $this->seek($this->TTCFonts[$TTCfontID]['offset']);
2338 - $this->version = $version = $this->read_ulong(); // TTFont version again now
2339 - }
2340 - $this->readTableDirectory($debug);
2341 - $tags = array ('OS/2', 'cmap', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'name', 'post', 'cvt ', 'fpgm', 'gasp', 'prep');
2342 -/*
2343 -Tables which require glyphIndex
2344 -hdmx
2345 -kern
2346 -LTSH
2347 -
2348 -Tables which do NOT require glyphIndex
2349 -VDMX
2350 -
2351 -GDEF
2352 -GPOS
2353 -GSUB
2354 -JSTF
2355 -
2356 -DSIG
2357 -PCLT - not recommended
2358 -*/
2359 -
2360 - foreach($tags AS $tag) {
2361 - if (isset($this->tables[$tag])) { $this->add($tag, $this->get_table($tag)); }
2362 - }
2363 - fclose($this->fh);
2364 - $stm = '';
2365 - $this->endTTFile($stm);
2366 - return $stm ;
2367 - }
2368 -
2369 -
2370 -}
2371 -
2372 -
2373 -?>
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 +}