]> git.immae.eu Git - github/wallabag/wallabag.git/blob - inc/3rdparty/libraries/mpdf/classes/ttfontsuni.php
add pdf and mobi libraries
[github/wallabag/wallabag.git] / inc / 3rdparty / libraries / mpdf / classes / ttfontsuni.php
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 ?>