Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

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