ttfontsuni_analysis.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. <?php
  2. require_once __DIR__ . '/../MpdfException.php';
  3. require_once __DIR__ . '/ttfontsuni.php';
  4. class TTFontFile_Analysis extends TTFontFile
  5. {
  6. // Used to get font information from files in directory
  7. function extractCoreInfo($file, $TTCfontID = 0)
  8. {
  9. $this->filename = $file;
  10. $this->fh = fopen($file, 'rb');
  11. if (!$this->fh) {
  12. return ('ERROR - Can\'t open file ' . $file);
  13. }
  14. $this->_pos = 0;
  15. $this->charWidths = '';
  16. $this->glyphPos = array();
  17. $this->charToGlyph = array();
  18. $this->tables = array();
  19. $this->otables = array();
  20. $this->ascent = 0;
  21. $this->descent = 0;
  22. $this->numTTCFonts = 0;
  23. $this->TTCFonts = array();
  24. $this->version = $version = $this->read_ulong();
  25. $this->panose = array(); // mPDF 5.0
  26. if ($version == 0x4F54544F) {
  27. throw new MpdfException('ERROR - NOT ADDED as Postscript outlines are not supported - ' . $file);
  28. }
  29. if ($version == 0x74746366) {
  30. if ($TTCfontID > 0) {
  31. $this->version = $version = $this->read_ulong(); // TTC Header version now
  32. if (!in_array($version, array(0x00010000, 0x00020000))) {
  33. throw new MpdfException("ERROR - NOT ADDED as Error parsing TrueType Collection: version=" . $version . " - " . $file);
  34. }
  35. } else {
  36. throw new MpdfException("ERROR - Error parsing TrueType Collection - " . $file);
  37. }
  38. $this->numTTCFonts = $this->read_ulong();
  39. for ($i = 1; $i <= $this->numTTCFonts; $i++) {
  40. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  41. }
  42. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  43. $this->version = $version = $this->read_ulong(); // TTFont version again now
  44. $this->readTableDirectory(false);
  45. } else {
  46. if (!in_array($version, array(0x00010000, 0x74727565))) {
  47. throw new MpdfException("ERROR - NOT ADDED as Not a TrueType font: version=" . $version . " - " . $file);
  48. }
  49. $this->readTableDirectory(false);
  50. }
  51. /* Included for testing...
  52. $cmap_offset = $this->seek_table("cmap");
  53. $this->skip(2);
  54. $cmapTableCount = $this->read_ushort();
  55. $unicode_cmap_offset = 0;
  56. for ($i=0;$i<$cmapTableCount;$i++) {
  57. $x[$i]['platformId'] = $this->read_ushort();
  58. $x[$i]['encodingId'] = $this->read_ushort();
  59. $x[$i]['offset'] = $this->read_ulong();
  60. $save_pos = $this->_pos;
  61. $x[$i]['format'] = $this->get_ushort($cmap_offset + $x[$i]['offset'] );
  62. $this->seek($save_pos );
  63. }
  64. print_r($x); exit;
  65. */
  66. ///////////////////////////////////
  67. // name - Naming table
  68. ///////////////////////////////////
  69. /* Test purposes - displays table of names
  70. $name_offset = $this->seek_table("name");
  71. $format = $this->read_ushort();
  72. if ($format != 0 && $format != 1) // mPDF 5.3.73
  73. die("Unknown name table format ".$format);
  74. $numRecords = $this->read_ushort();
  75. $string_data_offset = $name_offset + $this->read_ushort();
  76. for ($i=0;$i<$numRecords; $i++) {
  77. $x[$i]['platformId'] = $this->read_ushort();
  78. $x[$i]['encodingId'] = $this->read_ushort();
  79. $x[$i]['languageId'] = $this->read_ushort();
  80. $x[$i]['nameId'] = $this->read_ushort();
  81. $x[$i]['length'] = $this->read_ushort();
  82. $x[$i]['offset'] = $this->read_ushort();
  83. $N = '';
  84. if ($x[$i]['platformId'] == 1 && $x[$i]['encodingId'] == 0 && $x[$i]['languageId'] == 0) { // Roman
  85. $opos = $this->_pos;
  86. $N = $this->get_chunk($string_data_offset + $x[$i]['offset'] , $x[$i]['length'] );
  87. $this->_pos = $opos;
  88. $this->seek($opos);
  89. }
  90. else { // Unicode
  91. $opos = $this->_pos;
  92. $this->seek($string_data_offset + $x[$i]['offset'] );
  93. $length = $x[$i]['length'] ;
  94. if ($length % 2 != 0)
  95. $length -= 1;
  96. // die("PostScript name is UTF-16BE string of odd length");
  97. $length /= 2;
  98. $N = '';
  99. while ($length > 0) {
  100. $char = $this->read_ushort();
  101. $N .= (chr($char));
  102. $length -= 1;
  103. }
  104. $this->_pos = $opos;
  105. $this->seek($opos);
  106. }
  107. $x[$i]['names'][$nameId] = $N;
  108. }
  109. print_r($x); exit;
  110. */
  111. $name_offset = $this->seek_table("name");
  112. $format = $this->read_ushort();
  113. if ($format != 0 && $format != 1) // mPDF 5.3.73
  114. throw new MpdfException("ERROR - NOT ADDED as Unknown name table format " . $format . " - " . $file);
  115. $numRecords = $this->read_ushort();
  116. $string_data_offset = $name_offset + $this->read_ushort();
  117. $names = array(1 => '', 2 => '', 3 => '', 4 => '', 6 => '');
  118. $K = array_keys($names);
  119. $nameCount = count($names);
  120. for ($i = 0; $i < $numRecords; $i++) {
  121. $platformId = $this->read_ushort();
  122. $encodingId = $this->read_ushort();
  123. $languageId = $this->read_ushort();
  124. $nameId = $this->read_ushort();
  125. $length = $this->read_ushort();
  126. $offset = $this->read_ushort();
  127. if (!in_array($nameId, $K))
  128. continue;
  129. $N = '';
  130. if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name
  131. $opos = $this->_pos;
  132. $this->seek($string_data_offset + $offset);
  133. if ($length % 2 != 0)
  134. $length += 1;
  135. $length /= 2;
  136. $N = '';
  137. while ($length > 0) {
  138. $char = $this->read_ushort();
  139. $N .= (chr($char));
  140. $length -= 1;
  141. }
  142. $this->_pos = $opos;
  143. $this->seek($opos);
  144. } else if ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name
  145. $opos = $this->_pos;
  146. $N = $this->get_chunk($string_data_offset + $offset, $length);
  147. $this->_pos = $opos;
  148. $this->seek($opos);
  149. }
  150. if ($N && $names[$nameId] == '') {
  151. $names[$nameId] = $N;
  152. $nameCount -= 1;
  153. if ($nameCount == 0)
  154. break;
  155. }
  156. }
  157. if ($names[6])
  158. $psName = preg_replace('/ /', '-', $names[6]);
  159. else if ($names[4])
  160. $psName = preg_replace('/ /', '-', $names[4]);
  161. else if ($names[1])
  162. $psName = preg_replace('/ /', '-', $names[1]);
  163. else
  164. $psName = '';
  165. if (!$names[1] && !$psName)
  166. throw new MpdfException("ERROR - NOT ADDED as Could not find valid font name - " . $file);
  167. $this->name = $psName;
  168. if ($names[1]) {
  169. $this->familyName = $names[1];
  170. } else {
  171. $this->familyName = $psName;
  172. }
  173. if ($names[2]) {
  174. $this->styleName = $names[2];
  175. } else {
  176. $this->styleName = 'Regular';
  177. }
  178. ///////////////////////////////////
  179. // head - Font header table
  180. ///////////////////////////////////
  181. $this->seek_table("head");
  182. $ver_maj = $this->read_ushort();
  183. $ver_min = $this->read_ushort();
  184. if ($ver_maj != 1)
  185. throw new MpdfException('ERROR - NOT ADDED as Unknown head table version ' . $ver_maj . '.' . $ver_min . " - " . $file);
  186. $this->fontRevision = $this->read_ushort() . $this->read_ushort();
  187. $this->skip(4);
  188. $magic = $this->read_ulong();
  189. if ($magic != 0x5F0F3CF5)
  190. throw new MpdfException('ERROR - NOT ADDED as Invalid head table magic ' . $magic . " - " . $file);
  191. $this->skip(2);
  192. $this->unitsPerEm = $unitsPerEm = $this->read_ushort();
  193. $scale = 1000 / $unitsPerEm;
  194. $this->skip(24);
  195. $macStyle = $this->read_short();
  196. $this->skip(4);
  197. $indexLocFormat = $this->read_short();
  198. ///////////////////////////////////
  199. // OS/2 - OS/2 and Windows metrics table
  200. ///////////////////////////////////
  201. $sFamily = '';
  202. $panose = '';
  203. $fsSelection = '';
  204. if (isset($this->tables["OS/2"])) {
  205. $this->seek_table("OS/2");
  206. $this->skip(30);
  207. $sF = $this->read_short();
  208. $sFamily = ($sF >> 8);
  209. $this->_pos += 10; //PANOSE = 10 byte length
  210. $panose = fread($this->fh, 10);
  211. $this->panose = array();
  212. for ($p = 0; $p < strlen($panose); $p++) {
  213. $this->panose[] = ord($panose[$p]);
  214. }
  215. $this->skip(20);
  216. $fsSelection = $this->read_short();
  217. }
  218. ///////////////////////////////////
  219. // post - PostScript table
  220. ///////////////////////////////////
  221. $this->seek_table("post");
  222. $this->skip(4);
  223. $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0;
  224. $this->skip(4);
  225. $isFixedPitch = $this->read_ulong();
  226. ///////////////////////////////////
  227. // cmap - Character to glyph index mapping table
  228. ///////////////////////////////////
  229. $cmap_offset = $this->seek_table("cmap");
  230. $this->skip(2);
  231. $cmapTableCount = $this->read_ushort();
  232. $unicode_cmap_offset = 0;
  233. for ($i = 0; $i < $cmapTableCount; $i++) {
  234. $platformID = $this->read_ushort();
  235. $encodingID = $this->read_ushort();
  236. $offset = $this->read_ulong();
  237. $save_pos = $this->_pos;
  238. if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
  239. $format = $this->get_ushort($cmap_offset + $offset);
  240. if ($format == 4) {
  241. if (!$unicode_cmap_offset)
  242. $unicode_cmap_offset = $cmap_offset + $offset;
  243. }
  244. }
  245. else if ((($platformID == 3 && $encodingID == 10) || $platformID == 0)) { // Microsoft, Unicode Format 12 table HKCS
  246. $format = $this->get_ushort($cmap_offset + $offset);
  247. if ($format == 12) {
  248. $unicode_cmap_offset = $cmap_offset + $offset;
  249. break;
  250. }
  251. }
  252. $this->seek($save_pos);
  253. }
  254. if (!$unicode_cmap_offset)
  255. throw new MpdfException('ERROR - Font (' . $this->filename . ') NOT ADDED as it is not Unicode encoded, and cannot be used by mPDF');
  256. $rtl = false;
  257. $indic = false;
  258. $cjk = false;
  259. $sip = false;
  260. $smp = false;
  261. $pua = false;
  262. $puaag = false;
  263. $glyphToChar = array();
  264. $unAGlyphs = '';
  265. // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
  266. if ($format == 12) {
  267. $this->seek($unicode_cmap_offset + 4);
  268. $length = $this->read_ulong();
  269. $limit = $unicode_cmap_offset + $length;
  270. $this->skip(4);
  271. $nGroups = $this->read_ulong();
  272. for ($i = 0; $i < $nGroups; $i++) {
  273. $startCharCode = $this->read_ulong();
  274. $endCharCode = $this->read_ulong();
  275. $startGlyphCode = $this->read_ulong();
  276. if (($endCharCode > 0x20000 && $endCharCode < 0x2A6DF) || ($endCharCode > 0x2F800 && $endCharCode < 0x2FA1F)) {
  277. $sip = true;
  278. }
  279. if ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) {
  280. $smp = true;
  281. }
  282. if (($endCharCode > 0x0590 && $endCharCode < 0x077F) || ($endCharCode > 0xFE70 && $endCharCode < 0xFEFF) || ($endCharCode > 0xFB50 && $endCharCode < 0xFDFF)) {
  283. $rtl = true;
  284. }
  285. if ($endCharCode > 0x0900 && $endCharCode < 0x0DFF) {
  286. $indic = true;
  287. }
  288. if ($endCharCode > 0xE000 && $endCharCode < 0xF8FF) {
  289. $pua = true;
  290. if ($endCharCode > 0xF500 && $endCharCode < 0xF7FF) {
  291. $puaag = true;
  292. }
  293. }
  294. if (($endCharCode > 0x2E80 && $endCharCode < 0x4DC0) || ($endCharCode > 0x4E00 && $endCharCode < 0xA4CF) || ($endCharCode > 0xAC00 && $endCharCode < 0xD7AF) || ($endCharCode > 0xF900 && $endCharCode < 0xFAFF) || ($endCharCode > 0xFE30 && $endCharCode < 0xFE4F)) {
  295. $cjk = true;
  296. }
  297. $offset = 0;
  298. // Get each glyphToChar - only point if going to analyse un-mapped Arabic Glyphs
  299. if (isset($this->tables['post'])) {
  300. for ($unichar = $startCharCode; $unichar <= $endCharCode; $unichar++) {
  301. $glyph = $startGlyphCode + $offset;
  302. $offset++;
  303. $glyphToChar[$glyph][] = $unichar;
  304. }
  305. }
  306. }
  307. } else { // Format 4 CMap
  308. $this->seek($unicode_cmap_offset + 2);
  309. $length = $this->read_ushort();
  310. $limit = $unicode_cmap_offset + $length;
  311. $this->skip(2);
  312. $segCount = $this->read_ushort() / 2;
  313. $this->skip(6);
  314. $endCount = array();
  315. for ($i = 0; $i < $segCount; $i++) {
  316. $endCount[] = $this->read_ushort();
  317. }
  318. $this->skip(2);
  319. $startCount = array();
  320. for ($i = 0; $i < $segCount; $i++) {
  321. $startCount[] = $this->read_ushort();
  322. }
  323. $idDelta = array();
  324. for ($i = 0; $i < $segCount; $i++) {
  325. $idDelta[] = $this->read_short();
  326. }
  327. $idRangeOffset_start = $this->_pos;
  328. $idRangeOffset = array();
  329. for ($i = 0; $i < $segCount; $i++) {
  330. $idRangeOffset[] = $this->read_ushort();
  331. }
  332. for ($n = 0; $n < $segCount; $n++) {
  333. if (($endCount[$n] > 0x0590 && $endCount[$n] < 0x077F) || ($endCount[$n] > 0xFE70 && $endCount[$n] < 0xFEFF) || ($endCount[$n] > 0xFB50 && $endCount[$n] < 0xFDFF)) {
  334. $rtl = true;
  335. }
  336. if ($endCount[$n] > 0x0900 && $endCount[$n] < 0x0DFF) {
  337. $indic = true;
  338. }
  339. if (($endCount[$n] > 0x2E80 && $endCount[$n] < 0x4DC0) || ($endCount[$n] > 0x4E00 && $endCount[$n] < 0xA4CF) || ($endCount[$n] > 0xAC00 && $endCount[$n] < 0xD7AF) || ($endCount[$n] > 0xF900 && $endCount[$n] < 0xFAFF) || ($endCount[$n] > 0xFE30 && $endCount[$n] < 0xFE4F)) {
  340. $cjk = true;
  341. }
  342. if ($endCount[$n] > 0xE000 && $endCount[$n] < 0xF8FF) {
  343. $pua = true;
  344. if ($endCount[$n] > 0xF500 && $endCount[$n] < 0xF7FF) {
  345. $puaag = true;
  346. }
  347. }
  348. // Get each glyphToChar - only point if going to analyse un-mapped Arabic Glyphs
  349. if (isset($this->tables['post'])) {
  350. $endpoint = ($endCount[$n] + 1);
  351. for ($unichar = $startCount[$n]; $unichar < $endpoint; $unichar++) {
  352. if ($idRangeOffset[$n] == 0)
  353. $glyph = ($unichar + $idDelta[$n]) & 0xFFFF;
  354. else {
  355. $offset = ($unichar - $startCount[$n]) * 2 + $idRangeOffset[$n];
  356. $offset = $idRangeOffset_start + 2 * $n + $offset;
  357. if ($offset >= $limit)
  358. $glyph = 0;
  359. else {
  360. $glyph = $this->get_ushort($offset);
  361. if ($glyph != 0)
  362. $glyph = ($glyph + $idDelta[$n]) & 0xFFFF;
  363. }
  364. }
  365. $glyphToChar[$glyph][] = $unichar;
  366. }
  367. }
  368. }
  369. }
  370. $bold = false;
  371. $italic = false;
  372. $ftype = '';
  373. if ($macStyle & (1 << 0)) {
  374. $bold = true;
  375. } // bit 0 bold
  376. else if ($fsSelection & (1 << 5)) {
  377. $bold = true;
  378. } // 5 BOLD Characters are emboldened
  379. if ($macStyle & (1 << 1)) {
  380. $italic = true;
  381. } // bit 1 italic
  382. else if ($fsSelection & (1 << 0)) {
  383. $italic = true;
  384. } // 0 ITALIC Font contains Italic characters, otherwise they are upright
  385. else if ($this->italicAngle <> 0) {
  386. $italic = true;
  387. }
  388. if ($isFixedPitch) {
  389. $ftype = 'mono';
  390. } else if ($sFamily > 0 && $sFamily < 8) {
  391. $ftype = 'serif';
  392. } else if ($sFamily == 8) {
  393. $ftype = 'sans';
  394. } else if ($sFamily == 10) {
  395. $ftype = 'cursive';
  396. }
  397. // Use PANOSE
  398. if ($panose) {
  399. $bFamilyType = ord($panose[0]);
  400. if ($bFamilyType == 2) {
  401. $bSerifStyle = ord($panose[1]);
  402. if (!$ftype) {
  403. if ($bSerifStyle > 1 && $bSerifStyle < 11) {
  404. $ftype = 'serif';
  405. } else if ($bSerifStyle > 10) {
  406. $ftype = 'sans';
  407. }
  408. }
  409. $bProportion = ord($panose[3]);
  410. if ($bProportion == 9 || $bProportion == 1) {
  411. $ftype = 'mono';
  412. } // ==1 i.e. No Fit needed for OCR-a and -b
  413. } else if ($bFamilyType == 3) {
  414. $ftype = 'cursive';
  415. }
  416. }
  417. fclose($this->fh);
  418. return array($this->familyName, $bold, $italic, $ftype, $TTCfontID, $rtl, $indic, $cjk, $sip, $smp, $puaag, $pua, $unAGlyphs);
  419. }
  420. }