26define(
"_TTF_MAC_HEADER",
false);
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));
37#[AllowDynamicProperties]
73 $this->maxStrLenRead = 200000;
78 $this->filename =
$file;
79 $this->fh = fopen(
$file,
'rb') or die(
'Can\'t open file ' .
$file);
81 $this->charWidths =
'';
82 $this->glyphPos = array();
83 $this->charToGlyph = array();
84 $this->tables = array();
85 $this->otables = array();
88 $this->TTCFonts = array();
91 die(
"Postscript outlines are not supported");
93 die(
"ERROR - TrueType Fonts Collections not supported");
94 if (!in_array(
$version, array(0x00010000,0x74727565)))
95 die(
"Not a TrueType font: version=".
$version);
107 $this->tables = array();
114 $this->tables[$record[
'tag']] = $record;
124 if ($ylo > $xlo) { $xlo += 1 << 16; $yhi += 1; }
126 if ($yhi > $xhi) { $xhi += 1 << 16; }
128 $reshi = $reshi & 0xFFFF;
129 return array($reshi, $reslo);
133 if (strlen(
$data) % 4) {
$data .= str_repeat(
"\0",(4-(strlen(
$data) % 4))); }
143 return array(
$hi, $lo);
148 $length = $this->tables[
$tag][
'length'];
149 return array(
$offset, $length);
154 fseek($this->fh,$this->_pos);
158 $this->_pos = $this->_pos +
$delta;
159 fseek($this->fh,$this->_pos);
164 $this->_pos = $tpos[0] + $offset_in_table;
165 fseek($this->fh, $this->_pos);
171 return fread($this->fh,4);
176 $s = fread($this->fh,2);
177 $a = (ord(
$s[0])<<8) + ord(
$s[1]);
178 if (
$a & (1 << 15) ) {
$a = (
$a - (1 << 16)) ; }
183 $a = (ord(
$s[0])<<8) + ord(
$s[1]);
184 if (
$a & (1 << 15) ) {
185 $a = (
$a - (1 << 16));
192 $s = fread($this->fh,2);
193 return (ord(
$s[0])<<8) + ord(
$s[1]);
198 $s = fread($this->fh,4);
200 return (ord(
$s[0])*16777216) + (ord(
$s[1])<<16) + (ord(
$s[2])<<8) + ord(
$s[3]);
204 fseek($this->fh,
$pos);
205 $s = fread($this->fh,2);
206 return (ord(
$s[0])<<8) + ord(
$s[1]);
210 fseek($this->fh,
$pos);
211 $s = fread($this->fh,4);
213 return (ord(
$s[0])*16777216) + (ord(
$s[1])<<16) + (ord(
$s[2])<<8) + ord(
$s[3]);
222 return pack(
"n",
$val);
245 fseek($this->fh,
$pos);
246 if ($length <1) {
return ''; }
247 return (fread($this->fh,$length));
252 if ($length == 0) { die(
'Truetype font ('.$this->filename.
'): error reading table: '.
$tag); }
253 fseek($this->fh,
$pos);
254 return (fread($this->fh,$length));
258 if (
$tag ==
'head') {
275 $this->sFamilyClass = 0;
276 $this->sFamilySubClass = 0;
281 die(
"Unknown name table format ".$format);
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++) {
294 if (!in_array($nameId,$K))
continue;
296 if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) {
299 if ($length % 2 != 0)
300 die(
"PostScript name is UTF-16BE string of odd length");
303 while ($length > 0) {
311 else if ($platformId == 1 && $encodingId == 0 && $languageId == 0) {
317 if ($N && $names[$nameId]==
'') {
318 $names[$nameId] = $N;
320 if ($nameCount==0)
break;
326 $psName = preg_replace(
'/ /',
'-',$names[4]);
328 $psName = preg_replace(
'/ /',
'-',$names[1]);
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]; }
352 $this->bbox = array(($xMin*$scale), ($yMin*$scale), ($xMax*$scale), ($yMax*$scale));
356 if ($glyphDataFormat != 0)
357 die(
'Unknown glyph data format '.$glyphDataFormat);
363 if (isset($this->tables[
"hhea"])) {
368 $this->ascent = ($hheaAscender *$scale);
369 $this->descent = ($hheaDescender *$scale);
375 if (isset($this->tables[
"OS/2"])) {
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;
388 $this->sFamilyClass = ($sF >> 8);
389 $this->sFamilySubClass = ($sF & 0xFF);
391 $panose = fread($this->fh,10);
395 if (!$this->ascent) $this->ascent = ($sTypoAscender*$scale);
396 if (!$this->descent) $this->descent = ($sTypoDescender*$scale);
400 $this->capHeight = ($sCapHeight*$scale);
407 $usWeightClass = 500;
408 if (!$this->ascent) $this->ascent = ($yMax*$scale);
409 if (!$this->descent) $this->descent = ($yMin*$scale);
412 $this->stemV = 50 + intval(pow(($usWeightClass / 65.0),2));
420 $this->underlinePosition = $this->
read_short() * $scale;
421 $this->underlineThickness = $this->
read_short() * $scale;
426 if ($this->italicAngle!= 0)
427 $this->flags = $this->flags | 64;
428 if ($usWeightClass >= 600)
429 $this->flags = $this->flags | 262144;
431 $this->flags = $this->flags | 1;
439 if ($metricDataFormat != 0)
440 die(
'Unknown horizontal metric data format '.$metricDataFormat);
442 if ($numberOfHMetrics == 0)
443 die(
'Number of horizontal metrics is 0');
459 $unicode_cmap_offset = 0;
460 for (
$i=0;
$i<$cmapTableCount;
$i++) {
465 if (($platformID == 3 && $encodingID == 1) || $platformID == 0) {
468 if (!$unicode_cmap_offset) $unicode_cmap_offset = $cmap_offset +
$offset;
472 $this->
seek($save_pos );
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)');
478 $glyphToChar = array();
485 $this->
getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
495 $this->filename =
$file;
496 $this->fh = fopen(
$file ,
'rb') or die(
'Can\'t open file ' .
$file);
498 $this->charWidths =
'';
499 $this->glyphPos = array();
500 $this->charToGlyph = array();
501 $this->tables = array();
502 $this->otables = array();
524 $orignHmetrics = $numberOfHMetrics = $this->
read_ushort();
540 $unicode_cmap_offset = 0;
541 for (
$i=0;
$i<$cmapTableCount;
$i++) {
546 if (($platformID == 3 && $encodingID == 1) || $platformID == 0) {
549 $unicode_cmap_offset = $cmap_offset +
$offset;
553 $this->
seek($save_pos );
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)');
560 $glyphToChar = array();
570 $this->
getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
575 $this->
getLOCA($indexToLocFormat, $numGlyphs);
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;
582 $subsetCharToGlyph[
$code] = $this->charToGlyph[
$code];
585 $this->maxUni = max($this->maxUni,
$code);
591 ksort($subsetglyphs);
593 $fsLastCharIndex = 0;
594 foreach($subsetglyphs AS $originalGlyphIdx => $uni) {
595 $fsLastCharIndex = max($fsLastCharIndex , $uni);
596 $glyphSet[$originalGlyphIdx] = $n;
600 ksort($subsetCharToGlyph);
601 foreach($subsetCharToGlyph AS $uni => $originalGlyphIdx) {
602 $codeToGlyph[$uni] = $glyphSet[$originalGlyphIdx] ;
604 $this->codeToGlyph = $codeToGlyph;
606 ksort($subsetglyphs);
607 foreach($subsetglyphs AS $originalGlyphIdx => $uni) {
611 $numGlyphs = $numberOfHMetrics = count($subsetglyphs );
614 $tags = array (
'name');
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)); }
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);
628 unset($codeToGlyph[0]);
635 foreach ($codeToGlyph as $cid => $glidx) {
636 if ($cid == (
$prevcid + 1) && $glidx == ($prevglidx + 1)) {
649 $segCount = count(
$range) + 1;
658 $length = 16 + (8*$segCount ) + ($numGlyphs+1);
670 $endCode =
$start + (count($subrange)-1);
683 $idDelta = -(
$start-$subrange[0]);
684 $n += count($subrange);
689 foreach(
$range AS $subrange) {
694 foreach(
$range AS $subrange) {
695 foreach($subrange AS $glidx) {
701 foreach($cmap AS $cm) { $cmapstr .= pack(
"n",$cm); }
702 $this->
add(
'cmap', $cmapstr);
706 list($glyfOffset,$glyfLength) = $this->
get_table_pos(
'glyf');
707 if ($glyfLength < $this->maxStrLenRead) {
720 $advanceWidthMax = 0;
721 $minLeftSideBearing = 0;
722 $minRightSideBearing = 0;
726 $maxComponentPoints = 0;
727 $maxComponentContours = 0;
728 $maxComponentElements = 0;
729 $maxComponentDepth = 0;
730 $this->glyphdata = array();
732 foreach($subsetglyphs AS $originalGlyphIdx => $uni) {
734 $hm = $this->
getHMetric($orignHmetrics, $originalGlyphIdx);
738 $glyphPos = $this->glyphPos[$originalGlyphIdx];
739 $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] -
$glyphPos;
740 if ($glyfLength < $this->maxStrLenRead) {
749 $up = unpack(
"n", substr(
$data,0,2));
752 if ($glyphLen > 2 && (
$up[1] & (1 << 15)) ) {
755 $nComponentElements = 0;
757 $nComponentElements += 1;
758 $up = unpack(
"n", substr(
$data,$pos_in_glyph,2));
760 $up = unpack(
"n", substr(
$data,$pos_in_glyph+2,2));
762 $this->glyphdata[$originalGlyphIdx][
'compGlyphs'][] = $glyphIdx;
766 else { $pos_in_glyph += 2; }
771 $maxComponentElements = max($maxComponentElements, $nComponentElements);
777 $padding = 4 - (
$pos % 4);
778 $glyf .= str_repeat(
"\0",$padding);
784 $this->
add(
'glyf', $glyf);
787 $this->
add(
'hmtx', $hmtxstr);
791 if (((
$pos + 1) >> 1) > 0xFFFF) {
792 $indexToLocFormat = 1;
796 $indexToLocFormat = 0;
797 foreach($offsets AS
$offset) { $locastr .= pack(
"n",(
$offset/2)); }
799 $this->
add(
'loca', $locastr);
803 $head = $this->
_set_ushort($head, 50, $indexToLocFormat);
804 $this->
add(
'head', $head);
809 $hhea = $this->
_set_ushort($hhea, 34, $numberOfHMetrics);
810 $this->
add(
'hhea', $hhea);
815 $this->
add(
'maxp', $maxp);
820 $this->
add(
'OS/2', $os2 );
832 function getGlyphData($originalGlyphIdx, &$maxdepth, &$depth, &$points, &$contours) {
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);
840 else if (($this->glyphdata[$originalGlyphIdx][
'nContours'] > 0) && $depth > 0) {
841 $contours += $this->glyphdata[$originalGlyphIdx][
'nContours'];
842 $points += $this->glyphdata[$originalGlyphIdx][
'nPoints'];
851 $glyphPos = $this->glyphPos[$originalGlyphIdx];
852 $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] -
$glyphPos;
858 if ($numberOfContours < 0) {
864 if (!isset($glyphSet[$glyphIdx])) {
865 $glyphSet[$glyphIdx] = count($subsetglyphs);
866 $subsetglyphs[$glyphIdx] =
true;
868 $savepos = ftell($this->fh);
870 $this->
seek($savepos);
887 function getHMTX($numberOfHMetrics, $numGlyphs, &$glyphToChar, $scale) {
890 $this->charWidths = str_pad(
'', 256*256*2,
"\x00");
892 if (($numberOfHMetrics*4) < $this->maxStrLenRead) {
894 $arr = unpack(
"n*",
$data);
897 for( $glyph=0; $glyph<$numberOfHMetrics; $glyph++) {
899 if (($numberOfHMetrics*4) < $this->maxStrLenRead) {
900 $aw = $arr[($glyph*2)+1];
906 if (isset($glyphToChar[$glyph]) || $glyph == 0) {
908 if ($aw >= (1 << 15) ) { $aw = 0; }
911 $this->defaultWidth = $scale*$aw;
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);
928 $arr = unpack(
"n*",
$data);
929 $diff = $numGlyphs-$numberOfHMetrics;
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);
948 $this->charWidths[0] = chr($nCharWidths >> 8);
949 $this->charWidths[1] = chr($nCharWidths & 0xFF);
954 if ($gid < $numberOfHMetrics) {
956 $hm = fread($this->fh,4);
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);
967 function getLOCA($indexToLocFormat, $numGlyphs) {
969 $this->glyphPos = array();
970 if ($indexToLocFormat == 0) {
972 $arr = unpack(
"n*",
$data);
973 for ($n=0; $n<=$numGlyphs; $n++) {
974 $this->glyphPos[] = ($arr[$n+1] * 2);
977 else if ($indexToLocFormat == 1) {
979 $arr = unpack(
"N*",
$data);
980 for ($n=0; $n<=$numGlyphs; $n++) {
981 $this->glyphPos[] = ($arr[$n+1]);
985 die(
'Unknown location table format '.$indexToLocFormat);
991 $this->maxUniChar = 0;
992 $this->
seek($unicode_cmap_offset + 2);
994 $limit = $unicode_cmap_offset + $length;
1002 $startCount = array();
1007 $idRangeOffset = array();
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;
1016 $offset = ($unichar - $startCount[$n]) * 2 + $idRangeOffset[$n];
1023 $glyph = ($glyph + $idDelta[$n]) & 0xFFFF;
1027 if ($unichar < 196608) { $this->maxUniChar = max($unichar,$this->maxUniChar); }
1028 $glyphToChar[$glyph][] = $unichar;
1064 $stm .= pack(
"nn", $checksum[0],$checksum[1]);
1066 $paddedLength = (strlen(
$data)+3)&~3;
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);
foreach($array as $idx=> $m) $w
getHMTX($numberOfHMetrics, $numGlyphs, &$glyphToChar, $scale)
_set_short($stream, $offset, $val)
getLOCA($indexToLocFormat, $numGlyphs)
makeSubset($file, &$subset)
_set_ushort($stream, $offset, $value)
getGlyphs($originalGlyphIdx, &$start, &$glyphSet, &$subsetglyphs)
getGlyphData($originalGlyphIdx, &$maxdepth, &$depth, &$points, &$contours)
getCMAP4($unicode_cmap_offset, &$glyphToChar, &$charToGlyph)
splice($stream, $offset, $value)
seek_table($tag, $offset_in_table=0)
getHMetric($numberOfHMetrics, $gid)