noalyss Version-9
ttfonts.php
Go to the documentation of this file.
1<?php
2
3
4/*******************************************************************************
5* TTFontFile class *
6* *
7* This class is based on The ReportLab Open Source PDF library *
8* written in Python - http://www.reportlab.com/software/opensource/ *
9* together with ideas from the OpenOffice source code and others. *
10* *
11* Version: 1.04 *
12* Date: 2011-09-18 *
13* Author: Ian Back <ianb@bpm1.com> *
14* License: LGPL *
15* Copyright (c) Ian Back, 2010 *
16* This header must be retained in any redistribution or *
17* modification of the file. *
18* *
19*******************************************************************************/
20
21// Define the value used in the "head" table of a created TTF file
22// 0x74727565 "true" for Mac
23// 0x00010000 for Windows
24// Either seems to work for a font embedded in a PDF file
25// when read by Adobe Reader on a Windows PC(!)
26define("_TTF_MAC_HEADER", false);
27
28
29// TrueType Font Glyph operators
30define("GF_WORDS",(1 << 0));
31define("GF_SCALE",(1 << 3));
32define("GF_MORE",(1 << 5));
33define("GF_XYSCALE",(1 << 6));
34define("GF_TWOBYTWO",(1 << 7));
35
36
37#[AllowDynamicProperties]
39
49var $fh;
71
72 function __construct() {
73 $this->maxStrLenRead = 200000; // Maximum size of glyf table to read in as string (otherwise reads each glyph from file)
74 }
75
76
77 function getMetrics($file) {
78 $this->filename = $file;
79 $this->fh = fopen($file,'rb') or die('Can\'t open file ' . $file);
80 $this->_pos = 0;
81 $this->charWidths = '';
82 $this->glyphPos = array();
83 $this->charToGlyph = array();
84 $this->tables = array();
85 $this->otables = array();
86 $this->ascent = 0;
87 $this->descent = 0;
88 $this->TTCFonts = array();
89 $this->version = $version = $this->read_ulong();
90 if ($version==0x4F54544F)
91 die("Postscript outlines are not supported");
92 if ($version==0x74746366)
93 die("ERROR - TrueType Fonts Collections not supported");
94 if (!in_array($version, array(0x00010000,0x74727565)))
95 die("Not a TrueType font: version=".$version);
96 $this->readTableDirectory();
97 $this->extractInfo();
98 fclose($this->fh);
99 }
100
101
103 $this->numTables = $this->read_ushort();
104 $this->searchRange = $this->read_ushort();
105 $this->entrySelector = $this->read_ushort();
106 $this->rangeShift = $this->read_ushort();
107 $this->tables = array();
108 for ($i=0;$i<$this->numTables;$i++) {
109 $record = array();
110 $record['tag'] = $this->read_tag();
111 $record['checksum'] = array($this->read_ushort(),$this->read_ushort());
112 $record['offset'] = $this->read_ulong();
113 $record['length'] = $this->read_ulong();
114 $this->tables[$record['tag']] = $record;
115 }
116 }
117
118
119 function sub32($x, $y) {
120 $xlo = $x[1];
121 $xhi = $x[0];
122 $ylo = $y[1];
123 $yhi = $y[0];
124 if ($ylo > $xlo) { $xlo += 1 << 16; $yhi += 1; }
125 $reslo = $xlo-$ylo;
126 if ($yhi > $xhi) { $xhi += 1 << 16; }
127 $reshi = $xhi-$yhi;
128 $reshi = $reshi & 0xFFFF;
129 return array($reshi, $reslo);
130 }
131
132 function calcChecksum($data) {
133 if (strlen($data) % 4) { $data .= str_repeat("\0",(4-(strlen($data) % 4))); }
134 $hi=0x0000;
135 $lo=0x0000;
136 for($i=0;$i<strlen($data);$i+=4) {
137 $hi += (ord($data[$i])<<8) + ord($data[$i+1]);
138 $lo += (ord($data[$i+2])<<8) + ord($data[$i+3]);
139 $hi += $lo >> 16;
140 $lo = $lo & 0xFFFF;
141 $hi = $hi & 0xFFFF;
142 }
143 return array($hi, $lo);
144 }
145
146 function get_table_pos($tag) {
147 $offset = $this->tables[$tag]['offset'];
148 $length = $this->tables[$tag]['length'];
149 return array($offset, $length);
150 }
151
152 function seek($pos) {
153 $this->_pos = $pos;
154 fseek($this->fh,$this->_pos);
155 }
156
157 function skip($delta) {
158 $this->_pos = $this->_pos + $delta;
159 fseek($this->fh,$this->_pos);
160 }
161
162 function seek_table($tag, $offset_in_table = 0) {
163 $tpos = $this->get_table_pos($tag);
164 $this->_pos = $tpos[0] + $offset_in_table;
165 fseek($this->fh, $this->_pos);
166 return $this->_pos;
167 }
168
169 function read_tag() {
170 $this->_pos += 4;
171 return fread($this->fh,4);
172 }
173
174 function read_short() {
175 $this->_pos += 2;
176 $s = fread($this->fh,2);
177 $a = (ord($s[0])<<8) + ord($s[1]);
178 if ($a & (1 << 15) ) { $a = ($a - (1 << 16)) ; }
179 return $a;
180 }
181
182 function unpack_short($s) {
183 $a = (ord($s[0])<<8) + ord($s[1]);
184 if ($a & (1 << 15) ) {
185 $a = ($a - (1 << 16));
186 }
187 return $a;
188 }
189
190 function read_ushort() {
191 $this->_pos += 2;
192 $s = fread($this->fh,2);
193 return (ord($s[0])<<8) + ord($s[1]);
194 }
195
196 function read_ulong() {
197 $this->_pos += 4;
198 $s = fread($this->fh,4);
199 // if large uInt32 as an integer, PHP converts it to -ve
200 return (ord($s[0])*16777216) + (ord($s[1])<<16) + (ord($s[2])<<8) + ord($s[3]); // 16777216 = 1<<24
201 }
202
203 function get_ushort($pos) {
204 fseek($this->fh,$pos);
205 $s = fread($this->fh,2);
206 return (ord($s[0])<<8) + ord($s[1]);
207 }
208
209 function get_ulong($pos) {
210 fseek($this->fh,$pos);
211 $s = fread($this->fh,4);
212 // iF large uInt32 as an integer, PHP converts it to -ve
213 return (ord($s[0])*16777216) + (ord($s[1])<<16) + (ord($s[2])<<8) + ord($s[3]); // 16777216 = 1<<24
214 }
215
216 function pack_short($val) {
217 if ($val<0) {
218 $val = abs($val);
219 $val = ~$val;
220 $val += 1;
221 }
222 return pack("n",$val);
223 }
224
225 function splice($stream, $offset, $value) {
226 return substr($stream,0,$offset) . $value . substr($stream,$offset+strlen($value));
227 }
228
229 function _set_ushort($stream, $offset, $value) {
230 $up = pack("n", $value);
231 return $this->splice($stream, $offset, $up);
232 }
233
234 function _set_short($stream, $offset, $val) {
235 if ($val<0) {
236 $val = abs($val);
237 $val = ~$val;
238 $val += 1;
239 }
240 $up = pack("n",$val);
241 return $this->splice($stream, $offset, $up);
242 }
243
244 function get_chunk($pos, $length) {
245 fseek($this->fh,$pos);
246 if ($length <1) { return ''; }
247 return (fread($this->fh,$length));
248 }
249
250 function get_table($tag) {
251 list($pos, $length) = $this->get_table_pos($tag);
252 if ($length == 0) { die('Truetype font ('.$this->filename.'): error reading table: '.$tag); }
253 fseek($this->fh,$pos);
254 return (fread($this->fh,$length));
255 }
256
257 function add($tag, $data) {
258 if ($tag == 'head') {
259 $data = $this->splice($data, 8, "\0\0\0\0");
260 }
261 $this->otables[$tag] = $data;
262 }
263
264
265
266/////////////////////////////////////////////////////////////////////////////////////////
267/////////////////////////////////////////////////////////////////////////////////////////
268
269/////////////////////////////////////////////////////////////////////////////////////////
270
271 function extractInfo() {
272 ///////////////////////////////////
273 // name - Naming table
274 ///////////////////////////////////
275 $this->sFamilyClass = 0;
276 $this->sFamilySubClass = 0;
277
278 $name_offset = $this->seek_table("name");
279 $format = $this->read_ushort();
280 if ($format != 0)
281 die("Unknown name table format ".$format);
282 $numRecords = $this->read_ushort();
283 $string_data_offset = $name_offset + $this->read_ushort();
284 $names = array(1=>'',2=>'',3=>'',4=>'',6=>'');
285 $K = array_keys($names);
286 $nameCount = count($names);
287 for ($i=0;$i<$numRecords; $i++) {
288 $platformId = $this->read_ushort();
289 $encodingId = $this->read_ushort();
290 $languageId = $this->read_ushort();
291 $nameId = $this->read_ushort();
292 $length = $this->read_ushort();
293 $offset = $this->read_ushort();
294 if (!in_array($nameId,$K)) continue;
295 $N = '';
296 if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name
297 $opos = $this->_pos;
298 $this->seek($string_data_offset + $offset);
299 if ($length % 2 != 0)
300 die("PostScript name is UTF-16BE string of odd length");
301 $length /= 2;
302 $N = '';
303 while ($length > 0) {
304 $char = $this->read_ushort();
305 $N .= (chr($char));
306 $length -= 1;
307 }
308 $this->_pos = $opos;
309 $this->seek($opos);
310 }
311 else if ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name
312 $opos = $this->_pos;
313 $N = $this->get_chunk($string_data_offset + $offset, $length);
314 $this->_pos = $opos;
315 $this->seek($opos);
316 }
317 if ($N && $names[$nameId]=='') {
318 $names[$nameId] = $N;
319 $nameCount -= 1;
320 if ($nameCount==0) break;
321 }
322 }
323 if ($names[6])
324 $psName = $names[6];
325 else if ($names[4])
326 $psName = preg_replace('/ /','-',$names[4]);
327 else if ($names[1])
328 $psName = preg_replace('/ /','-',$names[1]);
329 else
330 $psName = '';
331 if (!$psName)
332 die("Could not find PostScript font name");
333 $this->name = $psName;
334 if ($names[1]) { $this->familyName = $names[1]; } else { $this->familyName = $psName; }
335 if ($names[2]) { $this->styleName = $names[2]; } else { $this->styleName = 'Regular'; }
336 if ($names[4]) { $this->fullName = $names[4]; } else { $this->fullName = $psName; }
337 if ($names[3]) { $this->uniqueFontID = $names[3]; } else { $this->uniqueFontID = $psName; }
338 if ($names[6]) { $this->fullName = $names[6]; }
339
340 ///////////////////////////////////
341 // head - Font header table
342 ///////////////////////////////////
343 $this->seek_table("head");
344 $this->skip(18);
345 $this->unitsPerEm = $unitsPerEm = $this->read_ushort();
346 $scale = 1000 / $unitsPerEm;
347 $this->skip(16);
348 $xMin = $this->read_short();
349 $yMin = $this->read_short();
350 $xMax = $this->read_short();
351 $yMax = $this->read_short();
352 $this->bbox = array(($xMin*$scale), ($yMin*$scale), ($xMax*$scale), ($yMax*$scale));
353 $this->skip(3*2);
354 $indexToLocFormat = $this->read_ushort();
355 $glyphDataFormat = $this->read_ushort();
356 if ($glyphDataFormat != 0)
357 die('Unknown glyph data format '.$glyphDataFormat);
358
359 ///////////////////////////////////
360 // hhea metrics table
361 ///////////////////////////////////
362 // ttf2t1 seems to use this value rather than the one in OS/2 - so put in for compatibility
363 if (isset($this->tables["hhea"])) {
364 $this->seek_table("hhea");
365 $this->skip(4);
366 $hheaAscender = $this->read_short();
367 $hheaDescender = $this->read_short();
368 $this->ascent = ($hheaAscender *$scale);
369 $this->descent = ($hheaDescender *$scale);
370 }
371
372 ///////////////////////////////////
373 // OS/2 - OS/2 and Windows metrics table
374 ///////////////////////////////////
375 if (isset($this->tables["OS/2"])) {
376 $this->seek_table("OS/2");
377 $version = $this->read_ushort();
378 $this->skip(2);
379 $usWeightClass = $this->read_ushort();
380 $this->skip(2);
381 $fsType = $this->read_ushort();
382 if ($fsType == 0x0002 || ($fsType & 0x0300) != 0) {
383 die('ERROR - Font file '.$this->filename.' cannot be embedded due to copyright restrictions.');
384 $this->restrictedUse = true;
385 }
386 $this->skip(20);
387 $sF = $this->read_short();
388 $this->sFamilyClass = ($sF >> 8);
389 $this->sFamilySubClass = ($sF & 0xFF);
390 $this->_pos += 10; //PANOSE = 10 byte length
391 $panose = fread($this->fh,10);
392 $this->skip(26);
393 $sTypoAscender = $this->read_short();
394 $sTypoDescender = $this->read_short();
395 if (!$this->ascent) $this->ascent = ($sTypoAscender*$scale);
396 if (!$this->descent) $this->descent = ($sTypoDescender*$scale);
397 if ($version > 1) {
398 $this->skip(16);
399 $sCapHeight = $this->read_short();
400 $this->capHeight = ($sCapHeight*$scale);
401 }
402 else {
403 $this->capHeight = $this->ascent;
404 }
405 }
406 else {
407 $usWeightClass = 500;
408 if (!$this->ascent) $this->ascent = ($yMax*$scale);
409 if (!$this->descent) $this->descent = ($yMin*$scale);
410 $this->capHeight = $this->ascent;
411 }
412 $this->stemV = 50 + intval(pow(($usWeightClass / 65.0),2));
413
414 ///////////////////////////////////
415 // post - PostScript table
416 ///////////////////////////////////
417 $this->seek_table("post");
418 $this->skip(4);
419 $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0;
420 $this->underlinePosition = $this->read_short() * $scale;
421 $this->underlineThickness = $this->read_short() * $scale;
422 $isFixedPitch = $this->read_ulong();
423
424 $this->flags = 4;
425
426 if ($this->italicAngle!= 0)
427 $this->flags = $this->flags | 64;
428 if ($usWeightClass >= 600)
429 $this->flags = $this->flags | 262144;
430 if ($isFixedPitch)
431 $this->flags = $this->flags | 1;
432
433 ///////////////////////////////////
434 // hhea - Horizontal header table
435 ///////////////////////////////////
436 $this->seek_table("hhea");
437 $this->skip(32);
438 $metricDataFormat = $this->read_ushort();
439 if ($metricDataFormat != 0)
440 die('Unknown horizontal metric data format '.$metricDataFormat);
441 $numberOfHMetrics = $this->read_ushort();
442 if ($numberOfHMetrics == 0)
443 die('Number of horizontal metrics is 0');
444
445 ///////////////////////////////////
446 // maxp - Maximum profile table
447 ///////////////////////////////////
448 $this->seek_table("maxp");
449 $this->skip(4);
450 $numGlyphs = $this->read_ushort();
451
452
453 ///////////////////////////////////
454 // cmap - Character to glyph index mapping table
455 ///////////////////////////////////
456 $cmap_offset = $this->seek_table("cmap");
457 $this->skip(2);
458 $cmapTableCount = $this->read_ushort();
459 $unicode_cmap_offset = 0;
460 for ($i=0;$i<$cmapTableCount;$i++) {
461 $platformID = $this->read_ushort();
462 $encodingID = $this->read_ushort();
463 $offset = $this->read_ulong();
464 $save_pos = $this->_pos;
465 if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
466 $format = $this->get_ushort($cmap_offset + $offset);
467 if ($format == 4) {
468 if (!$unicode_cmap_offset) $unicode_cmap_offset = $cmap_offset + $offset;
469 break;
470 }
471 }
472 $this->seek($save_pos );
473 }
474 if (!$unicode_cmap_offset)
475 die('Font ('.$this->filename .') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
476
477
478 $glyphToChar = array();
479 $charToGlyph = array();
480 $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph );
481
482 ///////////////////////////////////
483 // hmtx - Horizontal metrics table
484 ///////////////////////////////////
485 $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
486
487 }
488
489
490/////////////////////////////////////////////////////////////////////////////////////////
491/////////////////////////////////////////////////////////////////////////////////////////
492
493
494 function makeSubset($file, &$subset) {
495 $this->filename = $file;
496 $this->fh = fopen($file ,'rb') or die('Can\'t open file ' . $file);
497 $this->_pos = 0;
498 $this->charWidths = '';
499 $this->glyphPos = array();
500 $this->charToGlyph = array();
501 $this->tables = array();
502 $this->otables = array();
503 $this->ascent = 0;
504 $this->descent = 0;
505 $this->skip(4);
506 $this->maxUni = 0;
507 $this->readTableDirectory();
508
509
510 ///////////////////////////////////
511 // head - Font header table
512 ///////////////////////////////////
513 $this->seek_table("head");
514 $this->skip(50);
515 $indexToLocFormat = $this->read_ushort();
516 $glyphDataFormat = $this->read_ushort();
517
518 ///////////////////////////////////
519 // hhea - Horizontal header table
520 ///////////////////////////////////
521 $this->seek_table("hhea");
522 $this->skip(32);
523 $metricDataFormat = $this->read_ushort();
524 $orignHmetrics = $numberOfHMetrics = $this->read_ushort();
525
526 ///////////////////////////////////
527 // maxp - Maximum profile table
528 ///////////////////////////////////
529 $this->seek_table("maxp");
530 $this->skip(4);
531 $numGlyphs = $this->read_ushort();
532
533
534 ///////////////////////////////////
535 // cmap - Character to glyph index mapping table
536 ///////////////////////////////////
537 $cmap_offset = $this->seek_table("cmap");
538 $this->skip(2);
539 $cmapTableCount = $this->read_ushort();
540 $unicode_cmap_offset = 0;
541 for ($i=0;$i<$cmapTableCount;$i++) {
542 $platformID = $this->read_ushort();
543 $encodingID = $this->read_ushort();
544 $offset = $this->read_ulong();
545 $save_pos = $this->_pos;
546 if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
547 $format = $this->get_ushort($cmap_offset + $offset);
548 if ($format == 4) {
549 $unicode_cmap_offset = $cmap_offset + $offset;
550 break;
551 }
552 }
553 $this->seek($save_pos );
554 }
555
556 if (!$unicode_cmap_offset)
557 die('Font ('.$this->filename .') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
558
559
560 $glyphToChar = array();
561 $charToGlyph = array();
562 $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph );
563
564 $this->charToGlyph = $charToGlyph;
565
566 ///////////////////////////////////
567 // hmtx - Horizontal metrics table
568 ///////////////////////////////////
569 $scale = 1; // not used
570 $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
571
572 ///////////////////////////////////
573 // loca - Index to location
574 ///////////////////////////////////
575 $this->getLOCA($indexToLocFormat, $numGlyphs);
576
577 $subsetglyphs = array(0=>0);
578 $subsetCharToGlyph = array();
579 foreach($subset AS $code) {
580 if (isset($this->charToGlyph[$code])) {
581 $subsetglyphs[$this->charToGlyph[$code]] = $code; // Old Glyph ID => Unicode
582 $subsetCharToGlyph[$code] = $this->charToGlyph[$code]; // Unicode to old GlyphID
583
584 }
585 $this->maxUni = max($this->maxUni, $code);
586 }
587
588 list($start,$dummy) = $this->get_table_pos('glyf');
589
590 $glyphSet = array();
591 ksort($subsetglyphs);
592 $n = 0;
593 $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.
594 foreach($subsetglyphs AS $originalGlyphIdx => $uni) {
595 $fsLastCharIndex = max($fsLastCharIndex , $uni);
596 $glyphSet[$originalGlyphIdx] = $n; // old glyphID to new glyphID
597 $n++;
598 }
599
600 ksort($subsetCharToGlyph);
601 foreach($subsetCharToGlyph AS $uni => $originalGlyphIdx) {
602 $codeToGlyph[$uni] = $glyphSet[$originalGlyphIdx] ;
603 }
604 $this->codeToGlyph = $codeToGlyph;
605
606 ksort($subsetglyphs);
607 foreach($subsetglyphs AS $originalGlyphIdx => $uni) {
608 $this->getGlyphs($originalGlyphIdx, $start, $glyphSet, $subsetglyphs);
609 }
610
611 $numGlyphs = $numberOfHMetrics = count($subsetglyphs );
612
613 //tables copied from the original
614 $tags = array ('name');
615 foreach($tags AS $tag) { $this->add($tag, $this->get_table($tag)); }
616 $tags = array ('cvt ', 'fpgm', 'prep', 'gasp');
617 foreach($tags AS $tag) {
618 if (isset($this->tables[$tag])) { $this->add($tag, $this->get_table($tag)); }
619 }
620
621 // post - PostScript
622 $opost = $this->get_table('post');
623 $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";
624 $this->add('post', $post);
625
626 // Sort CID2GID map into segments of contiguous codes
627 ksort($codeToGlyph);
628 unset($codeToGlyph[0]);
629 //unset($codeToGlyph[65535]);
630 $rangeid = 0;
631 $range = array();
632 $prevcid = -2;
633 $prevglidx = -1;
634 // for each character
635 foreach ($codeToGlyph as $cid => $glidx) {
636 if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) {
637 $range[$rangeid][] = $glidx;
638 } else {
639 // new range
640 $rangeid = $cid;
641 $range[$rangeid] = array();
642 $range[$rangeid][] = $glidx;
643 }
644 $prevcid = $cid;
645 $prevglidx = $glidx;
646 }
647
648 // cmap - Character to glyph mapping - Format 4 (MS / )
649 $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF
650 $searchRange = 1;
651 $entrySelector = 0;
652 while ($searchRange * 2 <= $segCount ) {
655 }
657 $rangeShift = $segCount * 2 - $searchRange;
658 $length = 16 + (8*$segCount ) + ($numGlyphs+1);
659 $cmap = array(0, 1, // Index : version, number of encoding subtables
660 3, 1, // Encoding Subtable : platform (MS=3), encoding (Unicode)
661 0, 12, // Encoding Subtable : offset (hi,lo)
662 4, $length, 0, // Format 4 Mapping subtable: format, length, language
663 $segCount*2,
667
668 // endCode(s)
669 foreach($range AS $start=>$subrange) {
670 $endCode = $start + (count($subrange)-1);
671 $cmap[] = $endCode; // endCode(s)
672 }
673 $cmap[] = 0xFFFF; // endCode of last Segment
674 $cmap[] = 0; // reservedPad
675
676 // startCode(s)
677 foreach($range AS $start=>$subrange) {
678 $cmap[] = $start; // startCode(s)
679 }
680 $cmap[] = 0xFFFF; // startCode of last Segment
681 // idDelta(s)
682 foreach($range AS $start=>$subrange) {
683 $idDelta = -($start-$subrange[0]);
684 $n += count($subrange);
685 $cmap[] = $idDelta; // idDelta(s)
686 }
687 $cmap[] = 1; // idDelta of last Segment
688 // idRangeOffset(s)
689 foreach($range AS $subrange) {
690 $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
691
692 }
693 $cmap[] = 0; // idRangeOffset of last Segment
694 foreach($range AS $subrange) {
695 foreach($subrange AS $glidx) {
696 $cmap[] = $glidx;
697 }
698 }
699 $cmap[] = 0; // Mapping for last character
700 $cmapstr = '';
701 foreach($cmap AS $cm) { $cmapstr .= pack("n",$cm); }
702 $this->add('cmap', $cmapstr);
703
704
705 // glyf - Glyph data
706 list($glyfOffset,$glyfLength) = $this->get_table_pos('glyf');
707 if ($glyfLength < $this->maxStrLenRead) {
708 $glyphData = $this->get_table('glyf');
709 }
710
711 $offsets = array();
712 $glyf = '';
713 $pos = 0;
714
715 $hmtxstr = '';
716 $xMinT = 0;
717 $yMinT = 0;
718 $xMaxT = 0;
719 $yMaxT = 0;
720 $advanceWidthMax = 0;
721 $minLeftSideBearing = 0;
722 $minRightSideBearing = 0;
723 $xMaxExtent = 0;
724 $maxPoints = 0; // points in non-compound glyph
725 $maxContours = 0; // contours in non-compound glyph
726 $maxComponentPoints = 0; // points in compound glyph
727 $maxComponentContours = 0; // contours in compound glyph
728 $maxComponentElements = 0; // number of glyphs referenced at top level
729 $maxComponentDepth = 0; // levels of recursion, set to 0 if font has only simple glyphs
730 $this->glyphdata = array();
731
732 foreach($subsetglyphs AS $originalGlyphIdx => $uni) {
733 // hmtx - Horizontal Metrics
734 $hm = $this->getHMetric($orignHmetrics, $originalGlyphIdx);
735 $hmtxstr .= $hm;
736
737 $offsets[] = $pos;
738 $glyphPos = $this->glyphPos[$originalGlyphIdx];
739 $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
740 if ($glyfLength < $this->maxStrLenRead) {
741 $data = substr($glyphData,$glyphPos,$glyphLen);
742 }
743 else {
744 if ($glyphLen > 0) $data = $this->get_chunk($glyfOffset+$glyphPos,$glyphLen);
745 else $data = '';
746 }
747
748 if ($glyphLen > 0) {
749 $up = unpack("n", substr($data,0,2));
750 }
751
752 if ($glyphLen > 2 && ($up[1] & (1 << 15)) ) { // If number of contours <= -1 i.e. composiste glyph
753 $pos_in_glyph = 10;
754 $flags = GF_MORE;
755 $nComponentElements = 0;
756 while ($flags & GF_MORE) {
757 $nComponentElements += 1; // number of glyphs referenced at top level
758 $up = unpack("n", substr($data,$pos_in_glyph,2));
759 $flags = $up[1];
760 $up = unpack("n", substr($data,$pos_in_glyph+2,2));
761 $glyphIdx = $up[1];
762 $this->glyphdata[$originalGlyphIdx]['compGlyphs'][] = $glyphIdx;
763 $data = $this->_set_ushort($data, $pos_in_glyph + 2, $glyphSet[$glyphIdx]);
764 $pos_in_glyph += 4;
765 if ($flags & GF_WORDS) { $pos_in_glyph += 4; }
766 else { $pos_in_glyph += 2; }
767 if ($flags & GF_SCALE) { $pos_in_glyph += 2; }
768 else if ($flags & GF_XYSCALE) { $pos_in_glyph += 4; }
769 else if ($flags & GF_TWOBYTWO) { $pos_in_glyph += 8; }
770 }
771 $maxComponentElements = max($maxComponentElements, $nComponentElements);
772 }
773
774 $glyf .= $data;
775 $pos += $glyphLen;
776 if ($pos % 4 != 0) {
777 $padding = 4 - ($pos % 4);
778 $glyf .= str_repeat("\0",$padding);
779 $pos += $padding;
780 }
781 }
782
783 $offsets[] = $pos;
784 $this->add('glyf', $glyf);
785
786 // hmtx - Horizontal Metrics
787 $this->add('hmtx', $hmtxstr);
788
789 // loca - Index to location
790 $locastr = '';
791 if ((($pos + 1) >> 1) > 0xFFFF) {
792 $indexToLocFormat = 1; // long format
793 foreach($offsets AS $offset) { $locastr .= pack("N",$offset); }
794 }
795 else {
796 $indexToLocFormat = 0; // short format
797 foreach($offsets AS $offset) { $locastr .= pack("n",($offset/2)); }
798 }
799 $this->add('loca', $locastr);
800
801 // head - Font header
802 $head = $this->get_table('head');
803 $head = $this->_set_ushort($head, 50, $indexToLocFormat);
804 $this->add('head', $head);
805
806
807 // hhea - Horizontal Header
808 $hhea = $this->get_table('hhea');
809 $hhea = $this->_set_ushort($hhea, 34, $numberOfHMetrics);
810 $this->add('hhea', $hhea);
811
812 // maxp - Maximum Profile
813 $maxp = $this->get_table('maxp');
814 $maxp = $this->_set_ushort($maxp, 4, $numGlyphs);
815 $this->add('maxp', $maxp);
816
817
818 // OS/2 - OS/2
819 $os2 = $this->get_table('OS/2');
820 $this->add('OS/2', $os2 );
821
822 fclose($this->fh);
823
824 // Put the TTF file together
825 $stm = '';
826 $this->endTTFile($stm);
827 return $stm ;
828 }
829
830 //////////////////////////////////////////////////////////////////////////////////
831 // Recursively get composite glyph data
832 function getGlyphData($originalGlyphIdx, &$maxdepth, &$depth, &$points, &$contours) {
833 $depth++;
834 $maxdepth = max($maxdepth, $depth);
835 if (count($this->glyphdata[$originalGlyphIdx]['compGlyphs'])) {
836 foreach($this->glyphdata[$originalGlyphIdx]['compGlyphs'] AS $glyphIdx) {
837 $this->getGlyphData($glyphIdx, $maxdepth, $depth, $points, $contours);
838 }
839 }
840 else if (($this->glyphdata[$originalGlyphIdx]['nContours'] > 0) && $depth > 0) { // simple
841 $contours += $this->glyphdata[$originalGlyphIdx]['nContours'];
842 $points += $this->glyphdata[$originalGlyphIdx]['nPoints'];
843 }
844 $depth--;
845 }
846
847
848 //////////////////////////////////////////////////////////////////////////////////
849 // Recursively get composite glyphs
850 function getGlyphs($originalGlyphIdx, &$start, &$glyphSet, &$subsetglyphs) {
851 $glyphPos = $this->glyphPos[$originalGlyphIdx];
852 $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
853 if (!$glyphLen) {
854 return;
855 }
856 $this->seek($start + $glyphPos);
857 $numberOfContours = $this->read_short();
858 if ($numberOfContours < 0) {
859 $this->skip(8);
860 $flags = GF_MORE;
861 while ($flags & GF_MORE) {
862 $flags = $this->read_ushort();
863 $glyphIdx = $this->read_ushort();
864 if (!isset($glyphSet[$glyphIdx])) {
865 $glyphSet[$glyphIdx] = count($subsetglyphs); // old glyphID to new glyphID
866 $subsetglyphs[$glyphIdx] = true;
867 }
868 $savepos = ftell($this->fh);
869 $this->getGlyphs($glyphIdx, $start, $glyphSet, $subsetglyphs);
870 $this->seek($savepos);
871 if ($flags & GF_WORDS)
872 $this->skip(4);
873 else
874 $this->skip(2);
875 if ($flags & GF_SCALE)
876 $this->skip(2);
877 else if ($flags & GF_XYSCALE)
878 $this->skip(4);
879 else if ($flags & GF_TWOBYTWO)
880 $this->skip(8);
881 }
882 }
883 }
884
885 //////////////////////////////////////////////////////////////////////////////////
886
887 function getHMTX($numberOfHMetrics, $numGlyphs, &$glyphToChar, $scale) {
888 $start = $this->seek_table("hmtx");
889 $aw = 0;
890 $this->charWidths = str_pad('', 256*256*2, "\x00");
891 $nCharWidths = 0;
892 if (($numberOfHMetrics*4) < $this->maxStrLenRead) {
893 $data = $this->get_chunk($start,($numberOfHMetrics*4));
894 $arr = unpack("n*", $data);
895 }
896 else { $this->seek($start); }
897 for( $glyph=0; $glyph<$numberOfHMetrics; $glyph++) {
898
899 if (($numberOfHMetrics*4) < $this->maxStrLenRead) {
900 $aw = $arr[($glyph*2)+1];
901 }
902 else {
903 $aw = $this->read_ushort();
904 $lsb = $this->read_ushort();
905 }
906 if (isset($glyphToChar[$glyph]) || $glyph == 0) {
907
908 if ($aw >= (1 << 15) ) { $aw = 0; } // 1.03 Some (arabic) fonts have -ve values for width
909 // although should be unsigned value - comes out as e.g. 65108 (intended -50)
910 if ($glyph == 0) {
911 $this->defaultWidth = $scale*$aw;
912 continue;
913 }
914 foreach($glyphToChar[$glyph] AS $char) {
915 if ($char != 0 && $char != 65535) {
916 $w = intval(round($scale*$aw));
917 if ($w == 0) { $w = 65535; }
918 if ($char < 196608) {
919 $this->charWidths[$char*2] = chr($w >> 8);
920 $this->charWidths[$char*2 + 1] = chr($w & 0xFF);
921 $nCharWidths++;
922 }
923 }
924 }
925 }
926 }
927 $data = $this->get_chunk(($start+$numberOfHMetrics*4),($numGlyphs*2));
928 $arr = unpack("n*", $data);
929 $diff = $numGlyphs-$numberOfHMetrics;
930 for( $pos=0; $pos<$diff; $pos++) {
931 $glyph = $pos + $numberOfHMetrics;
932 if (isset($glyphToChar[$glyph])) {
933 foreach($glyphToChar[$glyph] AS $char) {
934 if ($char != 0 && $char != 65535) {
935 $w = intval(round($scale*$aw));
936 if ($w == 0) { $w = 65535; }
937 if ($char < 196608) {
938 $this->charWidths[$char*2] = chr($w >> 8);
939 $this->charWidths[$char*2 + 1] = chr($w & 0xFF);
940 $nCharWidths++;
941 }
942 }
943 }
944 }
945 }
946 // NB 65535 is a set width of 0
947 // First bytes define number of chars in font
948 $this->charWidths[0] = chr($nCharWidths >> 8);
949 $this->charWidths[1] = chr($nCharWidths & 0xFF);
950 }
951
952 function getHMetric($numberOfHMetrics, $gid) {
953 $start = $this->seek_table("hmtx");
954 if ($gid < $numberOfHMetrics) {
955 $this->seek($start+($gid*4));
956 $hm = fread($this->fh,4);
957 }
958 else {
959 $this->seek($start+(($numberOfHMetrics-1)*4));
960 $hm = fread($this->fh,2);
961 $this->seek($start+($numberOfHMetrics*2)+($gid*2));
962 $hm .= fread($this->fh,2);
963 }
964 return $hm;
965 }
966
967 function getLOCA($indexToLocFormat, $numGlyphs) {
968 $start = $this->seek_table('loca');
969 $this->glyphPos = array();
970 if ($indexToLocFormat == 0) {
971 $data = $this->get_chunk($start,($numGlyphs*2)+2);
972 $arr = unpack("n*", $data);
973 for ($n=0; $n<=$numGlyphs; $n++) {
974 $this->glyphPos[] = ($arr[$n+1] * 2);
975 }
976 }
977 else if ($indexToLocFormat == 1) {
978 $data = $this->get_chunk($start,($numGlyphs*4)+4);
979 $arr = unpack("N*", $data);
980 for ($n=0; $n<=$numGlyphs; $n++) {
981 $this->glyphPos[] = ($arr[$n+1]);
982 }
983 }
984 else
985 die('Unknown location table format '.$indexToLocFormat);
986 }
987
988
989 // CMAP Format 4
990 function getCMAP4($unicode_cmap_offset, &$glyphToChar, &$charToGlyph ) {
991 $this->maxUniChar = 0;
992 $this->seek($unicode_cmap_offset + 2);
993 $length = $this->read_ushort();
994 $limit = $unicode_cmap_offset + $length;
995 $this->skip(2);
996
997 $segCount = $this->read_ushort() / 2;
998 $this->skip(6);
999 $endCount = array();
1000 for($i=0; $i<$segCount; $i++) { $endCount[] = $this->read_ushort(); }
1001 $this->skip(2);
1002 $startCount = array();
1003 for($i=0; $i<$segCount; $i++) { $startCount[] = $this->read_ushort(); }
1004 $idDelta = array();
1005 for($i=0; $i<$segCount; $i++) { $idDelta[] = $this->read_short(); } // ???? was unsigned short
1006 $idRangeOffset_start = $this->_pos;
1007 $idRangeOffset = array();
1008 for($i=0; $i<$segCount; $i++) { $idRangeOffset[] = $this->read_ushort(); }
1009
1010 for ($n=0;$n<$segCount;$n++) {
1011 $endpoint = ($endCount[$n] + 1);
1012 for ($unichar=$startCount[$n];$unichar<$endpoint;$unichar++) {
1013 if ($idRangeOffset[$n] == 0)
1014 $glyph = ($unichar + $idDelta[$n]) & 0xFFFF;
1015 else {
1016 $offset = ($unichar - $startCount[$n]) * 2 + $idRangeOffset[$n];
1017 $offset = $idRangeOffset_start + 2 * $n + $offset;
1018 if ($offset >= $limit)
1019 $glyph = 0;
1020 else {
1021 $glyph = $this->get_ushort($offset);
1022 if ($glyph != 0)
1023 $glyph = ($glyph + $idDelta[$n]) & 0xFFFF;
1024 }
1025 }
1026 $charToGlyph[$unichar] = $glyph;
1027 if ($unichar < 196608) { $this->maxUniChar = max($unichar,$this->maxUniChar); }
1028 $glyphToChar[$glyph][] = $unichar;
1029 }
1030 }
1031 }
1032
1033
1034 // Put the TTF file together
1035 function endTTFile(&$stm) {
1036 $stm = '';
1037 $numTables = count($this->otables);
1038 $searchRange = 1;
1039 $entrySelector = 0;
1040 while ($searchRange * 2 <= $numTables) {
1043 }
1046
1047 // Header
1048 if (_TTF_MAC_HEADER) {
1049 $stm .= (pack("Nnnnn", 0x74727565, $numTables, $searchRange, $entrySelector, $rangeShift)); // Mac
1050 }
1051 else {
1052 $stm .= (pack("Nnnnn", 0x00010000 , $numTables, $searchRange, $entrySelector, $rangeShift)); // Windows
1053 }
1054
1055 // Table directory
1057
1058 ksort ($tables);
1059 $offset = 12 + $numTables * 16;
1060 foreach ($tables AS $tag=>$data) {
1061 if ($tag == 'head') { $head_start = $offset; }
1062 $stm .= $tag;
1063 $checksum = $this->calcChecksum($data);
1064 $stm .= pack("nn", $checksum[0],$checksum[1]);
1065 $stm .= pack("NN", $offset, strlen($data));
1066 $paddedLength = (strlen($data)+3)&~3;
1067 $offset = $offset + $paddedLength;
1068 }
1069
1070 // Table data
1071 foreach ($tables AS $tag=>$data) {
1072 $data .= "\0\0\0";
1073 $stm .= substr($data,0,(strlen($data)&~3));
1074 }
1075
1076 $checksum = $this->calcChecksum($stm);
1077 $checksum = $this->sub32(array(0xB1B0,0xAFBA), $checksum);
1078 $chk = pack("nn", $checksum[0],$checksum[1]);
1079 $stm = $this->splice($stm,($head_start + 8),$chk);
1080 return $stm ;
1081 }
1082
1083
1084
1085
1086}
1087
1088
1089?>
$code
foreach($array as $idx=> $m) $w
$from_poste name
getHMTX($numberOfHMetrics, $numGlyphs, &$glyphToChar, $scale)
Definition: ttfonts.php:887
_set_short($stream, $offset, $val)
Definition: ttfonts.php:234
sub32($x, $y)
Definition: ttfonts.php:119
read_short()
Definition: ttfonts.php:174
read_ulong()
Definition: ttfonts.php:196
getLOCA($indexToLocFormat, $numGlyphs)
Definition: ttfonts.php:967
calcChecksum($data)
Definition: ttfonts.php:132
makeSubset($file, &$subset)
Definition: ttfonts.php:494
$entrySelector
Definition: ttfonts.php:44
$maxStrLenRead
Definition: ttfonts.php:70
_set_ushort($stream, $offset, $value)
Definition: ttfonts.php:229
extractInfo()
Definition: ttfonts.php:271
read_ushort()
Definition: ttfonts.php:190
getGlyphs($originalGlyphIdx, &$start, &$glyphSet, &$subsetglyphs)
Definition: ttfonts.php:850
$underlinePosition
Definition: ttfonts.php:66
getGlyphData($originalGlyphIdx, &$maxdepth, &$depth, &$points, &$contours)
Definition: ttfonts.php:832
getMetrics($file)
Definition: ttfonts.php:77
get_table($tag)
Definition: ttfonts.php:250
getCMAP4($unicode_cmap_offset, &$glyphToChar, &$charToGlyph)
Definition: ttfonts.php:990
splice($stream, $offset, $value)
Definition: ttfonts.php:225
seek($pos)
Definition: ttfonts.php:152
unpack_short($s)
Definition: ttfonts.php:182
get_table_pos($tag)
Definition: ttfonts.php:146
__construct()
Definition: ttfonts.php:72
get_ulong($pos)
Definition: ttfonts.php:209
skip($delta)
Definition: ttfonts.php:157
get_ushort($pos)
Definition: ttfonts.php:203
seek_table($tag, $offset_in_table=0)
Definition: ttfonts.php:162
get_chunk($pos, $length)
Definition: ttfonts.php:244
$underlineThickness
Definition: ttfonts.php:67
pack_short($val)
Definition: ttfonts.php:216
readTableDirectory()
Definition: ttfonts.php:102
add($tag, $data)
Definition: ttfonts.php:257
endTTFile(&$stm)
Definition: ttfonts.php:1035
getHMetric($numberOfHMetrics, $gid)
Definition: ttfonts.php:952
$up
Definition: courier.php:4
$hi
Definition: export.php:44
$version
Definition: install.php:490
$chk
const GF_TWOBYTWO
Definition: ttfonts.php:34
const GF_XYSCALE
Definition: ttfonts.php:33
const _TTF_MAC_HEADER
Definition: ttfonts.php:26
const GF_SCALE
Definition: ttfonts.php:31
const GF_WORDS
Definition: ttfonts.php:30
const GF_MORE
Definition: ttfonts.php:32