1<?php 2 3require_once(HTML2PS_DIR.'ot.class.php'); 4 5/** 6 * @return Array font metrics hash or null of TTF file could not be parsed 7 */ 8function ReadTTF($fontfile, $map) { 9 if (!is_readable($fontfile)) { return null; }; 10 11 /** 12 * Open font file and read metrics information 13 */ 14 $font = new OpenTypeFile(); 15 $font->open($fontfile); 16 17 $head =& $font->getTable('head'); 18 $name =& $font->getTable('name'); 19 $cmap =& $font->getTable('cmap'); 20 $hmtx =& $font->getTable('hmtx'); 21 $hhea =& $font->getTable('hhea'); 22 $post =& $font->getTable('post'); 23 $subtable =& $cmap->findSubtable(OT_CMAP_PLATFORM_WINDOWS, 24 OT_CMAP_PLATFORM_WINDOWS_UNICODE); 25 26 /** 27 * Prepare initial data 28 */ 29 $widths = array(); 30 31 for ($i=0; $i<256; $i++) { 32 $code = chr($i); 33 if (!isset($map[$code])) { 34 $widths[] = 1000; 35 continue; 36 }; 37 $ucs2 = $map[$code]; 38 39 /** 40 * If the font is monospaced, only one entry need be in the array, 41 * but that entry is required. The last entry applies to all 42 * subsequent glyphs. 43 */ 44 $glyphIndex = $subtable->lookup($ucs2); 45 46 if (!is_null($glyphIndex)) { 47 $realIndex = min($glyphIndex, $hhea->_numberOfHMetrics-1); 48 $widths[] = floor($hmtx->_hMetrics[$realIndex]['advanceWidth']*1000/$head->_unitsPerEm); 49 } else { 50 $widths[] = 1000; 51 }; 52 }; 53 54 $font_info = array(); 55 56 /** 57 * Here we use a hack; as, acording to OT specifications, 58 * 59 * When translated to ASCII, these [...] strings must be 60 * identical; no longer than 63 characters; and restricted to the 61 * printable ASCII subset, codes 33 through 126, except for the 10 62 * characters: '[', ']', '(', ')', '{', '}', '<', '>', '/', '%'. 63 * 64 * we can assume that UCS-2 encoded string we receive can be easily 65 * translated to ASCII by removing the high-byte of all two-byte characters 66 */ 67 $ps_name_ucs2 = $name->lookup(OT_CMAP_PLATFORM_WINDOWS, 68 OT_CMAP_PLATFORM_WINDOWS_UNICODE, 69 OT_CMAP_LANGUAGE_WINDOWS_ENGLISH_AMERICAN, 70 OT_NAME_ID_POSTSCRIPT_NAME); 71 $ps_name_ascii = ""; 72 for ($i=0; $i<strlen($ps_name_ucs2); $i+=2) { 73 $ps_name_ascii .= $ps_name_ucs2{$i+1}; 74 }; 75 76 $font_info['FontName'] = $ps_name_ascii; 77 78 $font_info['Weight'] = $name->lookup(null, null, null, OT_NAME_ID_SUBFAMILY_NAME); 79 $font_info['ItalicAngle'] = $post->_italicAngle; 80 $font_info['IsFixedPitch'] = (bool)$post->_isFixedPitch; 81 // $font_info['CapHeight'] 82 // $font_info['StdVW'] 83 $font_info['Ascender'] = floor($hhea->_ascender*1000/$head->_unitsPerEm); 84 $font_info['Descender'] = floor($hhea->_descender*1000/$head->_unitsPerEm); 85 $font_info['UnderlineThickness'] = floor($post->_underlineThickness*1000/$head->_unitsPerEm); 86 $font_info['UnderlinePosition'] = floor($post->_underlinePosition*1000/$head->_unitsPerEm); 87 $font_info['FontBBox'] = array($head->_xMin*1000/$head->_unitsPerEm, 88 $head->_yMin*1000/$head->_unitsPerEm, 89 $head->_xMax*1000/$head->_unitsPerEm, 90 $head->_yMax*1000/$head->_unitsPerEm); 91 $font_info['Widths'] = $widths; 92 93 $font->_delete(); 94 unset($font); 95 96 return $font_info; 97} 98 99/** 100 * @return Array font metrics hash or null of AFM file is missing 101 */ 102function ReadAFM($file, $map) { 103 if (!is_readable($file)) { return null; }; 104 105 $afm_lines = file($file); 106 $widths=array(); 107 $fm=array(); 108 109 foreach ($afm_lines as $l) { 110 $e=explode(' ',rtrim($l)); 111 112 if (count($e)<2) { 113 continue; 114 }; 115 116 $code=$e[0]; 117 $param=$e[1]; 118 119 if ($code=='C') { 120 //Character metrics 121 $cc=(int)$e[1]; 122 $w=$e[4]; 123 $gn=$e[7]; 124 if (substr($gn,-4)=='20AC') { 125 $gn='Euro'; 126 }; 127 128 $widths[$gn]=$w; 129 130 if ($gn=='.notdef') { 131 $fm['MissingWidth']=$w; 132 }; 133 } 134 elseif($code=='FontName') 135 $fm['FontName']=$param; 136 elseif($code=='Weight') 137 $fm['Weight']=$param; 138 elseif($code=='ItalicAngle') 139 $fm['ItalicAngle']=(double)$param; 140 elseif($code=='Ascender') 141 $fm['Ascender']=(int)$param; 142 elseif($code=='Descender') 143 $fm['Descender']=(int)$param; 144 elseif($code=='UnderlineThickness') 145 $fm['UnderlineThickness']=(int)$param; 146 elseif($code=='UnderlinePosition') 147 $fm['UnderlinePosition']=(int)$param; 148 elseif($code=='IsFixedPitch') 149 $fm['IsFixedPitch']=($param=='true'); 150 elseif($code=='FontBBox') 151 $fm['FontBBox']=array($e[1],$e[2],$e[3],$e[4]); 152 elseif($code=='CapHeight') 153 $fm['CapHeight']=(int)$param; 154 elseif($code=='StdVW') 155 $fm['StdVW']=(int)$param; 156 } 157 158 if(!isset($fm['FontName'])) { 159 die('FontName not found'); 160 }; 161 162 if (!isset($widths['.notdef'])) { 163 $widths['.notdef']=600; 164 }; 165 166 if (!isset($widths['Delta']) and isset($widths['increment'])) { 167 $widths['Delta']=$widths['increment']; 168 }; 169 170 // Order widths according to map 171 for ($i=0; $i<=255; $i++) { 172 if(!isset($widths[$map[chr($i)]])) { 173 error_log('<B>Warning:</B> character '.$map[chr($i)].' is missing<BR>'); 174 $widths[$i]=$widths['.notdef']; 175 } else { 176 $widths[$i]=$widths[$map[chr($i)]]; 177 }; 178 }; 179 180 $fm['Widths']=$widths; 181 return $fm; 182} 183 184function MakeFontDescriptor($fm,$symbolic) { 185 //Ascent 186 $asc=(isset($fm['Ascender']) ? $fm['Ascender'] : 1000); 187 $fd="array('Ascent'=>".$asc; 188 189 //Descent 190 $desc=(isset($fm['Descender']) ? $fm['Descender'] : -200); 191 $fd.=",'Descent'=>".$desc; 192 193 //CapHeight 194 if (isset($fm['CapHeight'])) { 195 $ch=$fm['CapHeight']; 196 } elseif(isset($fm['CapXHeight'])) { 197 $ch=$fm['CapXHeight']; 198 } else { 199 $ch=$asc; 200 }; 201 $fd.=",'CapHeight'=>".$ch; 202 203 //Flags 204 $flags=0; 205 if (isset($fm['IsFixedPitch']) and $fm['IsFixedPitch']) { 206 $flags+=1<<0; 207 }; 208 209 if ($symbolic) { 210 $flags+=1<<2; 211 }; 212 213 if (!$symbolic) { 214 $flags+=1<<5; 215 }; 216 217 if (isset($fm['ItalicAngle']) and $fm['ItalicAngle']!=0) { 218 $flags+=1<<6; 219 }; 220 221 $fd.=",'Flags'=>".$flags; 222 223 //FontBBox 224 if (isset($fm['FontBBox'])) { 225 $fbb=$fm['FontBBox']; 226 } else { 227 $fbb=array(0,$des-100,1000,$asc+100); 228 }; 229 230 $fd.=",'FontBBox'=>'[".$fbb[0].' '.$fbb[1].' '.$fbb[2].' '.$fbb[3]."]'"; 231 232 //ItalicAngle 233 $ia=(isset($fm['ItalicAngle']) ? $fm['ItalicAngle'] : 0); 234 $fd.=",'ItalicAngle'=>".$ia; 235 236 //StemV 237 if (isset($fm['StdVW'])) { 238 $stemv=$fm['StdVW']; 239 } elseif(isset($fm['Weight']) and eregi('(bold|black)',$fm['Weight'])) { 240 $stemv=120; 241 } else { 242 $stemv=70; 243 }; 244 $fd.=",'StemV'=>".$stemv; 245 246 //MissingWidth 247 if (isset($fm['MissingWidth'])) { 248 $fd.=",'MissingWidth'=>".$fm['MissingWidth']; 249 }; 250 $fd.=')'; 251 252 return $fd; 253} 254 255function MakeWidthArray($fm) { 256 //Make character width array 257 $s="array(\n\t"; 258 $cw=$fm['Widths']; 259 for ($i=0; $i<=255; $i++) { 260 if (chr($i)=="'") { 261 $s.="'\\''"; 262 } elseif (chr($i)=="\\") { 263 $s.="'\\\\'"; 264 } elseif($i>=32 and $i<=126) { 265 $s.="'".chr($i)."'"; 266 } else { 267 $s.="chr($i)"; 268 }; 269 $s.='=>'.$fm['Widths'][$i]; 270 if ($i<255) { 271 $s.=','; 272 }; 273 274 if(($i+1)%22==0) { 275 $s.="\n\t"; 276 }; 277 } 278 $s.=')'; 279 return $s; 280} 281 282function MakeFontEncoding($map) { 283 //Build differences from reference encoding 284 $manager = ManagerEncoding::get(); 285 $ref = $manager->get_encoding_glyphs('windows-1252'); 286 287 $s=''; 288 $last=0; 289 for($i=32;$i<=255;$i++) { 290 if ($map[chr($i)]!=$ref[chr($i)]) { 291 if ($i!=$last+1) { 292 $s.=$i.' '; 293 }; 294 $last=$i; 295 $s.='/'.$map[chr($i)].' '; 296 }; 297 } 298 299 return rtrim($s); 300} 301 302function MakeFontCMap($encoding) { 303 //Build differences from reference encoding 304 $manager = ManagerEncoding::get(); 305 $ref = $manager->get_encoding_vector($encoding); 306 307 $s = "array(\n"; 308 foreach ($ref as $char => $ucs) { 309 $s .= sprintf("0x%02X => 0x%04X,\n", ord($char), $ucs); 310 }; 311 $s .= ")"; 312 313 return trim($s); 314} 315 316function SaveToFile($file,$s,$mode='t') 317{ 318 $f=fopen($file,'w'.$mode); 319 if(!$f) 320 die('Can\'t write to file '.$file); 321 fwrite($f,$s,strlen($s)); 322 fclose($f); 323} 324 325function ReadShort($f) 326{ 327 $a=unpack('n1n',fread($f,2)); 328 return $a['n']; 329} 330 331function ReadLong($f) 332{ 333 $a=unpack('N1N',fread($f,4)); 334 return $a['N']; 335} 336 337function CheckTTF($file) 338{ 339 //Check if font license allows embedding 340 $f=fopen($file,'rb'); 341 if(!$f) 342 die('<B>Error:</B> Can\'t open '.$file); 343 //Extract number of tables 344 fseek($f,4,SEEK_CUR); 345 $nb=ReadShort($f); 346 fseek($f,6,SEEK_CUR); 347 //Seek OS/2 table 348 $found=false; 349 350 for ($i=0;$i<$nb;$i++) { 351 if (fread($f,4)=='OS/2') { 352 $found=true; 353 break; 354 } 355 fseek($f,12,SEEK_CUR); 356 }; 357 358 if (!$found) { 359 fclose($f); 360 return; 361 }; 362 363 fseek($f,4,SEEK_CUR); 364 $offset=ReadLong($f); 365 fseek($f,$offset,SEEK_SET); 366 367 //Extract fsType flags 368 fseek($f,8,SEEK_CUR); 369 $fsType=ReadShort($f); 370 $rl=($fsType & 0x02)!=0; 371 $pp=($fsType & 0x04)!=0; 372 $e=($fsType & 0x08)!=0; 373 fclose($f); 374 if ($rl and !$pp and !$e) { 375 echo '<B>Warning:</B> font license does not allow embedding'; 376 }; 377} 378 379/******************************************************************************* 380 * $fontfile : chemin du fichier TTF (ou cha�ne vide si pas d'incorporation) * 381 * $afmfile : chemin du fichier AFM * 382 * $enc : encodage (ou cha�ne vide si la police est symbolique) * 383 * $patch : patch optionnel pour l'encodage * 384 * $type : type de la police si $fontfile est vide * 385 *******************************************************************************/ 386function MakeFont($fontfile, $afmfile, $destdir, $destfile, $enc) { 387 // Generate a font definition file 388 set_magic_quotes_runtime(0); 389 ini_set('auto_detect_line_endings','1'); 390 391 $manager = ManagerEncoding::get(); 392 $map = $manager->get_encoding_glyphs($enc); 393 394 $fm = ReadAFM($afmfile, $map); 395 396 if (is_null($fm)) { 397 error_log(sprintf("Notice: Missing AFM file '%s'; attempting to parse font file '%s' directly", 398 $afmfile, 399 $fontfile)); 400 401 $fm = ReadTTF($fontfile, $manager->get_encoding_vector($enc)); 402 403 if (is_null($fm)) { 404 die(sprintf("Cannot get font metrics for '%s'", $fontfile)); 405 }; 406 } 407 408 $diff = MakeFontEncoding($map); 409 $cmap = MakeFontCMap($enc); 410 $fd = MakeFontDescriptor($fm,empty($map)); 411 412 //Find font type 413 if ($fontfile) { 414 $ext=strtolower(substr($fontfile,-3)); 415 if ($ext=='ttf') { 416 $type='TrueType'; 417 } elseif($ext=='pfb') { 418 $type='Type1'; 419 } else { 420 die('<B>Error:</B> unrecognized font file extension: '.$ext); 421 }; 422 } else { 423 if ($type!='TrueType' and $type!='Type1') { 424 die('<B>Error:</B> incorrect font type: '.$type); 425 }; 426 } 427 428 //Start generation 429 $s='<?php'."\n"; 430 $s.='$type=\''.$type."';\n"; 431 $s.='$name=\''.$fm['FontName']."';\n"; 432 $s.='$desc='.$fd.";\n"; 433 if (!isset($fm['UnderlinePosition'])) { 434 $fm['UnderlinePosition']=-100; 435 }; 436 if (!isset($fm['UnderlineThickness'])) { 437 $fm['UnderlineThickness']=50; 438 }; 439 $s.='$up='.$fm['UnderlinePosition'].";\n"; 440 $s.='$ut='.$fm['UnderlineThickness'].";\n"; 441 $w=MakeWidthArray($fm); 442 $s.='$cw='.$w.";\n"; 443 $s.='$enc=\''.$enc."';\n"; 444 $s.='$diff=\''.$diff."';\n"; 445 $s.='$cmap='.$cmap.";\n"; 446 447 $basename=substr(basename($afmfile),0,-4); 448 449 if ($fontfile) { 450 //Embedded font 451 if (!file_exists($fontfile)) { 452 die('<B>Error:</B> font file not found: '.$fontfile); 453 }; 454 455 if ($type=='TrueType') { 456 CheckTTF($fontfile); 457 }; 458 459 $f=fopen($fontfile,'rb'); 460 if (!$f) { 461 die('<B>Error:</B> Can\'t open '.$fontfile); 462 }; 463 464 $file=fread($f,filesize($fontfile)); 465 fclose($f); 466 if ($type=='Type1') { 467 //Find first two sections and discard third one 468 $header=(ord($file{0})==128); 469 if ($header) { 470 //Strip first binary header 471 $file=substr($file,6); 472 } 473 $pos=strpos($file,'eexec'); 474 if(!$pos) { 475 die('<B>Error:</B> font file does not seem to be valid Type1'); 476 }; 477 $size1=$pos+6; 478 if($header and ord($file{$size1})==128) { 479 //Strip second binary header 480 $file=substr($file,0,$size1).substr($file,$size1+6); 481 } 482 $pos=strpos($file,'00000000'); 483 if (!$pos) { 484 die('<B>Error:</B> font file does not seem to be valid Type1'); 485 }; 486 487 $size2=$pos-$size1; 488 $file=substr($file,0,$size1+$size2); 489 } 490 491 $gzcompress_exists = function_exists('gzcompress'); 492 if ($gzcompress_exists) { 493 $cmp = $basename.'.z'; 494 SaveToFile($destdir.$cmp, gzcompress($file), 'b'); 495 496 $s.='$file=\''.$cmp."';\n"; 497 } else { 498 $cmp = $basename.'.ttf'; 499 SaveToFile($destdir.$cmp, $file, 'b'); 500 501 $s.='$file=\''.basename($fontfile)."';\n"; 502 error_log('Notice: font file could not be compressed (zlib extension not available)'); 503 } 504 505 if ($type=='Type1') { 506 $s.='$size1='.$size1.";\n"; 507 $s.='$size2='.$size2.";\n"; 508 } else { 509 $s.='$originalsize='.filesize($fontfile).";\n"; 510 } 511 } else { 512 //Not embedded font 513 $s.='$file='."'';\n"; 514 } 515 516 $s.="?>\n"; 517 SaveToFile($destdir.$destfile,$s); 518} 519?> 520