aboutsummaryrefslogtreecommitdiffhomepage
path: root/inc/3rdparty/libraries/mpdf/classes/ttfontsuni.php
diff options
context:
space:
mode:
Diffstat (limited to 'inc/3rdparty/libraries/mpdf/classes/ttfontsuni.php')
-rw-r--r--inc/3rdparty/libraries/mpdf/classes/ttfontsuni.php2065
1 files changed, 2065 insertions, 0 deletions
diff --git a/inc/3rdparty/libraries/mpdf/classes/ttfontsuni.php b/inc/3rdparty/libraries/mpdf/classes/ttfontsuni.php
new file mode 100644
index 00000000..1c93b600
--- /dev/null
+++ b/inc/3rdparty/libraries/mpdf/classes/ttfontsuni.php
@@ -0,0 +1,2065 @@
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(!)
24if (!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
28if (!defined('_RECALC_PROFILE')) define("_RECALC_PROFILE", false);
29
30// TrueType Font Glyph operators
31define("GF_WORDS",(1 << 0));
32define("GF_SCALE",(1 << 3));
33define("GF_MORE",(1 << 5));
34define("GF_XYSCALE",(1 << 6));
35define("GF_TWOBYTWO",(1 << 7));
36
37
38
39class TTFontFile {
40
41var $unAGlyphs; // mPDF 5.4.05
42var $panose;
43var $maxUni;
44var $sFamilyClass;
45var $sFamilySubClass;
46var $sipset;
47var $smpset;
48var $_pos;
49var $numTables;
50var $searchRange;
51var $entrySelector;
52var $rangeShift;
53var $tables;
54var $otables;
55var $filename;
56var $fh;
57var $glyphPos;
58var $charToGlyph;
59var $ascent;
60var $descent;
61var $name;
62var $familyName;
63var $styleName;
64var $fullName;
65var $uniqueFontID;
66var $unitsPerEm;
67var $bbox;
68var $capHeight;
69var $stemV;
70var $italicAngle;
71var $flags;
72var $underlinePosition;
73var $underlineThickness;
74var $charWidths;
75var $defaultWidth;
76var $maxStrLenRead;
77var $numTTCFonts;
78var $TTCFonts;
79var $maxUniChar;
80var $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/*
2035Tables which require glyphIndex
2036hdmx
2037kern
2038LTSH
2039
2040Tables which do NOT require glyphIndex
2041VDMX
2042
2043GDEF
2044GPOS
2045GSUB
2046JSTF
2047
2048DSIG
2049PCLT - 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?> \ No newline at end of file