TTFontFile

The WooCommerce PDF & Print TTFontFile class.

Defined (1)

The class is defined in the following location(s).

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