PageRenderTime 54ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/views/shared/items/font/unifont/ttfonts.php

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