You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4998 lines
184KB

  1. <?php
  2. require_once __DIR__ . '/../MpdfException.php';
  3. /*********************************************************************************
  4. * TTFontFile class *
  5. * *
  6. * Version: 4.01 *
  7. * Date: 2014-10-25 *
  8. * Author: Ian Back <ianb@bpm1.com> *
  9. * License: LGPL *
  10. * Copyright (c) Ian Back, 2010 *
  11. * This class is based on The ReportLab Open Source PDF library *
  12. * written in Python - http://www.reportlab.com/software/opensource/ *
  13. * together with ideas from the OpenOffice source code and others. *
  14. * This header must be retained in any redistribution or *
  15. * modification of the file. *
  16. * *
  17. ********************************************************************************/
  18. // NOTE*** If you change the defined constants below, be sure to delete all temporary font data files in /ttfontdata/
  19. // to force mPDF to regenerate cached font files.
  20. if (!defined('_OTL_OLD_SPEC_COMPAT_2'))
  21. define("_OTL_OLD_SPEC_COMPAT_2", true);
  22. // Define the value used in the "head" table of a created TTF file
  23. // 0x74727565 "true" for Mac
  24. // 0x00010000 for Windows
  25. // Either seems to work for a font embedded in a PDF file
  26. // when read by Adobe Reader on a Windows PC(!)
  27. if (!defined('_TTF_MAC_HEADER'))
  28. define("_TTF_MAC_HEADER", false);
  29. // Recalculate correct metadata/profiles when making subset fonts (not SIP/SMP)
  30. // e.g. xMin, xMax, maxNContours
  31. if (!defined('_RECALC_PROFILE'))
  32. define("_RECALC_PROFILE", false);
  33. // TrueType Font Glyph operators
  34. define("GF_WORDS", (1 << 0));
  35. define("GF_SCALE", (1 << 3));
  36. define("GF_MORE", (1 << 5));
  37. define("GF_XYSCALE", (1 << 6));
  38. define("GF_TWOBYTWO", (1 << 7));
  39. // mPDF 5.7.1
  40. if (!function_exists('unicode_hex')) {
  41. function unicode_hex($unicode_dec)
  42. {
  43. return (sprintf("%05s", strtoupper(dechex($unicode_dec))));
  44. }
  45. }
  46. class TTFontFile
  47. {
  48. var $GPOSFeatures; // mPDF 5.7.1
  49. var $GPOSLookups; // mPDF 5.7.1
  50. var $GPOSScriptLang; // mPDF 5.7.1
  51. var $MarkAttachmentType; // mPDF 5.7.1
  52. var $MarkGlyphSets; // mPDF 7.5.1
  53. var $GlyphClassMarks; // mPDF 5.7.1
  54. var $GlyphClassLigatures; // mPDF 5.7.1
  55. var $GlyphClassBases; // mPDF 5.7.1
  56. var $GlyphClassComponents; // mPDF 5.7.1
  57. var $GSUBScriptLang; // mPDF 5.7.1
  58. var $rtlPUAstr; // mPDF 5.7.1
  59. //var $rtlPUAarr; // mPDF 5.7.1
  60. var $fontkey; // mPDF 5.7.1
  61. var $useOTL; // mPDF 5.7.1 var $panose;
  62. var $maxUni;
  63. var $sFamilyClass;
  64. var $sFamilySubClass;
  65. var $sipset;
  66. var $smpset;
  67. var $_pos;
  68. var $numTables;
  69. var $searchRange;
  70. var $entrySelector;
  71. var $rangeShift;
  72. var $tables;
  73. var $otables;
  74. var $filename;
  75. var $fh;
  76. var $glyphPos;
  77. var $charToGlyph;
  78. var $ascent;
  79. var $descent;
  80. var $lineGap; // mPDF 6
  81. var $hheaascent;
  82. var $hheadescent;
  83. var $hhealineGap; // mPDF 6
  84. var $advanceWidthMax; // mPDF 6
  85. var $typoAscender; // mPDF 6
  86. var $typoDescender; // mPDF 6
  87. var $typoLineGap; // mPDF 6
  88. var $usWinAscent; // mPDF 6
  89. var $usWinDescent; // mPDF 6
  90. var $strikeoutSize;
  91. var $strikeoutPosition;
  92. var $name;
  93. var $familyName;
  94. var $styleName;
  95. var $fullName;
  96. var $uniqueFontID;
  97. var $unitsPerEm;
  98. var $bbox;
  99. var $capHeight;
  100. var $xHeight; // mPDF 6
  101. var $stemV;
  102. var $italicAngle;
  103. var $flags;
  104. var $underlinePosition;
  105. var $underlineThickness;
  106. var $charWidths;
  107. var $defaultWidth;
  108. var $maxStrLenRead;
  109. var $numTTCFonts;
  110. var $TTCFonts;
  111. var $maxUniChar;
  112. var $kerninfo;
  113. var $haskernGPOS;
  114. var $hassmallcapsGSUB;
  115. public function __construct()
  116. {
  117. $this->maxStrLenRead = 200000; // Maximum size of glyf table to read in as string (otherwise reads each glyph from file)
  118. }
  119. function getMetrics($file, $fontkey, $TTCfontID = 0, $debug = false, $BMPonly = false, $useOTL = 0)
  120. { // mPDF 5.7.1
  121. $this->useOTL = $useOTL; // mPDF 5.7.1
  122. $this->fontkey = $fontkey; // mPDF 5.7.1
  123. $this->filename = $file;
  124. $this->fh = fopen($file, 'rb');
  125. if (!$this->fh) {
  126. throw new MpdfException('Can\'t open file ' . $file);
  127. }
  128. $this->_pos = 0;
  129. $this->charWidths = '';
  130. $this->glyphPos = array();
  131. $this->charToGlyph = array();
  132. $this->tables = array();
  133. $this->otables = array();
  134. $this->kerninfo = array();
  135. $this->haskernGPOS = array();
  136. $this->hassmallcapsGSUB = array();
  137. $this->ascent = 0;
  138. $this->descent = 0;
  139. $this->lineGap = 0; // mPDF 6
  140. $this->hheaascent = 0; // mPDF 6
  141. $this->hheadescent = 0; // mPDF 6
  142. $this->hhealineGap = 0; // mPDF 6
  143. $this->xHeight = 0; // mPDF 6
  144. $this->capHeight = 0; // mPDF 6
  145. $this->panose = array();
  146. $this->sFamilyClass = 0;
  147. $this->sFamilySubClass = 0;
  148. $this->typoAscender = 0; // mPDF 6
  149. $this->typoDescender = 0; // mPDF 6
  150. $this->typoLineGap = 0; // mPDF 6
  151. $this->usWinAscent = 0; // mPDF 6
  152. $this->usWinDescent = 0; // mPDF 6
  153. $this->advanceWidthMax = 0; // mPDF 6
  154. $this->strikeoutSize = 0;
  155. $this->strikeoutPosition = 0;
  156. $this->numTTCFonts = 0;
  157. $this->TTCFonts = array();
  158. $this->version = $version = $this->read_ulong();
  159. $this->panose = array();
  160. if ($version == 0x4F54544F) {
  161. throw new MpdfException("Postscript outlines are not supported");
  162. }
  163. if ($version == 0x74746366 && !$TTCfontID) {
  164. throw new MpdfException("ERROR - You must define the TTCfontID for a TrueType Collection in config_fonts.php (" . $file . ")");
  165. }
  166. if (!in_array($version, array(0x00010000, 0x74727565)) && !$TTCfontID) {
  167. throw new MpdfException("Not a TrueType font: version=" . $version);
  168. }
  169. if ($TTCfontID > 0) {
  170. $this->version = $version = $this->read_ulong(); // TTC Header version now
  171. if (!in_array($version, array(0x00010000, 0x00020000))) {
  172. throw new MpdfException("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
  173. }
  174. $this->numTTCFonts = $this->read_ulong();
  175. for ($i = 1; $i <= $this->numTTCFonts; $i++) {
  176. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  177. }
  178. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  179. $this->version = $version = $this->read_ulong(); // TTFont version again now
  180. }
  181. $this->readTableDirectory($debug);
  182. $this->extractInfo($debug, $BMPonly, $useOTL);
  183. fclose($this->fh);
  184. }
  185. function readTableDirectory($debug = false)
  186. {
  187. $this->numTables = $this->read_ushort();
  188. $this->searchRange = $this->read_ushort();
  189. $this->entrySelector = $this->read_ushort();
  190. $this->rangeShift = $this->read_ushort();
  191. $this->tables = array();
  192. for ($i = 0; $i < $this->numTables; $i++) {
  193. $record = array();
  194. $record['tag'] = $this->read_tag();
  195. $record['checksum'] = array($this->read_ushort(), $this->read_ushort());
  196. $record['offset'] = $this->read_ulong();
  197. $record['length'] = $this->read_ulong();
  198. $this->tables[$record['tag']] = $record;
  199. }
  200. if ($debug)
  201. $this->checksumTables();
  202. }
  203. function checksumTables()
  204. {
  205. // Check the checksums for all tables
  206. foreach ($this->tables AS $t) {
  207. if ($t['length'] > 0 && $t['length'] < $this->maxStrLenRead) { // 1.02
  208. $table = $this->get_chunk($t['offset'], $t['length']);
  209. $checksum = $this->calcChecksum($table);
  210. if ($t['tag'] == 'head') {
  211. $up = unpack('n*', substr($table, 8, 4));
  212. $adjustment[0] = $up[1];
  213. $adjustment[1] = $up[2];
  214. $checksum = $this->sub32($checksum, $adjustment);
  215. }
  216. $xchecksum = $t['checksum'];
  217. if ($xchecksum != $checksum) {
  218. throw new MpdfException(sprintf('TTF file "%s": invalid checksum %s table: %s (expected %s)', $this->filename, dechex($checksum[0]) . dechex($checksum[1]), $t['tag'], dechex($xchecksum[0]) . dechex($xchecksum[1])));
  219. }
  220. }
  221. }
  222. }
  223. function sub32($x, $y)
  224. {
  225. $xlo = $x[1];
  226. $xhi = $x[0];
  227. $ylo = $y[1];
  228. $yhi = $y[0];
  229. if ($ylo > $xlo) {
  230. $xlo += 1 << 16;
  231. $yhi += 1;
  232. }
  233. $reslo = $xlo - $ylo;
  234. if ($yhi > $xhi) {
  235. $xhi += 1 << 16;
  236. }
  237. $reshi = $xhi - $yhi;
  238. $reshi = $reshi & 0xFFFF;
  239. return array($reshi, $reslo);
  240. }
  241. function calcChecksum($data)
  242. {
  243. if (strlen($data) % 4) {
  244. $data .= str_repeat("\0", (4 - (strlen($data) % 4)));
  245. }
  246. $len = strlen($data);
  247. $hi = 0x0000;
  248. $lo = 0x0000;
  249. for ($i = 0; $i < $len; $i+=4) {
  250. $hi += (ord($data[$i]) << 8) + ord($data[$i + 1]);
  251. $lo += (ord($data[$i + 2]) << 8) + ord($data[$i + 3]);
  252. $hi += ($lo >> 16) & 0xFFFF;
  253. $lo = $lo & 0xFFFF;
  254. }
  255. $hi = $hi & 0xFFFF; // mPDF 5.7.1
  256. return array($hi, $lo);
  257. }
  258. function get_table_pos($tag)
  259. {
  260. if (!isset($this->tables[$tag])) {
  261. return array(0, 0);
  262. }
  263. $offset = $this->tables[$tag]['offset'];
  264. $length = $this->tables[$tag]['length'];
  265. return array($offset, $length);
  266. }
  267. function seek($pos)
  268. {
  269. $this->_pos = $pos;
  270. fseek($this->fh, $this->_pos);
  271. }
  272. function skip($delta)
  273. {
  274. $this->_pos = $this->_pos + $delta;
  275. fseek($this->fh, $delta, SEEK_CUR);
  276. }
  277. function seek_table($tag, $offset_in_table = 0)
  278. {
  279. $tpos = $this->get_table_pos($tag);
  280. $this->_pos = $tpos[0] + $offset_in_table;
  281. fseek($this->fh, $this->_pos);
  282. return $this->_pos;
  283. }
  284. function read_tag()
  285. {
  286. $this->_pos += 4;
  287. return fread($this->fh, 4);
  288. }
  289. function read_short()
  290. {
  291. $this->_pos += 2;
  292. $s = fread($this->fh, 2);
  293. $a = (ord($s[0]) << 8) + ord($s[1]);
  294. if ($a & (1 << 15)) {
  295. $a = ($a - (1 << 16));
  296. }
  297. return $a;
  298. }
  299. function unpack_short($s)
  300. {
  301. $a = (ord($s[0]) << 8) + ord($s[1]);
  302. if ($a & (1 << 15)) {
  303. $a = ($a - (1 << 16));
  304. }
  305. return $a;
  306. }
  307. function read_ushort()
  308. {
  309. $this->_pos += 2;
  310. $s = fread($this->fh, 2);
  311. return (ord($s[0]) << 8) + ord($s[1]);
  312. }
  313. function read_ulong()
  314. {
  315. $this->_pos += 4;
  316. $s = fread($this->fh, 4);
  317. // if large uInt32 as an integer, PHP converts it to -ve
  318. return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 16777216 = 1<<24
  319. }
  320. function get_ushort($pos)
  321. {
  322. fseek($this->fh, $pos);
  323. $s = fread($this->fh, 2);
  324. return (ord($s[0]) << 8) + ord($s[1]);
  325. }
  326. function get_ulong($pos)
  327. {
  328. fseek($this->fh, $pos);
  329. $s = fread($this->fh, 4);
  330. // iF large uInt32 as an integer, PHP converts it to -ve
  331. return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 16777216 = 1<<24
  332. }
  333. function pack_short($val)
  334. {
  335. if ($val < 0) {
  336. $val = abs($val);
  337. $val = ~$val;
  338. $val += 1;
  339. }
  340. return pack("n", $val);
  341. }
  342. function splice($stream, $offset, $value)
  343. {
  344. return substr($stream, 0, $offset) . $value . substr($stream, $offset + strlen($value));
  345. }
  346. function _set_ushort($stream, $offset, $value)
  347. {
  348. $up = pack("n", $value);
  349. return $this->splice($stream, $offset, $up);
  350. }
  351. function _set_short($stream, $offset, $val)
  352. {
  353. if ($val < 0) {
  354. $val = abs($val);
  355. $val = ~$val;
  356. $val += 1;
  357. }
  358. $up = pack("n", $val);
  359. return $this->splice($stream, $offset, $up);
  360. }
  361. function get_chunk($pos, $length)
  362. {
  363. fseek($this->fh, $pos);
  364. if ($length < 1) {
  365. return '';
  366. }
  367. return (fread($this->fh, $length));
  368. }
  369. function get_table($tag)
  370. {
  371. list($pos, $length) = $this->get_table_pos($tag);
  372. if ($length == 0) {
  373. return '';
  374. }
  375. fseek($this->fh, $pos);
  376. return (fread($this->fh, $length));
  377. }
  378. function add($tag, $data)
  379. {
  380. if ($tag == 'head') {
  381. $data = $this->splice($data, 8, "\0\0\0\0");
  382. }
  383. $this->otables[$tag] = $data;
  384. }
  385. /////////////////////////////////////////////////////////////////////////////////////////
  386. function getCTG($file, $TTCfontID = 0, $debug = false, $useOTL = false)
  387. { // mPDF 5.7.1
  388. // Only called if font is not to be used as embedded subset i.e. NOT called for SIP/SMP fonts
  389. $this->useOTL = $useOTL; // mPDF 5.7.1
  390. $this->filename = $file;
  391. $this->fh = fopen($file, 'rb');
  392. if (!$this->fh) {
  393. throw new MpdfException('Can\'t open file ' . $file);
  394. }
  395. $this->_pos = 0;
  396. $this->charWidths = '';
  397. $this->glyphPos = array();
  398. $this->charToGlyph = array();
  399. $this->tables = array();
  400. $this->numTTCFonts = 0;
  401. $this->TTCFonts = array();
  402. $this->skip(4);
  403. if ($TTCfontID > 0) {
  404. $this->version = $version = $this->read_ulong(); // TTC Header version now
  405. if (!in_array($version, array(0x00010000, 0x00020000))) {
  406. throw new MpdfException("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
  407. }
  408. $this->numTTCFonts = $this->read_ulong();
  409. for ($i = 1; $i <= $this->numTTCFonts; $i++) {
  410. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  411. }
  412. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  413. $this->version = $version = $this->read_ulong(); // TTFont version again now
  414. }
  415. $this->readTableDirectory($debug);
  416. // cmap - Character to glyph index mapping table
  417. $cmap_offset = $this->seek_table("cmap");
  418. $this->skip(2);
  419. $cmapTableCount = $this->read_ushort();
  420. $unicode_cmap_offset = 0;
  421. for ($i = 0; $i < $cmapTableCount; $i++) {
  422. $platformID = $this->read_ushort();
  423. $encodingID = $this->read_ushort();
  424. $offset = $this->read_ulong();
  425. $save_pos = $this->_pos;
  426. if ($platformID == 3 && $encodingID == 1) { // Microsoft, Unicode
  427. $format = $this->get_ushort($cmap_offset + $offset);
  428. if ($format == 4) {
  429. $unicode_cmap_offset = $cmap_offset + $offset;
  430. break;
  431. }
  432. } else if ($platformID == 0) { // Unicode -- assume all encodings are compatible
  433. $format = $this->get_ushort($cmap_offset + $offset);
  434. if ($format == 4) {
  435. $unicode_cmap_offset = $cmap_offset + $offset;
  436. break;
  437. }
  438. }
  439. $this->seek($save_pos);
  440. }
  441. $glyphToChar = array();
  442. $charToGlyph = array();
  443. $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph);
  444. ///////////////////////////////////
  445. // mPDF 5.7.1
  446. // Map Unmapped glyphs - from $numGlyphs
  447. if ($useOTL) {
  448. $this->seek_table("maxp");
  449. $this->skip(4);
  450. $numGlyphs = $this->read_ushort();
  451. $bctr = 0xE000;
  452. for ($gid = 1; $gid < $numGlyphs; $gid++) {
  453. if (!isset($glyphToChar[$gid])) {
  454. while (isset($charToGlyph[$bctr])) {
  455. $bctr++;
  456. } // Avoid overwriting a glyph already mapped in PUA
  457. if ($bctr > 0xF8FF) {
  458. throw new MpdfException($file . " : WARNING - Font cannot map all included glyphs into Private Use Area U+E000 - U+F8FF; cannot use useOTL on this font");
  459. }
  460. $glyphToChar[$gid][] = $bctr;
  461. $charToGlyph[$bctr] = $gid;
  462. $bctr++;
  463. }
  464. }
  465. }
  466. ///////////////////////////////////
  467. fclose($this->fh);
  468. return ($charToGlyph);
  469. }
  470. /////////////////////////////////////////////////////////////////////////////////////////
  471. function getTTCFonts($file)
  472. {
  473. $this->filename = $file;
  474. $this->fh = fopen($file, 'rb');
  475. if (!$this->fh) {
  476. return ('ERROR - Can\'t open file ' . $file);
  477. }
  478. $this->numTTCFonts = 0;
  479. $this->TTCFonts = array();
  480. $this->version = $version = $this->read_ulong();
  481. if ($version == 0x74746366) {
  482. $this->version = $version = $this->read_ulong(); // TTC Header version now
  483. if (!in_array($version, array(0x00010000, 0x00020000)))
  484. return("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
  485. }
  486. else {
  487. return("ERROR - Not a TrueType Collection: version=" . $version . " - " . $file);
  488. }
  489. $this->numTTCFonts = $this->read_ulong();
  490. for ($i = 1; $i <= $this->numTTCFonts; $i++) {
  491. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  492. }
  493. }
  494. /////////////////////////////////////////////////////////////////////////////////////////
  495. /////////////////////////////////////////////////////////////////////////////////////////
  496. function extractInfo($debug = false, $BMPonly = false, $useOTL = 0)
  497. {
  498. // Values are all set to 0 or blank at start of getMetrics
  499. ///////////////////////////////////
  500. // name - Naming table
  501. ///////////////////////////////////
  502. $name_offset = $this->seek_table("name");
  503. $format = $this->read_ushort();
  504. if ($format != 0 && $format != 1)
  505. throw new MpdfException("Unknown name table format " . $format);
  506. $numRecords = $this->read_ushort();
  507. $string_data_offset = $name_offset + $this->read_ushort();
  508. $names = array(1 => '', 2 => '', 3 => '', 4 => '', 6 => '');
  509. $K = array_keys($names);
  510. $nameCount = count($names);
  511. for ($i = 0; $i < $numRecords; $i++) {
  512. $platformId = $this->read_ushort();
  513. $encodingId = $this->read_ushort();
  514. $languageId = $this->read_ushort();
  515. $nameId = $this->read_ushort();
  516. $length = $this->read_ushort();
  517. $offset = $this->read_ushort();
  518. if (!in_array($nameId, $K))
  519. continue;
  520. $N = '';
  521. if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name
  522. $opos = $this->_pos;
  523. $this->seek($string_data_offset + $offset);
  524. if ($length % 2 != 0)
  525. throw new MpdfException("PostScript name is UTF-16BE string of odd length");
  526. $length /= 2;
  527. $N = '';
  528. while ($length > 0) {
  529. $char = $this->read_ushort();
  530. $N .= (chr($char));
  531. $length -= 1;
  532. }
  533. $this->_pos = $opos;
  534. $this->seek($opos);
  535. } else if ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name
  536. $opos = $this->_pos;
  537. $N = $this->get_chunk($string_data_offset + $offset, $length);
  538. $this->_pos = $opos;
  539. $this->seek($opos);
  540. }
  541. if ($N && $names[$nameId] == '') {
  542. $names[$nameId] = $N;
  543. $nameCount -= 1;
  544. if ($nameCount == 0)
  545. break;
  546. }
  547. }
  548. if ($names[6])
  549. $psName = $names[6];
  550. else if ($names[4])
  551. $psName = preg_replace('/ /', '-', $names[4]);
  552. else if ($names[1])
  553. $psName = preg_replace('/ /', '-', $names[1]);
  554. else
  555. $psName = '';
  556. if (!$psName)
  557. throw new MpdfException("Could not find PostScript font name: " . $this->filename);
  558. // CHECK IF psName valid (PadaukBook contains illegal characters in Name ID 6 i.e. Postscript Name)
  559. $psNameInvalid = false;
  560. for ($i = 0; $i < count($psName); $i++) {
  561. $c = $psName[$i];
  562. $oc = ord($c);
  563. if ($oc > 126 || strpos(' [](){}<>/%', $c) !== false) {
  564. //throw new MpdfException("psName=".$psName." contains invalid character ".$c." ie U+".ord(c));
  565. $psNameInvalid = true;
  566. break;
  567. }
  568. }
  569. if ($psNameInvalid && $names[4])
  570. $psName = preg_replace('/ /', '-', $names[4]);
  571. $this->name = $psName;
  572. if ($names[1]) {
  573. $this->familyName = $names[1];
  574. } else {
  575. $this->familyName = $psName;
  576. }
  577. if ($names[2]) {
  578. $this->styleName = $names[2];
  579. } else {
  580. $this->styleName = 'Regular';
  581. }
  582. if ($names[4]) {
  583. $this->fullName = $names[4];
  584. } else {
  585. $this->fullName = $psName;
  586. }
  587. if ($names[3]) {
  588. $this->uniqueFontID = $names[3];
  589. } else {
  590. $this->uniqueFontID = $psName;
  591. }
  592. if (!$psNameInvalid && $names[6]) {
  593. $this->fullName = $names[6];
  594. }
  595. ///////////////////////////////////
  596. // head - Font header table
  597. ///////////////////////////////////
  598. $this->seek_table("head");
  599. if ($debug) {
  600. $ver_maj = $this->read_ushort();
  601. $ver_min = $this->read_ushort();
  602. if ($ver_maj != 1)
  603. throw new MpdfException('Unknown head table version ' . $ver_maj . '.' . $ver_min);
  604. $this->fontRevision = $this->read_ushort() . $this->read_ushort();
  605. $this->skip(4);
  606. $magic = $this->read_ulong();
  607. if ($magic != 0x5F0F3CF5)
  608. throw new MpdfException('Invalid head table magic ' . $magic);
  609. $this->skip(2);
  610. }
  611. else {
  612. $this->skip(18);
  613. }
  614. $this->unitsPerEm = $unitsPerEm = $this->read_ushort();
  615. $scale = 1000 / $unitsPerEm;
  616. $this->skip(16);
  617. $xMin = $this->read_short();
  618. $yMin = $this->read_short();
  619. $xMax = $this->read_short();
  620. $yMax = $this->read_short();
  621. $this->bbox = array(($xMin * $scale), ($yMin * $scale), ($xMax * $scale), ($yMax * $scale));
  622. $this->skip(3 * 2);
  623. $indexToLocFormat = $this->read_ushort();
  624. $glyphDataFormat = $this->read_ushort();
  625. if ($glyphDataFormat != 0) {
  626. throw new MpdfException('Unknown glyph data format ' . $glyphDataFormat);
  627. }
  628. ///////////////////////////////////
  629. // hhea metrics table
  630. ///////////////////////////////////
  631. if (isset($this->tables["hhea"])) {
  632. $this->seek_table("hhea");
  633. $this->skip(4);
  634. $hheaAscender = $this->read_short();
  635. $hheaDescender = $this->read_short();
  636. $hheaLineGap = $this->read_short(); // mPDF 6
  637. $hheaAdvanceWidthMax = $this->read_ushort(); // mPDF 6
  638. $this->hheaascent = ($hheaAscender * $scale);
  639. $this->hheadescent = ($hheaDescender * $scale);
  640. $this->hhealineGap = ($hheaLineGap * $scale); // mPDF 6
  641. $this->advanceWidthMax = ($hheaAdvanceWidthMax * $scale); // mPDF 6
  642. }
  643. ///////////////////////////////////
  644. // OS/2 - OS/2 and Windows metrics table
  645. ///////////////////////////////////
  646. $use_typo_metrics = false;
  647. if (isset($this->tables["OS/2"])) {
  648. $this->seek_table("OS/2");
  649. $version = $this->read_ushort();
  650. $this->skip(2);
  651. $usWeightClass = $this->read_ushort();
  652. $this->skip(2);
  653. $fsType = $this->read_ushort();
  654. if ($fsType == 0x0002 || ($fsType & 0x0300) != 0) {
  655. $this->restrictedUse = true;
  656. }
  657. // mPDF 6
  658. $this->skip(16);
  659. $yStrikeoutSize = $this->read_short();
  660. $yStrikeoutPosition = $this->read_short();
  661. $this->strikeoutSize = ($yStrikeoutSize * $scale);
  662. $this->strikeoutPosition = ($yStrikeoutPosition * $scale);
  663. $sF = $this->read_short();
  664. $this->sFamilyClass = ($sF >> 8);
  665. $this->sFamilySubClass = ($sF & 0xFF);
  666. $this->_pos += 10; //PANOSE = 10 byte length
  667. $panose = fread($this->fh, 10);
  668. $this->panose = array();
  669. for ($p = 0; $p < strlen($panose); $p++) {
  670. $this->panose[] = ord($panose[$p]);
  671. }
  672. //$this->skip(26);
  673. // mPDF 6
  674. $this->skip(20);
  675. $fsSelection = $this->read_ushort();
  676. $use_typo_metrics = (($fsSelection & 0x80) == 0x80); // bit#7 = USE_TYPO_METRICS
  677. $this->skip(4);
  678. $sTypoAscender = $this->read_short();
  679. $sTypoDescender = $this->read_short();
  680. $sTypoLineGap = $this->read_short(); // mPDF 6
  681. if ($sTypoAscender)
  682. $this->typoAscender = ($sTypoAscender * $scale); // mPDF 6
  683. if ($sTypoDescender)
  684. $this->typoDescender = ($sTypoDescender * $scale); // mPDF 6
  685. if ($sTypoLineGap)
  686. $this->typoLineGap = ($sTypoLineGap * $scale); // mPDF 6
  687. $usWinAscent = $this->read_ushort(); // mPDF 6
  688. $usWinDescent = $this->read_ushort(); // mPDF 6
  689. if ($usWinAscent)
  690. $this->usWinAscent = ($usWinAscent * $scale); // mPDF 6
  691. if ($usWinDescent)
  692. $this->usWinDescent = ($usWinDescent * $scale); // mPDF 6
  693. if ($version > 1) {
  694. $this->skip(8); // mPDF 6
  695. $sxHeight = $this->read_short();
  696. $this->xHeight = ($sxHeight * $scale);
  697. $sCapHeight = $this->read_short();
  698. $this->capHeight = ($sCapHeight * $scale);
  699. }
  700. } else {
  701. $usWeightClass = 400;
  702. }
  703. $this->stemV = 50 + intval(pow(($usWeightClass / 65.0), 2));
  704. // FONT DESCRIPTOR METRICS
  705. if (_FONT_DESCRIPTOR == 'winTypo') {
  706. $this->ascent = $this->typoAscender;
  707. $this->descent = $this->typoDescender;
  708. $this->lineGap = $this->typoLineGap;
  709. } else if (_FONT_DESCRIPTOR == 'mac') {
  710. $this->ascent = $this->hheaascent;
  711. $this->descent = $this->hheadescent;
  712. $this->lineGap = $this->hhealineGap;
  713. } else { // if (_FONT_DESCRIPTOR == 'win') { // default
  714. $this->ascent = $this->usWinAscent;
  715. $this->descent = -$this->usWinDescent;
  716. $this->lineGap = 0;
  717. /* Special case - if either the winAscent or winDescent are greater than the
  718. font bounding box yMin yMax, then reduce them accordingly.
  719. This works with Myanmar Text (Windows 8 version) to give a
  720. line-height normal that is equivalent to that produced in browsers.
  721. Also Khmer OS = compatible with MSWord, Wordpad and browser. */
  722. if ($this->ascent > $this->bbox[3]) {
  723. $this->ascent = $this->bbox[3];
  724. }
  725. if ($this->descent < $this->bbox[1]) {
  726. $this->descent = $this->bbox[1];
  727. }
  728. /* Override case - if the USE_TYPO_METRICS bit is set on OS/2 fsSelection
  729. this is telling the font to use the sTypo values and not the usWinAscent values.
  730. This works as a fix with Cambria Math to give a normal line-height;
  731. at present, this is the only font I have found with this bit set;
  732. although note that MS WordPad and windows FF browser uses the big line-height from winAscent
  733. but Word 2007 get it right . */
  734. if ($use_typo_metrics && $this->typoAscender) {
  735. $this->ascent = $this->typoAscender;
  736. $this->descent = $this->typoDescender;
  737. $this->lineGap = $this->typoLineGap;
  738. }
  739. }
  740. ///////////////////////////////////
  741. // post - PostScript table
  742. ///////////////////////////////////
  743. $this->seek_table("post");
  744. if ($debug) {
  745. $ver_maj = $this->read_ushort();
  746. $ver_min = $this->read_ushort();
  747. if ($ver_maj < 1 || $ver_maj > 4)
  748. throw new MpdfException('Unknown post table version ' . $ver_maj);
  749. }
  750. else {
  751. $this->skip(4);
  752. }
  753. $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0;
  754. $this->underlinePosition = $this->read_short() * $scale;
  755. $this->underlineThickness = $this->read_short() * $scale;
  756. $isFixedPitch = $this->read_ulong();
  757. $this->flags = 4;
  758. if ($this->italicAngle != 0)
  759. $this->flags = $this->flags | 64;
  760. if ($usWeightClass >= 600)
  761. $this->flags = $this->flags | 262144;
  762. if ($isFixedPitch)
  763. $this->flags = $this->flags | 1;
  764. ///////////////////////////////////
  765. // hhea - Horizontal header table
  766. ///////////////////////////////////
  767. $this->seek_table("hhea");
  768. if ($debug) {
  769. $ver_maj = $this->read_ushort();
  770. $ver_min = $this->read_ushort();
  771. if ($ver_maj != 1)
  772. throw new MpdfException('Unknown hhea table version ' . $ver_maj);
  773. $this->skip(28);
  774. }
  775. else {
  776. $this->skip(32);
  777. }
  778. $metricDataFormat = $this->read_ushort();
  779. if ($metricDataFormat != 0) {
  780. throw new MpdfException('Unknown horizontal metric data format ' . $metricDataFormat);
  781. }
  782. $numberOfHMetrics = $this->read_ushort();
  783. if ($numberOfHMetrics == 0) {
  784. throw new MpdfException('Number of horizontal metrics is 0');
  785. }
  786. ///////////////////////////////////
  787. // maxp - Maximum profile table
  788. ///////////////////////////////////
  789. $this->seek_table("maxp");
  790. if ($debug) {
  791. $ver_maj = $this->read_ushort();
  792. $ver_min = $this->read_ushort();
  793. if ($ver_maj != 1) {
  794. throw new MpdfException('Unknown maxp table version ' . $ver_maj);
  795. }
  796. }
  797. else {
  798. $this->skip(4);
  799. }
  800. $numGlyphs = $this->read_ushort();
  801. ///////////////////////////////////
  802. // cmap - Character to glyph index mapping table
  803. ///////////////////////////////////
  804. $cmap_offset = $this->seek_table("cmap");
  805. $this->skip(2);
  806. $cmapTableCount = $this->read_ushort();
  807. $unicode_cmap_offset = 0;
  808. for ($i = 0; $i < $cmapTableCount; $i++) {
  809. $platformID = $this->read_ushort();
  810. $encodingID = $this->read_ushort();
  811. $offset = $this->read_ulong();
  812. $save_pos = $this->_pos;
  813. if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
  814. $format = $this->get_ushort($cmap_offset + $offset);
  815. if ($format == 4) {
  816. if (!$unicode_cmap_offset)
  817. $unicode_cmap_offset = $cmap_offset + $offset;
  818. if ($BMPonly)
  819. break;
  820. }
  821. }
  822. // Microsoft, Unicode Format 12 table HKCS
  823. else if ((($platformID == 3 && $encodingID == 10) || $platformID == 0) && !$BMPonly) {
  824. $format = $this->get_ushort($cmap_offset + $offset);
  825. if ($format == 12) {
  826. $unicode_cmap_offset = $cmap_offset + $offset;
  827. break;
  828. }
  829. }
  830. $this->seek($save_pos);
  831. }
  832. if (!$unicode_cmap_offset) {
  833. throw new MpdfException('Font (' . $this->filename . ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
  834. }
  835. $sipset = false;
  836. $smpset = false;
  837. // mPDF 5.7.1
  838. $this->rtlPUAstr = '';
  839. //$this->rtlPUAarr = array();
  840. $this->GSUBScriptLang = array();
  841. $this->GSUBFeatures = array();
  842. $this->GSUBLookups = array();
  843. $this->GPOSScriptLang = array();
  844. $this->GPOSFeatures = array();
  845. $this->GPOSLookups = array();
  846. $this->glyphIDtoUni = '';
  847. // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
  848. if ($format == 12 && !$BMPonly) {
  849. $this->maxUniChar = 0;
  850. $this->seek($unicode_cmap_offset + 4);
  851. $length = $this->read_ulong();
  852. $limit = $unicode_cmap_offset + $length;
  853. $this->skip(4);
  854. $nGroups = $this->read_ulong();
  855. $glyphToChar = array();
  856. $charToGlyph = array();
  857. for ($i = 0; $i < $nGroups; $i++) {
  858. $startCharCode = $this->read_ulong();
  859. $endCharCode = $this->read_ulong();
  860. $startGlyphCode = $this->read_ulong();
  861. // ZZZ98
  862. if ($endCharCode > 0x20000 && $endCharCode < 0x2FFFF) {
  863. $sipset = true;
  864. } else if ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) {
  865. $smpset = true;
  866. }
  867. $offset = 0;
  868. for ($unichar = $startCharCode; $unichar <= $endCharCode; $unichar++) {
  869. $glyph = $startGlyphCode + $offset;
  870. $offset++;
  871. // ZZZ98
  872. if ($unichar < 0x30000) {
  873. $charToGlyph[$unichar] = $glyph;
  874. $this->maxUniChar = max($unichar, $this->maxUniChar);
  875. $glyphToChar[$glyph][] = $unichar;
  876. }
  877. }
  878. }
  879. } else {
  880. $glyphToChar = array();
  881. $charToGlyph = array();
  882. $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph);
  883. }
  884. $this->sipset = $sipset;
  885. $this->smpset = $smpset;
  886. ///////////////////////////////////
  887. // mPDF 5.7.1
  888. // Map Unmapped glyphs (or glyphs mapped to upper PUA U+F00000 onwards i.e. > U+2FFFF) - from $numGlyphs
  889. if ($this->useOTL) {
  890. $bctr = 0xE000;
  891. for ($gid = 1; $gid < $numGlyphs; $gid++) {
  892. if (!isset($glyphToChar[$gid])) {
  893. while (isset($charToGlyph[$bctr])) {
  894. $bctr++;
  895. } // Avoid overwriting a glyph already mapped in PUA
  896. // ZZZ98
  897. if (($bctr > 0xF8FF) && ($bctr < 0x2CEB0)) {
  898. if (!$BMPonly) {
  899. $bctr = 0x2CEB0; // Use unassigned area 0x2CEB0 to 0x2F7FF (space for 10,000 characters)
  900. $this->sipset = $sipset = true; // forces subsetting; also ensure charwidths are saved
  901. while (isset($charToGlyph[$bctr])) {
  902. $bctr++;
  903. }
  904. } else {
  905. throw new MpdfException($names[1] . " : WARNING - The font does not have enough space to map all (unmapped) included glyphs into Private Use Area U+E000 - U+F8FF");
  906. }
  907. }
  908. $glyphToChar[$gid][] = $bctr;
  909. $charToGlyph[$bctr] = $gid;
  910. $this->maxUniChar = max($bctr, $this->maxUniChar);
  911. $bctr++;
  912. }
  913. }
  914. }
  915. $this->glyphToChar = $glyphToChar;
  916. ///////////////////////////////////
  917. // mPDF 5.7.1 OpenType Layout tables
  918. $this->GSUBScriptLang = array();
  919. $this->rtlPUAstr = '';
  920. //$this->rtlPUAarr = array();
  921. if ($useOTL) {
  922. $this->_getGDEFtables();
  923. list($this->GSUBScriptLang, $this->GSUBFeatures, $this->GSUBLookups, $this->rtlPUAstr) = $this->_getGSUBtables();
  924. // , $this->rtlPUAarr not needed
  925. list($this->GPOSScriptLang, $this->GPOSFeatures, $this->GPOSLookups) = $this->_getGPOStables();
  926. $this->glyphIDtoUni = str_pad('', 256 * 256 * 3, "\x00");
  927. foreach ($glyphToChar AS $gid => $arr) {
  928. if (isset($glyphToChar[$gid][0])) {
  929. $char = $glyphToChar[$gid][0];
  930. if ($char != 0 && $char != 65535) {
  931. $this->glyphIDtoUni[$gid * 3] = chr($char >> 16);
  932. $this->glyphIDtoUni[$gid * 3 + 1] = chr(($char >> 8) & 0xFF);
  933. $this->glyphIDtoUni[$gid * 3 + 2] = chr($char & 0xFF);
  934. }
  935. }
  936. }
  937. }
  938. ///////////////////////////////////
  939. // if xHeight and/or CapHeight are not available from OS/2 (e.g. eraly versions)
  940. // Calculate from yMax of 'x' or 'H' Glyphs...
  941. if ($this->xHeight == 0) {
  942. if (isset($charToGlyph[0x78])) {
  943. $gidx = $charToGlyph[0x78]; // U+0078 (LATIN SMALL LETTER X)
  944. $start = $this->seek_table('loca');
  945. if ($indexToLocFormat == 0) {
  946. $this->skip($gidx * 2);
  947. $locax = $this->read_ushort() * 2;
  948. } else if ($indexToLocFormat == 1) {
  949. $this->skip($gidx * 4);
  950. $locax = $this->read_ulong();
  951. }
  952. $start = $this->seek_table('glyf');
  953. $this->skip($locax);
  954. $this->skip(8);
  955. $yMaxx = $this->read_short();
  956. $this->xHeight = $yMaxx * $scale;
  957. }
  958. }
  959. if ($this->capHeight == 0) {
  960. if (isset($charToGlyph[0x48])) {
  961. $gidH = $charToGlyph[0x48]; // U+0048 (LATIN CAPITAL LETTER H)
  962. $start = $this->seek_table('loca');
  963. if ($indexToLocFormat == 0) {
  964. $this->skip($gidH * 2);
  965. $locaH = $this->read_ushort() * 2;
  966. } else if ($indexToLocFormat == 1) {
  967. $this->skip($gidH * 4);
  968. $locaH = $this->read_ulong();
  969. }
  970. $start = $this->seek_table('glyf');
  971. $this->skip($locaH);
  972. $this->skip(8);
  973. $yMaxH = $this->read_short();
  974. $this->capHeight = $yMaxH * $scale;
  975. } else {
  976. $this->capHeight = $this->ascent;
  977. } // final default is to set it = to Ascent
  978. }
  979. ///////////////////////////////////
  980. // hmtx - Horizontal metrics table
  981. ///////////////////////////////////
  982. $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
  983. ///////////////////////////////////
  984. // kern - Kerning pair table
  985. ///////////////////////////////////
  986. // Recognises old form of Kerning table - as required by Windows - Format 0 only
  987. $kern_offset = $this->seek_table("kern");
  988. $version = $this->read_ushort();
  989. $nTables = $this->read_ushort();
  990. // subtable header
  991. $sversion = $this->read_ushort();
  992. $slength = $this->read_ushort();
  993. $scoverage = $this->read_ushort();
  994. $format = $scoverage >> 8;
  995. if ($kern_offset && $version == 0 && $format == 0) {
  996. // Format 0
  997. $nPairs = $this->read_ushort();
  998. $this->skip(6);
  999. for ($i = 0; $i < $nPairs; $i++) {
  1000. $left = $this->read_ushort();
  1001. $right = $this->read_ushort();
  1002. $val = $this->read_short();
  1003. if (count($glyphToChar[$left]) == 1 && count($glyphToChar[$right]) == 1) {
  1004. if ($left != 32 && $right != 32) {
  1005. $this->kerninfo[$glyphToChar[$left][0]][$glyphToChar[$right][0]] = intval($val * $scale);
  1006. }
  1007. }
  1008. }
  1009. }
  1010. }
  1011. /////////////////////////////////////////////////////////////////////////////////////////
  1012. function _getGDEFtables()
  1013. {
  1014. ///////////////////////////////////
  1015. // GDEF - Glyph Definition
  1016. ///////////////////////////////////
  1017. // http://www.microsoft.com/typography/otspec/gdef.htm
  1018. if (isset($this->tables["GDEF"])) {
  1019. $gdef_offset = $this->seek_table("GDEF");
  1020. // ULONG Version of the GDEF table-currently 0x00010000
  1021. $ver_maj = $this->read_ushort();
  1022. $ver_min = $this->read_ushort();
  1023. $GlyphClassDef_offset = $this->read_ushort();
  1024. $AttachList_offset = $this->read_ushort();
  1025. $LigCaretList_offset = $this->read_ushort();
  1026. $MarkAttachClassDef_offset = $this->read_ushort();
  1027. // Version 0x00010002 of GDEF header contains additional Offset to a list defining mark glyph set definitions (MarkGlyphSetDef)
  1028. if ($ver_min == 2) {
  1029. $MarkGlyphSetsDef_offset = $this->read_ushort();
  1030. }
  1031. // GlyphClassDef
  1032. if ($GlyphClassDef_offset) {
  1033. $this->seek($gdef_offset + $GlyphClassDef_offset);
  1034. /*
  1035. 1 Base glyph (single character, spacing glyph)
  1036. 2 Ligature glyph (multiple character, spacing glyph)
  1037. 3 Mark glyph (non-spacing combining glyph)
  1038. 4 Component glyph (part of single character, spacing glyph)
  1039. */
  1040. $GlyphByClass = $this->_getClassDefinitionTable();
  1041. } else {
  1042. $GlyphByClass = array();
  1043. }
  1044. if (isset($GlyphByClass[1]) && count($GlyphByClass[1]) > 0) {
  1045. $this->GlyphClassBases = ' ' . implode('| ', $GlyphByClass[1]);
  1046. } else {
  1047. $this->GlyphClassBases = '';
  1048. }
  1049. if (isset($GlyphByClass[2]) && count($GlyphByClass[2]) > 0) {
  1050. $this->GlyphClassLigatures = ' ' . implode('| ', $GlyphByClass[2]);
  1051. } else {
  1052. $this->GlyphClassLigatures = '';
  1053. }
  1054. if (isset($GlyphByClass[3]) && count($GlyphByClass[3]) > 0) {
  1055. $this->GlyphClassMarks = ' ' . implode('| ', $GlyphByClass[3]);
  1056. } else {
  1057. $this->GlyphClassMarks = '';
  1058. }
  1059. if (isset($GlyphByClass[4]) && count($GlyphByClass[4]) > 0) {
  1060. $this->GlyphClassComponents = ' ' . implode('| ', $GlyphByClass[4]);
  1061. } else {
  1062. $this->GlyphClassComponents = '';
  1063. }
  1064. if (isset($GlyphByClass[3]) && count($GlyphByClass[3]) > 0) {
  1065. $Marks = $GlyphByClass[3];
  1066. } // to use for MarkAttachmentType
  1067. else {
  1068. $Marks = array();
  1069. }
  1070. /* Required for GPOS
  1071. // Attachment List
  1072. if ($AttachList_offset) {
  1073. $this->seek($gdef_offset+$AttachList_offset );
  1074. }
  1075. The Attachment Point List table (AttachmentList) identifies all the attachment points defined in the GPOS table and their associated glyphs so a client can quickly access coordinates for each glyph's attachment points. As a result, the client can cache coordinates for attachment points along with glyph bitmaps and avoid recalculating the attachment points each time it displays a glyph. Without this table, processing speed would be slower because the client would have to decode the GPOS lookups that define attachment points and compile the points in a list.
  1076. The Attachment List table (AttachList) may be used to cache attachment point coordinates along with glyph bitmaps.
  1077. The table consists of an offset to a Coverage table (Coverage) listing all glyphs that define attachment points in the GPOS table, a count of the glyphs with attachment points (GlyphCount), and an array of offsets to AttachPoint tables (AttachPoint). The array lists the AttachPoint tables, one for each glyph in the Coverage table, in the same order as the Coverage Index.
  1078. AttachList table
  1079. Type Name Description
  1080. Offset Coverage Offset to Coverage table - from beginning of AttachList table
  1081. uint16 GlyphCount Number of glyphs with attachment points
  1082. Offset AttachPoint[GlyphCount] Array of offsets to AttachPoint tables-from beginning of AttachList table-in Coverage Index order
  1083. An AttachPoint table consists of a count of the attachment points on a single glyph (PointCount) and an array of contour indices of those points (PointIndex), listed in increasing numerical order.
  1084. AttachPoint table
  1085. Type Name Description
  1086. uint16 PointCount Number of attachment points on this glyph
  1087. uint16 PointIndex[PointCount] Array of contour point indices -in increasing numerical order
  1088. See Example 3 - http://www.microsoft.com/typography/otspec/gdef.htm
  1089. */
  1090. // Ligature Caret List
  1091. // The Ligature Caret List table (LigCaretList) defines caret positions for all the ligatures in a font.
  1092. // Not required for mDPF
  1093. // MarkAttachmentType
  1094. if ($MarkAttachClassDef_offset) {
  1095. $this->seek($gdef_offset + $MarkAttachClassDef_offset);
  1096. $MarkAttachmentTypes = $this->_getClassDefinitionTable();
  1097. foreach ($MarkAttachmentTypes AS $class => $glyphs) {
  1098. if (is_array($Marks) && count($Marks)) {
  1099. $mat = array_diff($Marks, $MarkAttachmentTypes[$class]);
  1100. sort($mat, SORT_STRING);
  1101. } else {
  1102. $mat = array();
  1103. }
  1104. $this->MarkAttachmentType[$class] = ' ' . implode('| ', $mat);
  1105. }
  1106. } else {
  1107. $this->MarkAttachmentType = array();
  1108. }
  1109. // MarkGlyphSets only in Version 0x00010002 of GDEF
  1110. if ($ver_min == 2 && $MarkGlyphSetsDef_offset) {
  1111. $this->seek($gdef_offset + $MarkGlyphSetsDef_offset);
  1112. $MarkSetTableFormat = $this->read_ushort();
  1113. $MarkSetCount = $this->read_ushort();
  1114. $MarkSetOffset = array();
  1115. for ($i = 0; $i < $MarkSetCount; $i++) {
  1116. $MarkSetOffset[] = $this->read_ulong();
  1117. }
  1118. for ($i = 0; $i < $MarkSetCount; $i++) {
  1119. $this->seek($MarkSetOffset[$i]);
  1120. $glyphs = $this->_getCoverage();
  1121. $this->MarkGlyphSets[$i] = ' ' . implode('| ', $glyphs);
  1122. }
  1123. } else {
  1124. $this->MarkGlyphSets = array();
  1125. }
  1126. } else {
  1127. throw new MpdfException('Warning - You cannot set this font (' . $this->filename . ') to use OTL, as it does not include OTL tables (or at least, not a GDEF table).');
  1128. }
  1129. //=====================================================================================
  1130. //=====================================================================================
  1131. //=====================================================================================
  1132. $GSUB_offset = 0;
  1133. $GPOS_offset = 0;
  1134. $GSUB_length = 0;
  1135. $s = '';
  1136. if (isset($this->tables["GSUB"])) {
  1137. $GSUB_offset = $this->seek_table("GSUB");
  1138. $GSUB_length = $this->tables["GSUB"]['length'];
  1139. $s .= fread($this->fh, $this->tables["GSUB"]['length']);
  1140. }
  1141. if (isset($this->tables["GPOS"])) {
  1142. $GPOS_offset = $this->seek_table("GPOS");
  1143. $s .= fread($this->fh, $this->tables["GPOS"]['length']);
  1144. }
  1145. if ($s)
  1146. file_put_contents(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GSUBGPOStables.dat', $s);
  1147. //=====================================================================================
  1148. //=====================================================================================
  1149. $s = '<?php
  1150. $GSUB_offset = ' . $GSUB_offset . ';
  1151. $GPOS_offset = ' . $GPOS_offset . ';
  1152. $GSUB_length = ' . $GSUB_length . ';
  1153. $GlyphClassBases = \'' . $this->GlyphClassBases . '\';
  1154. $GlyphClassMarks = \'' . $this->GlyphClassMarks . '\';
  1155. $GlyphClassLigatures = \'' . $this->GlyphClassLigatures . '\';
  1156. $GlyphClassComponents = \'' . $this->GlyphClassComponents . '\';
  1157. $MarkGlyphSets = ' . var_export($this->MarkGlyphSets, true) . ';
  1158. $MarkAttachmentType = ' . var_export($this->MarkAttachmentType, true) . ';
  1159. ?>';
  1160. file_put_contents(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GDEFdata.php', $s);
  1161. //=====================================================================================
  1162. //echo $this->GlyphClassMarks ; exit;
  1163. //print_r($GlyphClass); exit;
  1164. //print_r($GlyphByClass); exit;
  1165. }
  1166. function _getClassDefinitionTable()
  1167. {
  1168. // NB Any glyph not included in the range of covered GlyphIDs automatically belongs to Class 0. This is not returned by this function
  1169. $ClassFormat = $this->read_ushort();
  1170. $GlyphByClass = array();
  1171. if ($ClassFormat == 1) {
  1172. $StartGlyph = $this->read_ushort();
  1173. $GlyphCount = $this->read_ushort();
  1174. for ($i = 0; $i < $GlyphCount; $i++) {
  1175. $gid = $StartGlyph + $i;
  1176. $class = $this->read_ushort();
  1177. // Several fonts (mainly dejavu.../Freeserif etc) have a MarkAttachClassDef Format 1, where StartGlyph is 0 and GlyphCount is 1
  1178. // This doesn't seem to do anything useful?
  1179. // Freeserif does not have $this->glyphToChar[0] allocated and would throw an error, so check if isset:
  1180. if (isset($this->glyphToChar[$gid][0])) {
  1181. $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]);
  1182. }
  1183. }
  1184. } else if ($ClassFormat == 2) {
  1185. $tableCount = $this->read_ushort();
  1186. for ($i = 0; $i < $tableCount; $i++) {
  1187. $startGlyphID = $this->read_ushort();
  1188. $endGlyphID = $this->read_ushort();
  1189. $class = $this->read_ushort();
  1190. for ($gid = $startGlyphID; $gid <= $endGlyphID; $gid++) {
  1191. if (isset($this->glyphToChar[$gid][0])) {
  1192. $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]);
  1193. }
  1194. }
  1195. }
  1196. }
  1197. foreach ($GlyphByClass AS $class => $glyphs) {
  1198. sort($GlyphByClass[$class], SORT_STRING); // SORT makes it easier to read in development ? order not important ???
  1199. }
  1200. ksort($GlyphByClass);
  1201. return $GlyphByClass;
  1202. }
  1203. function _getGSUBtables()
  1204. {
  1205. ///////////////////////////////////
  1206. // GSUB - Glyph Substitution
  1207. ///////////////////////////////////
  1208. if (isset($this->tables["GSUB"])) {
  1209. $ffeats = array();
  1210. $gsub_offset = $this->seek_table("GSUB");
  1211. $this->skip(4);
  1212. $ScriptList_offset = $gsub_offset + $this->read_ushort();
  1213. $FeatureList_offset = $gsub_offset + $this->read_ushort();
  1214. $LookupList_offset = $gsub_offset + $this->read_ushort();
  1215. // ScriptList
  1216. $this->seek($ScriptList_offset);
  1217. $ScriptCount = $this->read_ushort();
  1218. for ($i = 0; $i < $ScriptCount; $i++) {
  1219. $ScriptTag = $this->read_tag(); // = "beng", "deva" etc.
  1220. $ScriptTableOffset = $this->read_ushort();
  1221. $ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset;
  1222. }
  1223. // Script Table
  1224. foreach ($ffeats AS $t => $o) {
  1225. $ls = array();
  1226. $this->seek($o);
  1227. $DefLangSys_offset = $this->read_ushort();
  1228. if ($DefLangSys_offset > 0) {
  1229. $ls['DFLT'] = $DefLangSys_offset + $o;
  1230. }
  1231. $LangSysCount = $this->read_ushort();
  1232. for ($i = 0; $i < $LangSysCount; $i++) {
  1233. $LangTag = $this->read_tag(); // =
  1234. $LangTableOffset = $this->read_ushort();
  1235. $ls[$LangTag] = $o + $LangTableOffset;
  1236. }
  1237. $ffeats[$t] = $ls;
  1238. }
  1239. //print_r($ffeats); exit;
  1240. // Get FeatureIndexList
  1241. // LangSys Table - from first listed langsys
  1242. foreach ($ffeats AS $st => $scripts) {
  1243. foreach ($scripts AS $t => $o) {
  1244. $FeatureIndex = array();
  1245. $langsystable_offset = $o;
  1246. $this->seek($langsystable_offset);
  1247. $LookUpOrder = $this->read_ushort(); //==NULL
  1248. $ReqFeatureIndex = $this->read_ushort();
  1249. if ($ReqFeatureIndex != 0xFFFF) {
  1250. $FeatureIndex[] = $ReqFeatureIndex;
  1251. }
  1252. $FeatureCount = $this->read_ushort();
  1253. for ($i = 0; $i < $FeatureCount; $i++) {
  1254. $FeatureIndex[] = $this->read_ushort(); // = index of feature
  1255. }
  1256. $ffeats[$st][$t] = $FeatureIndex;
  1257. }
  1258. }
  1259. //print_r($ffeats); exit;
  1260. // Feauture List => LookupListIndex es
  1261. $this->seek($FeatureList_offset);
  1262. $FeatureCount = $this->read_ushort();
  1263. $Feature = array();
  1264. for ($i = 0; $i < $FeatureCount; $i++) {
  1265. $tag = $this->read_tag();
  1266. if ($tag == 'smcp') {
  1267. $this->hassmallcapsGSUB = true;
  1268. }
  1269. $Feature[$i] = array('tag' => $tag);
  1270. $Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort();
  1271. }
  1272. for ($i = 0; $i < $FeatureCount; $i++) {
  1273. $this->seek($Feature[$i]['offset']);
  1274. $this->read_ushort(); // null [FeatureParams]
  1275. $Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort();
  1276. $Feature[$i]['LookupListIndex'] = array();
  1277. for ($c = 0; $c < $Lookupcount; $c++) {
  1278. $Feature[$i]['LookupListIndex'][] = $this->read_ushort();
  1279. }
  1280. }
  1281. //print_r($Feature); exit;
  1282. foreach ($ffeats AS $st => $scripts) {
  1283. foreach ($scripts AS $t => $o) {
  1284. $FeatureIndex = $ffeats[$st][$t];
  1285. foreach ($FeatureIndex AS $k => $fi) {
  1286. $ffeats[$st][$t][$k] = $Feature[$fi];
  1287. }
  1288. }
  1289. }
  1290. //=====================================================================================
  1291. $gsub = array();
  1292. $GSUBScriptLang = array();
  1293. foreach ($ffeats AS $st => $scripts) {
  1294. foreach ($scripts AS $t => $langsys) {
  1295. $lg = array();
  1296. foreach ($langsys AS $ft) {
  1297. $lg[$ft['LookupListIndex'][0]] = $ft;
  1298. }
  1299. // list of Lookups in order they need to be run i.e. order listed in Lookup table
  1300. ksort($lg);
  1301. foreach ($lg AS $ft) {
  1302. $gsub[$st][$t][$ft['tag']] = $ft['LookupListIndex'];
  1303. }
  1304. if (!isset($GSUBScriptLang[$st])) {
  1305. $GSUBScriptLang[$st] = '';
  1306. }
  1307. $GSUBScriptLang[$st] .= $t . ' ';
  1308. }
  1309. }
  1310. //print_r($gsub); exit;
  1311. //=====================================================================================
  1312. // Get metadata and offsets for whole Lookup List table
  1313. $this->seek($LookupList_offset);
  1314. $LookupCount = $this->read_ushort();
  1315. $GSLookup = array();
  1316. $Offsets = array();
  1317. $SubtableCount = array();
  1318. for ($i = 0; $i < $LookupCount; $i++) {
  1319. $Offsets[$i] = $LookupList_offset + $this->read_ushort();
  1320. }
  1321. for ($i = 0; $i < $LookupCount; $i++) {
  1322. $this->seek($Offsets[$i]);
  1323. $GSLookup[$i]['Type'] = $this->read_ushort();
  1324. $GSLookup[$i]['Flag'] = $flag = $this->read_ushort();
  1325. $GSLookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort();
  1326. for ($c = 0; $c < $SubtableCount[$i]; $c++) {
  1327. $GSLookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort();
  1328. }
  1329. // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure
  1330. if (($flag & 0x0010) == 0x0010) {
  1331. $GSLookup[$i]['MarkFilteringSet'] = $this->read_ushort();
  1332. } else {
  1333. $GSLookup[$i]['MarkFilteringSet'] = '';
  1334. }
  1335. // Lookup Type 7: Extension
  1336. if ($GSLookup[$i]['Type'] == 7) {
  1337. // Overwrites new offset (32-bit) for each subtable, and a new lookup Type
  1338. for ($c = 0; $c < $SubtableCount[$i]; $c++) {
  1339. $this->seek($GSLookup[$i]['Subtables'][$c]);
  1340. $ExtensionPosFormat = $this->read_ushort();
  1341. $type = $this->read_ushort();
  1342. $ext_offset = $this->read_ulong();
  1343. $GSLookup[$i]['Subtables'][$c] = $GSLookup[$i]['Subtables'][$c] + $ext_offset;
  1344. }
  1345. $GSLookup[$i]['Type'] = $type;
  1346. }
  1347. }
  1348. //print_r($GSLookup); exit;
  1349. //=====================================================================================
  1350. // Process Whole LookupList - Get LuCoverage = Lookup coverage just for first glyph
  1351. $this->GSLuCoverage = array();
  1352. for ($i = 0; $i < $LookupCount; $i++) {
  1353. for ($c = 0; $c < $GSLookup[$i]['SubtableCount']; $c++) {
  1354. $this->seek($GSLookup[$i]['Subtables'][$c]);
  1355. $PosFormat = $this->read_ushort();
  1356. if ($GSLookup[$i]['Type'] == 5 && $PosFormat == 3) {
  1357. $this->skip(4);
  1358. } else if ($GSLookup[$i]['Type'] == 6 && $PosFormat == 3) {
  1359. $BacktrackGlyphCount = $this->read_ushort();
  1360. $this->skip(2 * $BacktrackGlyphCount + 2);
  1361. }
  1362. // NB Coverage only looks at glyphs for position 1 (i.e. 5.3 and 6.3) // NEEDS TO READ ALL ********************
  1363. $Coverage = $GSLookup[$i]['Subtables'][$c] + $this->read_ushort();
  1364. $this->seek($Coverage);
  1365. $glyphs = $this->_getCoverage(false, 2);
  1366. $this->GSLuCoverage[$i][$c] = $glyphs;
  1367. }
  1368. }
  1369. // $this->GSLuCoverage and $GSLookup
  1370. //=====================================================================================
  1371. //=====================================================================================
  1372. $s = '<?php
  1373. $GSLuCoverage = ' . var_export($this->GSLuCoverage, true) . ';
  1374. ?>';
  1375. file_put_contents(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GSUBdata.php', $s);
  1376. //=====================================================================================
  1377. //=====================================================================================
  1378. //=====================================================================================
  1379. //=====================================================================================
  1380. // Now repeats as original to get Substitution rules
  1381. //=====================================================================================
  1382. //=====================================================================================
  1383. //=====================================================================================
  1384. // Get metadata and offsets for whole Lookup List table
  1385. $this->seek($LookupList_offset);
  1386. $LookupCount = $this->read_ushort();
  1387. $Lookup = array();
  1388. for ($i = 0; $i < $LookupCount; $i++) {
  1389. $Lookup[$i]['offset'] = $LookupList_offset + $this->read_ushort();
  1390. }
  1391. for ($i = 0; $i < $LookupCount; $i++) {
  1392. $this->seek($Lookup[$i]['offset']);
  1393. $Lookup[$i]['Type'] = $this->read_ushort();
  1394. $Lookup[$i]['Flag'] = $flag = $this->read_ushort();
  1395. $Lookup[$i]['SubtableCount'] = $this->read_ushort();
  1396. for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
  1397. $Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['offset'] + $this->read_ushort();
  1398. }
  1399. // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure
  1400. if (($flag & 0x0010) == 0x0010) {
  1401. $Lookup[$i]['MarkFilteringSet'] = $this->read_ushort();
  1402. } else {
  1403. $Lookup[$i]['MarkFilteringSet'] = '';
  1404. }
  1405. // Lookup Type 7: Extension
  1406. if ($Lookup[$i]['Type'] == 7) {
  1407. // Overwrites new offset (32-bit) for each subtable, and a new lookup Type
  1408. for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
  1409. $this->seek($Lookup[$i]['Subtable'][$c]['Offset']);
  1410. $ExtensionPosFormat = $this->read_ushort();
  1411. $type = $this->read_ushort();
  1412. $Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ulong();
  1413. }
  1414. $Lookup[$i]['Type'] = $type;
  1415. }
  1416. }
  1417. //print_r($Lookup); exit;
  1418. //=====================================================================================
  1419. // Process (1) Whole LookupList
  1420. for ($i = 0; $i < $LookupCount; $i++) {
  1421. for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
  1422. $this->seek($Lookup[$i]['Subtable'][$c]['Offset']);
  1423. $SubstFormat = $this->read_ushort();
  1424. $Lookup[$i]['Subtable'][$c]['Format'] = $SubstFormat;
  1425. /*
  1426. Lookup['Type'] Enumeration table for glyph substitution
  1427. Value Type Description
  1428. 1 Single Replace one glyph with one glyph
  1429. 2 Multiple Replace one glyph with more than one glyph
  1430. 3 Alternate Replace one glyph with one of many glyphs
  1431. 4 Ligature Replace multiple glyphs with one glyph
  1432. 5 Context Replace one or more glyphs in context
  1433. 6 Chaining Context Replace one or more glyphs in chained context
  1434. 7 Extension Substitution Extension mechanism for other substitutions (i.e. this excludes the Extension type substitution itself)
  1435. 8 Reverse chaining context single Applied in reverse order, replace single glyph in chaining context
  1436. */
  1437. // LookupType 1: Single Substitution Subtable
  1438. if ($Lookup[$i]['Type'] == 1) {
  1439. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1440. if ($SubstFormat == 1) { // Calculated output glyph indices
  1441. $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'] = $this->read_short();
  1442. } else if ($SubstFormat == 2) { // Specified output glyph indices
  1443. $GlyphCount = $this->read_ushort();
  1444. for ($g = 0; $g < $GlyphCount; $g++) {
  1445. $Lookup[$i]['Subtable'][$c]['Glyphs'][] = $this->read_ushort();
  1446. }
  1447. }
  1448. }
  1449. // LookupType 2: Multiple Substitution Subtable
  1450. else if ($Lookup[$i]['Type'] == 2) {
  1451. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1452. $Lookup[$i]['Subtable'][$c]['SequenceCount'] = $SequenceCount = $this->read_short();
  1453. for ($s = 0; $s < $SequenceCount; $s++) {
  1454. $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
  1455. }
  1456. for ($s = 0; $s < $SequenceCount; $s++) {
  1457. // Sequence Tables
  1458. $this->seek($Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset']);
  1459. $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount'] = $this->read_short();
  1460. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount']; $g++) {
  1461. $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['SubstituteGlyphID'][] = $this->read_ushort();
  1462. }
  1463. }
  1464. }
  1465. // LookupType 3: Alternate Forms
  1466. else if ($Lookup[$i]['Type'] == 3) {
  1467. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1468. $Lookup[$i]['Subtable'][$c]['AlternateSetCount'] = $AlternateSetCount = $this->read_short();
  1469. for ($s = 0; $s < $AlternateSetCount; $s++) {
  1470. $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
  1471. }
  1472. for ($s = 0; $s < $AlternateSetCount; $s++) {
  1473. // AlternateSet Tables
  1474. $this->seek($Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset']);
  1475. $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount'] = $this->read_short();
  1476. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount']; $g++) {
  1477. $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['SubstituteGlyphID'][] = $this->read_ushort();
  1478. }
  1479. }
  1480. }
  1481. // LookupType 4: Ligature Substitution Subtable
  1482. else if ($Lookup[$i]['Type'] == 4) {
  1483. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1484. $Lookup[$i]['Subtable'][$c]['LigSetCount'] = $LigSetCount = $this->read_short();
  1485. for ($s = 0; $s < $LigSetCount; $s++) {
  1486. $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
  1487. }
  1488. for ($s = 0; $s < $LigSetCount; $s++) {
  1489. // LigatureSet Tables
  1490. $this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset']);
  1491. $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount'] = $this->read_short();
  1492. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
  1493. $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g] = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] + $this->read_ushort();
  1494. }
  1495. }
  1496. for ($s = 0; $s < $LigSetCount; $s++) {
  1497. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
  1498. // Ligature tables
  1499. $this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g]);
  1500. $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph'] = $this->read_ushort();
  1501. $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount'] = $this->read_ushort();
  1502. for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) {
  1503. $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l] = $this->read_ushort();
  1504. }
  1505. }
  1506. }
  1507. }
  1508. // LookupType 5: Contextual Substitution Subtable
  1509. else if ($Lookup[$i]['Type'] == 5) {
  1510. // Format 1: Context Substitution
  1511. if ($SubstFormat == 1) {
  1512. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1513. $Lookup[$i]['Subtable'][$c]['SubRuleSetCount'] = $SubRuleSetCount = $this->read_short();
  1514. for ($s = 0; $s < $SubRuleSetCount; $s++) {
  1515. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
  1516. }
  1517. for ($s = 0; $s < $SubRuleSetCount; $s++) {
  1518. // SubRuleSet Tables
  1519. $this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset']);
  1520. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount'] = $this->read_short();
  1521. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) {
  1522. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] + $this->read_ushort();
  1523. }
  1524. }
  1525. for ($s = 0; $s < $SubRuleSetCount; $s++) {
  1526. // SubRule Tables
  1527. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) {
  1528. // Ligature tables
  1529. $this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g]);
  1530. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount'] = $this->read_ushort();
  1531. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount'] = $this->read_ushort();
  1532. // "Input"::[GlyphCount - 1]::Array of input GlyphIDs-start with second glyph
  1533. for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount']; $l++) {
  1534. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['Input'][$l] = $this->read_ushort();
  1535. }
  1536. // "SubstLookupRecord"::[SubstCount]::Array of SubstLookupRecords-in design order
  1537. for ($l = 0; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount']; $l++) {
  1538. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['SequenceIndex'] = $this->read_ushort();
  1539. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['LookupListIndex'] = $this->read_ushort();
  1540. }
  1541. }
  1542. }
  1543. }
  1544. // Format 2: Class-based Context Glyph Substitution
  1545. else if ($SubstFormat == 2) {
  1546. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1547. $Lookup[$i]['Subtable'][$c]['ClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1548. $Lookup[$i]['Subtable'][$c]['SubClassSetCnt'] = $this->read_ushort();
  1549. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $b++) {
  1550. $offset = $this->read_ushort();
  1551. if ($offset == 0x0000) {
  1552. $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = 0;
  1553. } else {
  1554. $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset;
  1555. }
  1556. }
  1557. } else {
  1558. throw new MpdfException("GPOS Lookup Type " . $Lookup[$i]['Type'] . ", Format " . $SubstFormat . " not supported (ttfontsuni.php).");
  1559. }
  1560. }
  1561. // LookupType 6: Chaining Contextual Substitution Subtable
  1562. else if ($Lookup[$i]['Type'] == 6) {
  1563. // Format 1: Simple Chaining Context Glyph Substitution p255
  1564. if ($SubstFormat == 1) {
  1565. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1566. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount'] = $this->read_ushort();
  1567. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $b++) {
  1568. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1569. }
  1570. }
  1571. // Format 2: Class-based Chaining Context Glyph Substitution p257
  1572. else if ($SubstFormat == 2) {
  1573. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1574. $Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1575. $Lookup[$i]['Subtable'][$c]['InputClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1576. $Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1577. $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt'] = $this->read_ushort();
  1578. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $b++) {
  1579. $offset = $this->read_ushort();
  1580. if ($offset == 0x0000) {
  1581. $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $offset;
  1582. } else {
  1583. $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset;
  1584. }
  1585. }
  1586. }
  1587. // Format 3: Coverage-based Chaining Context Glyph Substitution p259
  1588. else if ($SubstFormat == 3) {
  1589. $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount'] = $this->read_ushort();
  1590. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) {
  1591. $Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1592. }
  1593. $Lookup[$i]['Subtable'][$c]['InputGlyphCount'] = $this->read_ushort();
  1594. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) {
  1595. $Lookup[$i]['Subtable'][$c]['CoverageInput'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1596. }
  1597. $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount'] = $this->read_ushort();
  1598. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) {
  1599. $Lookup[$i]['Subtable'][$c]['CoverageLookahead'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1600. }
  1601. $Lookup[$i]['Subtable'][$c]['SubstCount'] = $this->read_ushort();
  1602. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) {
  1603. $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'] = $this->read_ushort();
  1604. $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'] = $this->read_ushort();
  1605. /*
  1606. Substitution Lookup Record
  1607. All contextual substitution subtables specify the substitution data in a Substitution Lookup Record (SubstLookupRecord). Each record contains a SequenceIndex, which indicates the position where the substitution will occur in the glyph sequence. In addition, a LookupListIndex identifies the lookup to be applied at the glyph position specified by the SequenceIndex.
  1608. */
  1609. }
  1610. }
  1611. } else {
  1612. throw new MpdfException("Lookup Type " . $Lookup[$i]['Type'] . " not supported.");
  1613. }
  1614. }
  1615. }
  1616. //print_r($Lookup); exit;
  1617. //=====================================================================================
  1618. // Process (2) Whole LookupList
  1619. // Get Coverage tables and prepare preg_replace
  1620. for ($i = 0; $i < $LookupCount; $i++) {
  1621. for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
  1622. $SubstFormat = $Lookup[$i]['Subtable'][$c]['Format'];
  1623. // LookupType 1: Single Substitution Subtable 1 => 1
  1624. if ($Lookup[$i]['Type'] == 1) {
  1625. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1626. $glyphs = $this->_getCoverage(false);
  1627. for ($g = 0; $g < count($glyphs); $g++) {
  1628. $replace = array();
  1629. $substitute = array();
  1630. $replace[] = unicode_hex($this->glyphToChar[$glyphs[$g]][0]);
  1631. // Flag = Ignore
  1632. if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
  1633. continue;
  1634. }
  1635. if (isset($Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])) { // Format 1
  1636. $substitute[] = unicode_hex($this->glyphToChar[($glyphs[$g] + $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])][0]);
  1637. } else { // Format 2
  1638. $substitute[] = unicode_hex($this->glyphToChar[($Lookup[$i]['Subtable'][$c]['Glyphs'][$g])][0]);
  1639. }
  1640. $Lookup[$i]['Subtable'][$c]['subs'][] = array('Replace' => $replace, 'substitute' => $substitute);
  1641. }
  1642. }
  1643. // LookupType 2: Multiple Substitution Subtable 1 => n
  1644. else if ($Lookup[$i]['Type'] == 2) {
  1645. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1646. $glyphs = $this->_getCoverage();
  1647. for ($g = 0; $g < count($glyphs); $g++) {
  1648. $replace = array();
  1649. $substitute = array();
  1650. $replace[] = $glyphs[$g];
  1651. // Flag = Ignore
  1652. if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
  1653. continue;
  1654. }
  1655. if (!isset($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) || count($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) == 0) {
  1656. continue;
  1657. } // Illegal for GlyphCount to be 0; either error in font, or something has gone wrong - lets carry on for now!
  1658. foreach ($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID'] AS $sub) {
  1659. $substitute[] = unicode_hex($this->glyphToChar[$sub][0]);
  1660. }
  1661. $Lookup[$i]['Subtable'][$c]['subs'][] = array('Replace' => $replace, 'substitute' => $substitute);
  1662. }
  1663. }
  1664. // LookupType 3: Alternate Forms 1 => 1 (only first alternate form is used)
  1665. else if ($Lookup[$i]['Type'] == 3) {
  1666. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1667. $glyphs = $this->_getCoverage();
  1668. for ($g = 0; $g < count($glyphs); $g++) {
  1669. $replace = array();
  1670. $substitute = array();
  1671. $replace[] = $glyphs[$g];
  1672. // Flag = Ignore
  1673. if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
  1674. continue;
  1675. }
  1676. $gid = $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['SubstituteGlyphID'][0];
  1677. if (!isset($this->glyphToChar[$gid][0])) {
  1678. continue;
  1679. }
  1680. $substitute[] = unicode_hex($this->glyphToChar[$gid][0]);
  1681. $Lookup[$i]['Subtable'][$c]['subs'][] = array('Replace' => $replace, 'substitute' => $substitute);
  1682. }
  1683. }
  1684. // LookupType 4: Ligature Substitution Subtable n => 1
  1685. else if ($Lookup[$i]['Type'] == 4) {
  1686. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1687. $glyphs = $this->_getCoverage();
  1688. $LigSetCount = $Lookup[$i]['Subtable'][$c]['LigSetCount'];
  1689. for ($s = 0; $s < $LigSetCount; $s++) {
  1690. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
  1691. $replace = array();
  1692. $substitute = array();
  1693. $replace[] = $glyphs[$s];
  1694. // Flag = Ignore
  1695. if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
  1696. continue;
  1697. }
  1698. for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) {
  1699. $gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l];
  1700. $rpl = unicode_hex($this->glyphToChar[$gid][0]);
  1701. // Flag = Ignore
  1702. if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $rpl, $Lookup[$i]['MarkFilteringSet'])) {
  1703. continue 2;
  1704. }
  1705. $replace[] = $rpl;
  1706. }
  1707. $gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph'];
  1708. if (!isset($this->glyphToChar[$gid][0])) {
  1709. continue;
  1710. }
  1711. $substitute[] = unicode_hex($this->glyphToChar[$gid][0]);
  1712. $Lookup[$i]['Subtable'][$c]['subs'][] = array('Replace' => $replace, 'substitute' => $substitute, 'CompCount' => $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']);
  1713. }
  1714. }
  1715. }
  1716. // LookupType 5: Contextual Substitution Subtable
  1717. else if ($Lookup[$i]['Type'] == 5) {
  1718. // Format 1: Context Substitution
  1719. if ($SubstFormat == 1) {
  1720. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1721. $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
  1722. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) {
  1723. $SubRuleSet = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s];
  1724. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph'] = $CoverageGlyphs[$s];
  1725. for ($r = 0; $r < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $r++) {
  1726. $GlyphCount = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['GlyphCount'];
  1727. for ($g = 1; $g < $GlyphCount; $g++) {
  1728. $glyphID = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['Input'][$g];
  1729. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
  1730. }
  1731. }
  1732. }
  1733. }
  1734. // Format 2: Class-based Context Glyph Substitution
  1735. else if ($SubstFormat == 2) {
  1736. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1737. $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
  1738. $InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['ClassDefOffset']);
  1739. $Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses;
  1740. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) {
  1741. if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) {
  1742. $this->seek($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s]);
  1743. $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt'] = $SubClassRuleCnt = $this->read_ushort();
  1744. $SubClassRule = array();
  1745. for ($b = 0; $b < $SubClassRuleCnt; $b++) {
  1746. $SubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] + $this->read_ushort();
  1747. $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $SubClassRule[$b];
  1748. }
  1749. }
  1750. }
  1751. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) {
  1752. if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) {
  1753. $SubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt'];
  1754. for ($b = 0; $b < $SubClassRuleCnt; $b++) {
  1755. $this->seek($Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b]);
  1756. $Rule = array();
  1757. $Rule['InputGlyphCount'] = $this->read_ushort();
  1758. $Rule['SubstCount'] = $this->read_ushort();
  1759. for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) {
  1760. $Rule['Input'][$r] = $this->read_ushort();
  1761. }
  1762. for ($r = 0; $r < $Rule['SubstCount']; $r++) {
  1763. $Rule['SequenceIndex'][$r] = $this->read_ushort();
  1764. $Rule['LookupListIndex'][$r] = $this->read_ushort();
  1765. }
  1766. $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $Rule;
  1767. }
  1768. }
  1769. }
  1770. }
  1771. // Format 3: Coverage-based Context Glyph Substitution
  1772. else if ($SubstFormat == 3) {
  1773. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) {
  1774. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]);
  1775. $glyphs = $this->_getCoverage();
  1776. $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs);
  1777. }
  1778. throw new MpdfException("Lookup Type 5, SubstFormat 3 not tested. Please report this with the name of font used - " . $this->fontkey);
  1779. }
  1780. }
  1781. // LookupType 6: Chaining Contextual Substitution Subtable
  1782. else if ($Lookup[$i]['Type'] == 6) {
  1783. // Format 1: Simple Chaining Context Glyph Substitution p255
  1784. if ($SubstFormat == 1) {
  1785. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1786. $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
  1787. $ChainSubRuleSetCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount'];
  1788. for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) {
  1789. $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s]);
  1790. $ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount'] = $this->read_ushort();
  1791. for ($r = 0; $r < $ChainSubRuleCnt; $r++) {
  1792. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r] = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s] + $this->read_ushort();
  1793. }
  1794. }
  1795. for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) {
  1796. $ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount'];
  1797. for ($r = 0; $r < $ChainSubRuleCnt; $r++) {
  1798. // ChainSubRule
  1799. $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r]);
  1800. $BacktrackGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphCount'] = $this->read_ushort();
  1801. for ($g = 0; $g < $BacktrackGlyphCount; $g++) {
  1802. $glyphID = $this->read_ushort();
  1803. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
  1804. }
  1805. $InputGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphCount'] = $this->read_ushort();
  1806. for ($g = 1; $g < $InputGlyphCount; $g++) {
  1807. $glyphID = $this->read_ushort();
  1808. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
  1809. }
  1810. $LookaheadGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphCount'] = $this->read_ushort();
  1811. for ($g = 0; $g < $LookaheadGlyphCount; $g++) {
  1812. $glyphID = $this->read_ushort();
  1813. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
  1814. }
  1815. $SubstCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SubstCount'] = $this->read_ushort();
  1816. for ($lu = 0; $lu < $SubstCount; $lu++) {
  1817. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SequenceIndex'][$lu] = $this->read_ushort();
  1818. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookupListIndex'][$lu] = $this->read_ushort();
  1819. }
  1820. }
  1821. }
  1822. }
  1823. // Format 2: Class-based Chaining Context Glyph Substitution p257
  1824. else if ($SubstFormat == 2) {
  1825. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1826. $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
  1827. $BacktrackClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset']);
  1828. $Lookup[$i]['Subtable'][$c]['BacktrackClasses'] = $BacktrackClasses;
  1829. $InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['InputClassDefOffset']);
  1830. $Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses;
  1831. $LookaheadClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset']);
  1832. $Lookup[$i]['Subtable'][$c]['LookaheadClasses'] = $LookaheadClasses;
  1833. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) {
  1834. if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) {
  1835. $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s]);
  1836. $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'] = $ChainSubClassRuleCnt = $this->read_ushort();
  1837. $ChainSubClassRule = array();
  1838. for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) {
  1839. $ChainSubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] + $this->read_ushort();
  1840. $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $ChainSubClassRule[$b];
  1841. }
  1842. }
  1843. }
  1844. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) {
  1845. if (isset($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'])) {
  1846. $ChainSubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'];
  1847. } else {
  1848. $ChainSubClassRuleCnt = 0;
  1849. }
  1850. for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) {
  1851. if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) {
  1852. $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b]);
  1853. $Rule = array();
  1854. $Rule['BacktrackGlyphCount'] = $this->read_ushort();
  1855. for ($r = 0; $r < $Rule['BacktrackGlyphCount']; $r++) {
  1856. $Rule['Backtrack'][$r] = $this->read_ushort();
  1857. }
  1858. $Rule['InputGlyphCount'] = $this->read_ushort();
  1859. for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) {
  1860. $Rule['Input'][$r] = $this->read_ushort();
  1861. }
  1862. $Rule['LookaheadGlyphCount'] = $this->read_ushort();
  1863. for ($r = 0; $r < $Rule['LookaheadGlyphCount']; $r++) {
  1864. $Rule['Lookahead'][$r] = $this->read_ushort();
  1865. }
  1866. $Rule['SubstCount'] = $this->read_ushort();
  1867. for ($r = 0; $r < $Rule['SubstCount']; $r++) {
  1868. $Rule['SequenceIndex'][$r] = $this->read_ushort();
  1869. $Rule['LookupListIndex'][$r] = $this->read_ushort();
  1870. }
  1871. $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $Rule;
  1872. }
  1873. }
  1874. }
  1875. }
  1876. // Format 3: Coverage-based Chaining Context Glyph Substitution p259
  1877. else if ($SubstFormat == 3) {
  1878. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) {
  1879. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][$b]);
  1880. $glyphs = $this->_getCoverage();
  1881. $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'][] = implode("|", $glyphs);
  1882. }
  1883. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) {
  1884. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]);
  1885. $glyphs = $this->_getCoverage();
  1886. $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs);
  1887. // Don't use above value as these are ordered numerically not as need to process
  1888. }
  1889. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) {
  1890. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageLookahead'][$b]);
  1891. $glyphs = $this->_getCoverage();
  1892. $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'][] = implode("|", $glyphs);
  1893. }
  1894. }
  1895. }
  1896. }
  1897. }
  1898. //=====================================================================================
  1899. //=====================================================================================
  1900. //=====================================================================================
  1901. //=====================================================================================
  1902. $GSUBScriptLang = array();
  1903. $rtlpua = array(); // All glyphs added to PUA [for magic_reverse]
  1904. foreach ($gsub AS $st => $scripts) {
  1905. foreach ($scripts AS $t => $langsys) {
  1906. $lul = array(); // array of LookupListIndexes
  1907. $tags = array(); // corresponding array of feature tags e.g. 'ccmp'
  1908. //print_r($langsys ); exit;
  1909. foreach ($langsys AS $tag => $ft) {
  1910. foreach ($ft AS $ll) {
  1911. $lul[$ll] = $tag;
  1912. }
  1913. }
  1914. ksort($lul); // Order the Lookups in the order they are in the GUSB table, regardless of Feature order
  1915. $volt = $this->_getGSUBarray($Lookup, $lul, $st);
  1916. //print_r($lul); exit;
  1917. //=====================================================================================
  1918. //=====================================================================================
  1919. // Interrogate $volt
  1920. // isol, fin, medi, init(arab syrc) into $rtlSUB for use in ArabJoin
  1921. // but also identify all RTL chars in PUA for magic_reverse (arab syrc hebr thaa nko samr)
  1922. // identify reph, matras, vatu, half forms etc for Indic for final re-ordering
  1923. //=====================================================================================
  1924. //=====================================================================================
  1925. $rtl = array();
  1926. $rtlSUB = "array()";
  1927. $finals = '';
  1928. if (strpos('arab syrc hebr thaa nko samr', $st) !== false) { // all RTL scripts [any/all languages] ? Mandaic
  1929. //print_r($volt); exit;
  1930. foreach ($volt AS $v) {
  1931. // isol fina fin2 fin3 medi med2 for Syriac
  1932. // ISOLATED FORM :: FINAL :: INITIAL :: MEDIAL :: MED2 :: FIN2 :: FIN3
  1933. if (strpos('isol fina init medi fin2 fin3 med2', $v['tag']) !== false) {
  1934. $key = $v['match'];
  1935. $key = preg_replace('/[\(\)]*/', '', $key);
  1936. $sub = $v['replace'];
  1937. if ($v['tag'] == 'isol')
  1938. $kk = 0;
  1939. else if ($v['tag'] == 'fina')
  1940. $kk = 1;
  1941. else if ($v['tag'] == 'init')
  1942. $kk = 2;
  1943. else if ($v['tag'] == 'medi')
  1944. $kk = 3;
  1945. else if ($v['tag'] == 'med2')
  1946. $kk = 4;
  1947. else if ($v['tag'] == 'fin2')
  1948. $kk = 5;
  1949. else if ($v['tag'] == 'fin3')
  1950. $kk = 6;
  1951. $rtl[$key][$kk] = $sub;
  1952. if (isset($v['prel']) && count($v['prel']))
  1953. $rtl[$key]['prel'][$kk] = $v['prel'];
  1954. if (isset($v['postl']) && count($v['postl']))
  1955. $rtl[$key]['postl'][$kk] = $v['postl'];
  1956. if (isset($v['ignore']) && $v['ignore']) {
  1957. $rtl[$key]['ignore'][$kk] = $v['ignore'];
  1958. }
  1959. $rtlpua[] = $sub;
  1960. }
  1961. // Add any other glyphs which are in PUA
  1962. else {
  1963. if (isset($v['context']) && $v['context']) {
  1964. foreach ($v['rules'] AS $vs) {
  1965. for ($i = 0; $i < count($vs['match']); $i++) {
  1966. if (isset($vs['replace'][$i]) && preg_match('/^0[A-F0-9]{4}$/', $vs['match'][$i])) {
  1967. if (preg_match('/^0[EF][A-F0-9]{3}$/', $vs['replace'][$i])) {
  1968. $rtlpua[] = $vs['replace'][$i];
  1969. }
  1970. }
  1971. }
  1972. }
  1973. } else {
  1974. preg_match_all('/\((0[A-F0-9]{4})\)/', $v['match'], $m);
  1975. for ($i = 0; $i < count($m[0]); $i++) {
  1976. $sb = explode(' ', $v['replace']);
  1977. foreach ($sb AS $sbg) {
  1978. if (preg_match('/(0[EF][A-F0-9]{3})/', $sbg, $mr)) {
  1979. $rtlpua[] = $mr[1];
  1980. }
  1981. }
  1982. }
  1983. }
  1984. }
  1985. }
  1986. //print_r($rtl); exit;
  1987. // For kashida, need to determine all final forms except ones already identified by kashida
  1988. // priority rules (see otl.php)
  1989. foreach ($rtl AS $base => $variants) {
  1990. if (isset($variants[1])) { // i.e. final form
  1991. if (strpos('0FE8E 0FE94 0FEA2 0FEAA 0FEAE 0FEC2 0FEDA 0FEDE 0FB93 0FECA 0FED2 0FED6 0FEEE 0FEF0 0FEF2', $variants[1]) === false) { // not already included
  1992. // This version does not exclude RA (0631) FEAE; Ya (064A) FEF2; Alef Maqsurah (0649) FEF0 which
  1993. // are selected in priority if connected to a medial Bah
  1994. //if (strpos('0FE8E 0FE94 0FEA2 0FEAA 0FEC2 0FEDA 0FEDE 0FB93 0FECA 0FED2 0FED6 0FEEE', $variants[1])===false) { // not already included
  1995. $finals .= $variants[1] . ' ';
  1996. }
  1997. }
  1998. }
  1999. //echo $finals ; exit;
  2000. //print_r($rtlpua); exit;
  2001. ksort($rtl);
  2002. $a = var_export($rtl, true);
  2003. $a = preg_replace('/\\\\\\\\/', "\\", $a);
  2004. $a = preg_replace('/\'/', '"', $a);
  2005. $a = preg_replace('/\r/', '', $a);
  2006. $a = preg_replace('/> \n/', '>', $a);
  2007. $a = preg_replace('/\n \)/', ')', $a);
  2008. $a = preg_replace('/\n /', ' ', $a);
  2009. $a = preg_replace('/\[IGNORE(\d+)\]/', '".$ignore[\\1]."', $a);
  2010. $rtlSUB = preg_replace('/[ ]+/', ' ', $a);
  2011. }
  2012. //=====================================================================================
  2013. // INDIC - Dynamic properties
  2014. //=====================================================================================
  2015. $rphf = array();
  2016. $half = array();
  2017. $pref = array();
  2018. $blwf = array();
  2019. $pstf = array();
  2020. if (strpos('dev2 bng2 gur2 gjr2 ory2 tml2 tel2 knd2 mlm2 deva beng guru gujr orya taml telu knda mlym', $st) !== false) { // all INDIC scripts [any/all languages]
  2021. if (strpos('deva beng guru gujr orya taml telu knda mlym', $st) !== false) {
  2022. $is_old_spec = true;
  2023. } else {
  2024. $is_old_spec = false;
  2025. }
  2026. // First get 'locl' substitutions (reversed!)
  2027. $loclsubs = array();
  2028. foreach ($volt AS $v) {
  2029. if (strpos('locl', $v['tag']) !== false) {
  2030. $key = $v['match'];
  2031. $key = preg_replace('/[\(\)]*/', '', $key);
  2032. $sub = $v['replace'];
  2033. if ($key && strlen(trim($key)) == 5 && $sub) {
  2034. $loclsubs[$sub] = $key;
  2035. }
  2036. }
  2037. }
  2038. //if (count($loclsubs)) { print_r($loclsubs); exit; }
  2039. foreach ($volt AS $v) {
  2040. // <rphf> <half> <pref> <blwf> <pstf>
  2041. // defines consonant types:
  2042. // Reph <rphf>
  2043. // Half forms <half>
  2044. // Pre-base-reordering forms of Ra/Rra <pref>
  2045. // Below-base forms <blwf>
  2046. // Post-base forms <pstf>
  2047. // applied together with <locl> feature to input sequences consisting of two characters
  2048. // This is done for each consonant
  2049. // for <rphf> and <half>, features are applied to Consonant + Halant combinations
  2050. // for <pref>, <blwf> and <pstf>, features are applied to Halant + Consonant combinations
  2051. // Old version eg 'deva' <pref>, <blwf> and <pstf>, features are applied to Consonant + Halant
  2052. // Some malformed fonts still do Consonant + Halant for these - so match both??
  2053. // If these two glyphs form a ligature, with no additional glyphs in context
  2054. // this means the consonant has the corresponding form
  2055. // Currently set to cope with both
  2056. // See also classes/otl.php
  2057. if (strpos('rphf half pref blwf pstf', $v['tag']) !== false) {
  2058. if (isset($v['context']) && $v['context'] && $v['nBacktrack'] == 0 && $v['nLookahead'] == 0) {
  2059. foreach ($v['rules'] AS $vs) {
  2060. if (count($vs['match']) == 2 && count($vs['replace']) == 1) {
  2061. $sub = $vs['replace'][0];
  2062. // If Halant Cons <pref>, <blwf> and <pstf> in New version only
  2063. if (strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', $vs['match'][0]) !== false && strpos('pref blwf pstf', $v['tag']) !== false && !$is_old_spec) {
  2064. $key = $vs['match'][1];
  2065. $tag = $v['tag'];
  2066. if (isset($loclsubs[$key])) {
  2067. $$tag[$loclsubs[$key]] = $sub;
  2068. }
  2069. $tmp = &$$tag;
  2070. $tmp[hexdec($key)] = hexdec($sub);
  2071. }
  2072. // If Cons Halant <rphf> and <half> always
  2073. // and <pref>, <blwf> and <pstf> in Old version
  2074. else if (strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', $vs['match'][1]) !== false && (strpos('rphf half', $v['tag']) !== false || (strpos('pref blwf pstf', $v['tag']) !== false && ($is_old_spec || _OTL_OLD_SPEC_COMPAT_2)))) {
  2075. $key = $vs['match'][0];
  2076. $tag = $v['tag'];
  2077. if (isset($loclsubs[$key])) {
  2078. $$tag[$loclsubs[$key]] = $sub;
  2079. }
  2080. $tmp = &$$tag;
  2081. $tmp[hexdec($key)] = hexdec($sub);
  2082. }
  2083. }
  2084. }
  2085. } else if (!isset($v['context'])) {
  2086. $key = $v['match'];
  2087. $key = preg_replace('/[\(\)]*/', '', $key);
  2088. $sub = $v['replace'];
  2089. if ($key && strlen(trim($key)) == 11 && $sub) {
  2090. // If Cons Halant <rphf> and <half> always
  2091. // and <pref>, <blwf> and <pstf> in Old version
  2092. // If Halant Cons <pref>, <blwf> and <pstf> in New version only
  2093. if (strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', substr($key, 0, 5)) !== false && strpos('pref blwf pstf', $v['tag']) !== false && !$is_old_spec) {
  2094. $key = substr($key, 6, 5);
  2095. $tag = $v['tag'];
  2096. if (isset($loclsubs[$key])) {
  2097. $$tag[$loclsubs[$key]] = $sub;
  2098. }
  2099. $tmp = &$$tag;
  2100. $tmp[hexdec($key)] = hexdec($sub);
  2101. } else if (strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', substr($key, 6, 5)) !== false && (strpos('rphf half', $v['tag']) !== false || (strpos('pref blwf pstf', $v['tag']) !== false && ($is_old_spec || _OTL_OLD_SPEC_COMPAT_2)))) {
  2102. $key = substr($key, 0, 5);
  2103. $tag = $v['tag'];
  2104. if (isset($loclsubs[$key])) {
  2105. $$tag[$loclsubs[$key]] = $sub;
  2106. }
  2107. $tmp = &$$tag;
  2108. $tmp[hexdec($key)] = hexdec($sub);
  2109. }
  2110. }
  2111. }
  2112. }
  2113. }
  2114. /*
  2115. print_r($rphf );
  2116. print_r($half );
  2117. print_r($pref );
  2118. print_r($blwf );
  2119. print_r($pstf ); exit;
  2120. */
  2121. }
  2122. //print_r($rtlpua); exit;
  2123. //=====================================================================================
  2124. //=====================================================================================
  2125. //=====================================================================================
  2126. //=====================================================================================
  2127. if (count($rtl) || count($rphf) || count($half) || count($pref) || count($blwf) || count($pstf) || $finals) {
  2128. // SAVE LOOKUPS TO FILE fontname.GSUB.scripttag.langtag.php
  2129. $s = '<?php
  2130. $rtlSUB = ' . $rtlSUB . ';
  2131. $finals = \'' . $finals . '\';
  2132. $rphf = ' . var_export($rphf, true) . ';
  2133. $half = ' . var_export($half, true) . ';
  2134. $pref = ' . var_export($pref, true) . ';
  2135. $blwf = ' . var_export($blwf, true) . ';
  2136. $pstf = ' . var_export($pstf, true) . ';
  2137. ' . "\n" . '?>';
  2138. file_put_contents(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GSUB.' . $st . '.' . $t . '.php', $s);
  2139. }
  2140. //=====================================================================================
  2141. if (!isset($GSUBScriptLang[$st])) {
  2142. $GSUBScriptLang[$st] = '';
  2143. }
  2144. $GSUBScriptLang[$st] .= $t . ' ';
  2145. //=====================================================================================
  2146. }
  2147. }
  2148. //print_r($rtlpua); exit;
  2149. // All RTL glyphs from font added to (or already in) PUA [reqd for magic_reverse]
  2150. $rtlPUAstr = "";
  2151. if (count($rtlpua)) {
  2152. $rtlpua = array_unique($rtlpua);
  2153. sort($rtlpua);
  2154. $n = count($rtlpua);
  2155. for ($i = 0; $i < $n; $i++) {
  2156. if (hexdec($rtlpua[$i]) < hexdec('E000') || hexdec($rtlpua[$i]) > hexdec('F8FF')) {
  2157. unset($rtlpua[$i]);
  2158. }
  2159. }
  2160. sort($rtlpua, SORT_STRING);
  2161. $rangeid = -1;
  2162. $range = array();
  2163. $prevgid = -2;
  2164. // for each character
  2165. foreach ($rtlpua as $gidhex) {
  2166. $gid = hexdec($gidhex);
  2167. if ($gid == ($prevgid + 1)) {
  2168. $range[$rangeid]['end'] = $gidhex;
  2169. $range[$rangeid]['count'] ++;
  2170. } else {
  2171. // new range
  2172. $rangeid++;
  2173. $range[$rangeid] = array();
  2174. $range[$rangeid]['start'] = $gidhex;
  2175. $range[$rangeid]['end'] = $gidhex;
  2176. $range[$rangeid]['count'] = 1;
  2177. }
  2178. $prevgid = $gid;
  2179. }
  2180. foreach ($range AS $rg) {
  2181. if ($rg['count'] == 1) {
  2182. $rtlPUAstr .= "\x{" . $rg['start'] . "}";
  2183. } else if ($rg['count'] == 2) {
  2184. $rtlPUAstr .= "\x{" . $rg['start'] . "}\x{" . $rg['end'] . "}";
  2185. } else {
  2186. $rtlPUAstr .= "\x{" . $rg['start'] . "}-\x{" . $rg['end'] . "}";
  2187. }
  2188. }
  2189. }
  2190. //print_r($rtlPUAstr ); exit;
  2191. //=====================================================================================
  2192. //=====================================================================================
  2193. //=====================================================================================
  2194. //=====================================================================================
  2195. //print_r($rtlpua); exit;
  2196. //print_r($GSUBScriptLang); exit;
  2197. }
  2198. //print_r($Lookup); exit;
  2199. return array($GSUBScriptLang, $gsub, $GSLookup, $rtlPUAstr); // , $rtlPUAarr Not needed
  2200. }
  2201. /////////////////////////////////////////////////////////////////////////////////////////
  2202. // GSUB functions
  2203. function _getGSUBarray(&$Lookup, &$lul, $scripttag)
  2204. {
  2205. // Process (3) LookupList for specific Script-LangSys
  2206. // Generate preg_replace
  2207. $volt = array();
  2208. $reph = '';
  2209. $matraE = '';
  2210. $vatu = '';
  2211. foreach ($lul AS $i => $tag) {
  2212. for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
  2213. $SubstFormat = $Lookup[$i]['Subtable'][$c]['Format'];
  2214. // LookupType 1: Single Substitution Subtable
  2215. if ($Lookup[$i]['Type'] == 1) {
  2216. for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
  2217. $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
  2218. $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
  2219. // Ignore has already been applied earlier on
  2220. $repl = $this->_makeGSUBinputMatch($inputGlyphs, "()");
  2221. $subs = $this->_makeGSUBinputReplacement(1, $substitute, "()", 0, 1, 0);
  2222. $volt[] = array('match' => $repl, 'replace' => $subs, 'tag' => $tag, 'key' => $inputGlyphs[0], 'type' => 1);
  2223. }
  2224. }
  2225. // LookupType 2: Multiple Substitution Subtable
  2226. else if ($Lookup[$i]['Type'] == 2) {
  2227. for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
  2228. $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
  2229. $substitute = implode(" ", $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']);
  2230. // Ignore has already been applied earlier on
  2231. $repl = $this->_makeGSUBinputMatch($inputGlyphs, "()");
  2232. $subs = $this->_makeGSUBinputReplacement(1, $substitute, "()", 0, 1, 0);
  2233. $volt[] = array('match' => $repl, 'replace' => $subs, 'tag' => $tag, 'key' => $inputGlyphs[0], 'type' => 2);
  2234. }
  2235. }
  2236. // LookupType 3: Alternate Forms
  2237. else if ($Lookup[$i]['Type'] == 3) {
  2238. for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
  2239. $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
  2240. $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
  2241. // Ignore has already been applied earlier on
  2242. $repl = $this->_makeGSUBinputMatch($inputGlyphs, "()");
  2243. $subs = $this->_makeGSUBinputReplacement(1, $substitute, "()", 0, 1, 0);
  2244. $volt[] = array('match' => $repl, 'replace' => $subs, 'tag' => $tag, 'key' => $inputGlyphs[0], 'type' => 3);
  2245. }
  2246. }
  2247. // LookupType 4: Ligature Substitution Subtable
  2248. else if ($Lookup[$i]['Type'] == 4) {
  2249. for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
  2250. $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
  2251. $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
  2252. // Ignore has already been applied earlier on
  2253. $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
  2254. $repl = $this->_makeGSUBinputMatch($inputGlyphs, $ignore);
  2255. $subs = $this->_makeGSUBinputReplacement(count($inputGlyphs), $substitute, $ignore, 0, count($inputGlyphs), 0);
  2256. $volt[] = array('match' => $repl, 'replace' => $subs, 'tag' => $tag, 'key' => $inputGlyphs[0], 'type' => 4, 'CompCount' => $Lookup[$i]['Subtable'][$c]['subs'][$s]['CompCount'], 'Lig' => $substitute);
  2257. }
  2258. }
  2259. // LookupType 5: Chaining Contextual Substitution Subtable
  2260. else if ($Lookup[$i]['Type'] == 5) {
  2261. // Format 1: Context Substitution
  2262. if ($SubstFormat == 1) {
  2263. $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
  2264. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) {
  2265. // SubRuleSet
  2266. $subRule = array();
  2267. foreach ($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'] AS $rule) {
  2268. // SubRule
  2269. $inputGlyphs = array();
  2270. if ($rule['GlyphCount'] > 1) {
  2271. $inputGlyphs = $rule['InputGlyphs'];
  2272. }
  2273. $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph'];
  2274. ksort($inputGlyphs);
  2275. $nInput = count($inputGlyphs);
  2276. $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, array(), 0);
  2277. $subRule = array('context' => 1, 'tag' => $tag, 'matchback' => '', 'match' => $contextInputMatch, 'nBacktrack' => 0, 'nInput' => $nInput, 'nLookahead' => 0, 'rules' => array(),);
  2278. for ($b = 0; $b < $rule['SubstCount']; $b++) {
  2279. $lup = $rule['SubstLookupRecord'][$b]['LookupListIndex'];
  2280. $seqIndex = $rule['SubstLookupRecord'][$b]['SequenceIndex'];
  2281. // $Lookup[$lup] = secondary Lookup
  2282. for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
  2283. if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
  2284. foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] AS $luss) {
  2285. $lookupGlyphs = $luss['Replace'];
  2286. $mLen = count($lookupGlyphs);
  2287. // Only apply if the (first) 'Replace' glyph from the
  2288. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  2289. // then apply the substitution
  2290. if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) {
  2291. continue;
  2292. }
  2293. $REPL = implode(" ", $luss['substitute']);
  2294. if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
  2295. $volt[] = array('match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore);
  2296. } else {
  2297. $subRule['rules'][] = array('type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],);
  2298. }
  2299. }
  2300. }
  2301. }
  2302. }
  2303. if (count($subRule['rules']))
  2304. $volt[] = $subRule;
  2305. }
  2306. }
  2307. }
  2308. // Format 2: Class-based Context Glyph Substitution
  2309. else if ($SubstFormat == 2) {
  2310. $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
  2311. foreach ($Lookup[$i]['Subtable'][$c]['SubClassSet'] AS $inputClass => $cscs) {
  2312. for ($cscrule = 0; $cscrule < $cscs['SubClassRuleCnt']; $cscrule++) {
  2313. $rule = $cscs['SubClassRule'][$cscrule];
  2314. $inputGlyphs = array();
  2315. $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass];
  2316. if ($rule['InputGlyphCount'] > 1) {
  2317. // NB starts at 1
  2318. for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) {
  2319. $classindex = $rule['Input'][$gcl];
  2320. if (isset($Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex])) {
  2321. $inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex];
  2322. }
  2323. // if class[0] = all glyphs excluding those specified in all other classes
  2324. // set to blank '' for now
  2325. else {
  2326. $inputGlyphs[$gcl] = '';
  2327. }
  2328. }
  2329. }
  2330. $nInput = $rule['InputGlyphCount'];
  2331. $nIsubs = (2 * $nInput) - 1;
  2332. $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, array(), 0);
  2333. $subRule = array('context' => 1, 'tag' => $tag, 'matchback' => '', 'match' => $contextInputMatch, 'nBacktrack' => 0, 'nInput' => $nInput, 'nLookahead' => 0, 'rules' => array(),);
  2334. for ($b = 0; $b < $rule['SubstCount']; $b++) {
  2335. $lup = $rule['LookupListIndex'][$b];
  2336. $seqIndex = $rule['SequenceIndex'][$b];
  2337. // $Lookup[$lup] = secondary Lookup
  2338. for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
  2339. if (isset($Lookup[$lup]['Subtable'][$lus]['subs']) && count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
  2340. foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] AS $luss) {
  2341. $lookupGlyphs = $luss['Replace'];
  2342. $mLen = count($lookupGlyphs);
  2343. // Only apply if the (first) 'Replace' glyph from the
  2344. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  2345. // then apply the substitution
  2346. if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) {
  2347. continue;
  2348. }
  2349. // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
  2350. $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex);
  2351. $REPL = implode(" ", $luss['substitute']);
  2352. // Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}"
  2353. if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
  2354. $volt[] = array('match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore);
  2355. } else {
  2356. $subRule['rules'][] = array('type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],);
  2357. }
  2358. }
  2359. }
  2360. }
  2361. }
  2362. if (count($subRule['rules']))
  2363. $volt[] = $subRule;
  2364. }
  2365. }
  2366. }
  2367. // Format 3: Coverage-based Context Glyph Substitution p259
  2368. else if ($SubstFormat == 3) {
  2369. // IgnoreMarks flag set on main Lookup table
  2370. $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
  2371. $inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'];
  2372. $CoverageInputGlyphs = implode('|', $inputGlyphs);
  2373. $nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount'];
  2374. if ($Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']) {
  2375. $backtrackGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'];
  2376. } else {
  2377. $backtrackGlyphs = array();
  2378. }
  2379. // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦
  2380. $backtrackMatch = $this->_makeGSUBbacktrackMatch($backtrackGlyphs, $ignore);
  2381. if ($Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']) {
  2382. $lookaheadGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'];
  2383. } else {
  2384. $lookaheadGlyphs = array();
  2385. }
  2386. // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦
  2387. $lookaheadMatch = $this->_makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore);
  2388. $nBsubs = 2 * count($backtrackGlyphs);
  2389. $nIsubs = (2 * $nInput) - 1;
  2390. $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, array(), 0);
  2391. $subRule = array('context' => 1, 'tag' => $tag, 'matchback' => $backtrackMatch, 'match' => ($contextInputMatch . $lookaheadMatch), 'nBacktrack' => count($backtrackGlyphs), 'nInput' => $nInput, 'nLookahead' => count($lookaheadGlyphs), 'rules' => array(),);
  2392. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) {
  2393. $lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'];
  2394. $seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'];
  2395. for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
  2396. if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
  2397. foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] AS $luss) {
  2398. $lookupGlyphs = $luss['Replace'];
  2399. $mLen = count($lookupGlyphs);
  2400. // Only apply if the (first) 'Replace' glyph from the
  2401. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  2402. // then apply the substitution
  2403. if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) {
  2404. continue;
  2405. }
  2406. // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
  2407. $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex);
  2408. $REPL = implode(" ", $luss['substitute']);
  2409. if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
  2410. $volt[] = array('match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore);
  2411. } else {
  2412. $subRule['rules'][] = array('type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],);
  2413. }
  2414. }
  2415. }
  2416. }
  2417. }
  2418. if (count($subRule['rules']))
  2419. $volt[] = $subRule;
  2420. }
  2421. //print_r($Lookup[$i]);
  2422. //print_r($volt[(count($volt)-1)]); exit;
  2423. }
  2424. // LookupType 6: ing Contextual Substitution Subtable
  2425. else if ($Lookup[$i]['Type'] == 6) {
  2426. // Format 1: Simple Chaining Context Glyph Substitution p255
  2427. if ($SubstFormat == 1) {
  2428. $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
  2429. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $s++) {
  2430. // ChainSubRuleSet
  2431. $subRule = array();
  2432. $firstInputGlyph = $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'][$s]; // First input gyyph
  2433. foreach ($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'] AS $rule) {
  2434. // ChainSubRule
  2435. $inputGlyphs = array();
  2436. if ($rule['InputGlyphCount'] > 1) {
  2437. $inputGlyphs = $rule['InputGlyphs'];
  2438. }
  2439. $inputGlyphs[0] = $firstInputGlyph;
  2440. ksort($inputGlyphs);
  2441. $nInput = count($inputGlyphs);
  2442. if ($rule['BacktrackGlyphCount']) {
  2443. $backtrackGlyphs = $rule['BacktrackGlyphs'];
  2444. } else {
  2445. $backtrackGlyphs = array();
  2446. }
  2447. $backtrackMatch = $this->_makeGSUBbacktrackMatch($backtrackGlyphs, $ignore);
  2448. if ($rule['LookaheadGlyphCount']) {
  2449. $lookaheadGlyphs = $rule['LookaheadGlyphs'];
  2450. } else {
  2451. $lookaheadGlyphs = array();
  2452. }
  2453. $lookaheadMatch = $this->_makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore);
  2454. $nBsubs = 2 * count($backtrackGlyphs);
  2455. $nIsubs = (2 * $nInput) - 1;
  2456. $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, array(), 0);
  2457. $subRule = array('context' => 1, 'tag' => $tag, 'matchback' => $backtrackMatch, 'match' => ($contextInputMatch . $lookaheadMatch), 'nBacktrack' => count($backtrackGlyphs), 'nInput' => $nInput, 'nLookahead' => count($lookaheadGlyphs), 'rules' => array(),);
  2458. for ($b = 0; $b < $rule['SubstCount']; $b++) {
  2459. $lup = $rule['LookupListIndex'][$b];
  2460. $seqIndex = $rule['SequenceIndex'][$b];
  2461. // $Lookup[$lup] = secondary Lookup
  2462. for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
  2463. if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
  2464. foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] AS $luss) {
  2465. $lookupGlyphs = $luss['Replace'];
  2466. $mLen = count($lookupGlyphs);
  2467. // Only apply if the (first) 'Replace' glyph from the
  2468. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  2469. // then apply the substitution
  2470. if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) {
  2471. continue;
  2472. }
  2473. // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
  2474. $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex);
  2475. $REPL = implode(" ", $luss['substitute']);
  2476. if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
  2477. $volt[] = array('match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore);
  2478. } else {
  2479. $subRule['rules'][] = array('type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],);
  2480. }
  2481. }
  2482. }
  2483. }
  2484. }
  2485. if (count($subRule['rules']))
  2486. $volt[] = $subRule;
  2487. }
  2488. }
  2489. }
  2490. // Format 2: Class-based Chaining Context Glyph Substitution p257
  2491. else if ($SubstFormat == 2) {
  2492. $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
  2493. foreach ($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'] AS $inputClass => $cscs) {
  2494. for ($cscrule = 0; $cscrule < $cscs['ChainSubClassRuleCnt']; $cscrule++) {
  2495. $rule = $cscs['ChainSubClassRule'][$cscrule];
  2496. // These contain classes of glyphs as strings
  2497. // $Lookup[$i]['Subtable'][$c]['InputClasses'][(class)] e.g. 02E6|02E7|02E8
  2498. // $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][(class)]
  2499. // $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][(class)]
  2500. // These contain arrays of classIndexes
  2501. // [Backtrack] [Lookahead] and [Input] (Input is from the second position only)
  2502. $inputGlyphs = array();
  2503. if (isset($Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass])) {
  2504. $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass];
  2505. } else {
  2506. $inputGlyphs[0] = '';
  2507. }
  2508. if ($rule['InputGlyphCount'] > 1) {
  2509. // NB starts at 1
  2510. for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) {
  2511. $classindex = $rule['Input'][$gcl];
  2512. if (isset($Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex])) {
  2513. $inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex];
  2514. }
  2515. // if class[0] = all glyphs excluding those specified in all other classes
  2516. // set to blank '' for now
  2517. else {
  2518. $inputGlyphs[$gcl] = '';
  2519. }
  2520. }
  2521. }
  2522. $nInput = $rule['InputGlyphCount'];
  2523. if ($rule['BacktrackGlyphCount']) {
  2524. for ($gcl = 0; $gcl < $rule['BacktrackGlyphCount']; $gcl++) {
  2525. $classindex = $rule['Backtrack'][$gcl];
  2526. if (isset($Lookup[$i]['Subtable'][$c]['BacktrackClasses'][$classindex])) {
  2527. $backtrackGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][$classindex];
  2528. }
  2529. // if class[0] = all glyphs excluding those specified in all other classes
  2530. // set to blank '' for now
  2531. else {
  2532. $backtrackGlyphs[$gcl] = '';
  2533. }
  2534. }
  2535. } else {
  2536. $backtrackGlyphs = array();
  2537. }
  2538. // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦
  2539. $backtrackMatch = $this->_makeGSUBbacktrackMatch($backtrackGlyphs, $ignore);
  2540. if ($rule['LookaheadGlyphCount']) {
  2541. for ($gcl = 0; $gcl < $rule['LookaheadGlyphCount']; $gcl++) {
  2542. $classindex = $rule['Lookahead'][$gcl];
  2543. if (isset($Lookup[$i]['Subtable'][$c]['LookaheadClasses'][$classindex])) {
  2544. $lookaheadGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][$classindex];
  2545. }
  2546. // if class[0] = all glyphs excluding those specified in all other classes
  2547. // set to blank '' for now
  2548. else {
  2549. $lookaheadGlyphs[$gcl] = '';
  2550. }
  2551. }
  2552. } else {
  2553. $lookaheadGlyphs = array();
  2554. }
  2555. // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦
  2556. $lookaheadMatch = $this->_makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore);
  2557. $nBsubs = 2 * count($backtrackGlyphs);
  2558. $nIsubs = (2 * $nInput) - 1;
  2559. $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, array(), 0);
  2560. $subRule = array('context' => 1, 'tag' => $tag, 'matchback' => $backtrackMatch, 'match' => ($contextInputMatch . $lookaheadMatch), 'nBacktrack' => count($backtrackGlyphs), 'nInput' => $nInput, 'nLookahead' => count($lookaheadGlyphs), 'rules' => array(),);
  2561. for ($b = 0; $b < $rule['SubstCount']; $b++) {
  2562. $lup = $rule['LookupListIndex'][$b];
  2563. $seqIndex = $rule['SequenceIndex'][$b];
  2564. // $Lookup[$lup] = secondary Lookup
  2565. for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
  2566. if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
  2567. foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] AS $luss) {
  2568. $lookupGlyphs = $luss['Replace'];
  2569. $mLen = count($lookupGlyphs);
  2570. // Only apply if the (first) 'Replace' glyph from the
  2571. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  2572. // then apply the substitution
  2573. if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) {
  2574. continue;
  2575. }
  2576. // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
  2577. $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex);
  2578. $REPL = implode(" ", $luss['substitute']);
  2579. // Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}"
  2580. if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
  2581. $volt[] = array('match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore);
  2582. } else {
  2583. $subRule['rules'][] = array('type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],);
  2584. }
  2585. }
  2586. }
  2587. }
  2588. }
  2589. if (count($subRule['rules']))
  2590. $volt[] = $subRule;
  2591. }
  2592. }
  2593. //print_r($Lookup[$i]['Subtable'][$c]); exit;
  2594. }
  2595. // Format 3: Coverage-based Chaining Context Glyph Substitution p259
  2596. else if ($SubstFormat == 3) {
  2597. // IgnoreMarks flag set on main Lookup table
  2598. $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
  2599. $inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'];
  2600. $CoverageInputGlyphs = implode('|', $inputGlyphs);
  2601. $nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount'];
  2602. if ($Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']) {
  2603. $backtrackGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'];
  2604. } else {
  2605. $backtrackGlyphs = array();
  2606. }
  2607. // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦
  2608. $backtrackMatch = $this->_makeGSUBbacktrackMatch($backtrackGlyphs, $ignore);
  2609. if ($Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']) {
  2610. $lookaheadGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'];
  2611. } else {
  2612. $lookaheadGlyphs = array();
  2613. }
  2614. // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦
  2615. $lookaheadMatch = $this->_makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore);
  2616. $nBsubs = 2 * count($backtrackGlyphs);
  2617. $nIsubs = (2 * $nInput) - 1;
  2618. $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, array(), 0);
  2619. $subRule = array('context' => 1, 'tag' => $tag, 'matchback' => $backtrackMatch, 'match' => ($contextInputMatch . $lookaheadMatch), 'nBacktrack' => count($backtrackGlyphs), 'nInput' => $nInput, 'nLookahead' => count($lookaheadGlyphs), 'rules' => array(),);
  2620. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) {
  2621. $lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'];
  2622. $seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'];
  2623. for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
  2624. if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
  2625. foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] AS $luss) {
  2626. $lookupGlyphs = $luss['Replace'];
  2627. $mLen = count($lookupGlyphs);
  2628. // Only apply if the (first) 'Replace' glyph from the
  2629. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  2630. // then apply the substitution
  2631. if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) {
  2632. continue;
  2633. }
  2634. // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
  2635. $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex);
  2636. $REPL = implode(" ", $luss['substitute']);
  2637. if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') {
  2638. $volt[] = array('match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore);
  2639. } else {
  2640. $subRule['rules'][] = array('type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],);
  2641. }
  2642. }
  2643. }
  2644. }
  2645. }
  2646. if (count($subRule['rules']))
  2647. $volt[] = $subRule;
  2648. }
  2649. }
  2650. }
  2651. }
  2652. //print_r($Lookup); exit;
  2653. return $volt;
  2654. }
  2655. //=====================================================================================
  2656. //=====================================================================================
  2657. // mPDF 5.7.1
  2658. function _checkGSUBignore($flag, $glyph, $MarkFilteringSet)
  2659. {
  2660. $ignore = false;
  2661. // Flag & 0x0008 = Ignore Marks - (unless already done with MarkAttachmentType)
  2662. if ((($flag & 0x0008) == 0x0008 && ($flag & 0xFF00) == 0) && strpos($this->GlyphClassMarks, $glyph)) {
  2663. $ignore = true;
  2664. }
  2665. if ((($flag & 0x0004) == 0x0004) && strpos($this->GlyphClassLigatures, $glyph)) {
  2666. $ignore = true;
  2667. }
  2668. if ((($flag & 0x0002) == 0x0002) && strpos($this->GlyphClassBases, $glyph)) {
  2669. $ignore = true;
  2670. }
  2671. // Flag & 0xFF?? = MarkAttachmentType
  2672. if ($flag & 0xFF00) {
  2673. // "a lookup must ignore any mark glyphs that are not in the specified mark attachment class"
  2674. // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table
  2675. if (strpos($this->MarkAttachmentType[($flag >> 8)], $glyph)) {
  2676. $ignore = true;
  2677. }
  2678. }
  2679. // Flag & 0x0010 = UseMarkFilteringSet
  2680. if (($flag & 0x0010) && strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) {
  2681. $ignore = true;
  2682. }
  2683. return $ignore;
  2684. }
  2685. function _getGSUBignoreString($flag, $MarkFilteringSet)
  2686. {
  2687. // If ignoreFlag set, combine all ignore glyphs into -> "((?:(?: FBA1| FBA2| FBA3))*)"
  2688. // else "()"
  2689. // for Input - set on secondary Lookup table if in Context, and set Backtrack and Lookahead on Context Lookup
  2690. $str = "";
  2691. $ignoreflag = 0;
  2692. // Flag & 0xFF?? = MarkAttachmentType
  2693. if ($flag & 0xFF00) {
  2694. // "a lookup must ignore any mark glyphs that are not in the specified mark attachment class"
  2695. // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table
  2696. $MarkAttachmentType = $flag >> 8;
  2697. $ignoreflag = $flag;
  2698. $str = $this->MarkAttachmentType[$MarkAttachmentType];
  2699. }
  2700. // Flag & 0x0010 = UseMarkFilteringSet
  2701. if ($flag & 0x0010) {
  2702. throw new MpdfException("This font " . $this->fontkey . " contains MarkGlyphSets - Not tested yet");
  2703. $str = $this->MarkGlyphSets[$MarkFilteringSet];
  2704. }
  2705. // If Ignore Marks set, supercedes any above
  2706. // Flag & 0x0008 = Ignore Marks - (unless already done with MarkAttachmentType)
  2707. if (($flag & 0x0008) == 0x0008 && ($flag & 0xFF00) == 0) {
  2708. $ignoreflag = 8;
  2709. $str = $this->GlyphClassMarks;
  2710. }
  2711. // Flag & 0x0004 = Ignore Ligatures
  2712. if (($flag & 0x0004) == 0x0004) {
  2713. $ignoreflag += 4;
  2714. if ($str) {
  2715. $str .= "|";
  2716. }
  2717. $str .= $this->GlyphClassLigatures;
  2718. }
  2719. // Flag & 0x0002 = Ignore BaseGlyphs
  2720. if (($flag & 0x0002) == 0x0002) {
  2721. $ignoreflag += 2;
  2722. if ($str) {
  2723. $str .= "|";
  2724. }
  2725. $str .= $this->GlyphClassBases;
  2726. }
  2727. if ($str) {
  2728. // This originally returned e.g. ((?:(?:[IGNORE8]))*) when NOT specific to a Lookup e.g. rtlSub in
  2729. // arabictypesetting.GSUB.arab.DFLT.php
  2730. // This would save repeatedly saving long text strings if used multiple times
  2731. // When writing e.g. arabictypesetting.GSUB.arab.DFLT.php to file, included as $ignore[8]
  2732. // Would need to also write the $ignore array to that file
  2733. // // If UseMarkFilteringSet (specific to the Lookup) return the string
  2734. // if (($flag & 0x0010) && ($flag & 0x0008) != 0x0008) {
  2735. // return "((?:(?:" . $str . "))*)";
  2736. // }
  2737. // else { return "((?:(?:" . "[IGNORE".$ignoreflag."]" . "))*)"; }
  2738. // // e.g. ((?:(?: 0031| 0032| 0033| 0034| 0045))*)
  2739. // But never finished coding it to add the $ignore array to the file, and it doesn't seem to occur often enough to be worth
  2740. // writing. So just output it as a string:
  2741. return "((?:(?:" . $str . "))*)";
  2742. } else
  2743. return "()";
  2744. }
  2745. // GSUB Patterns
  2746. /*
  2747. BACKTRACK INPUT LOOKAHEAD
  2748. ================================== ================== ==================================
  2749. (FEEB|FEEC)(ign) ¦(FD12|FD13)(ign) ¦(0612)¦(ign) (0613)¦(ign) (FD12|FD13)¦(ign) (FEEB|FEEC)
  2750. ---------------- ---------------- ----- ------------ --------------- ---------------
  2751. Backtrack 1 Backtrack 2 Input 1 Input 2 Lookahead 1 Lookahead 2
  2752. -------- --- --------- --- ---- --- ---- --- --------- --- -------
  2753. \${1} \${2} \${3} \${4} \${5+} \${6+} \${7+} \${8+}
  2754. nBacktrack = 2 nInput = 2 nLookahead = 2
  2755. nBsubs = 2xnBack nIsubs = (nBsubs+) nLsubs = (nBsubs+nIsubs+) 2xnLookahead
  2756. "\${1}\${2} " (nInput*2)-1 "\${5+} \${6+}"
  2757. "REPL"
  2758. ¦\${1}\${2} ¦\${3}\${4} ¦REPL¦\${5+} \${6+}¦\${7+} \${8+}¦
  2759. INPUT nInput = 5
  2760. ============================================================
  2761. ¦(0612)¦(ign) (0613)¦(ign) (0614)¦(ign) (0615)¦(ign) (0615)¦
  2762. \${1} \${2} \${3} \${4} \${5} \${6} \${7} \${8} \${9} (All backreference numbers are + nBsubs)
  2763. ----- ------------ ------------ ------------ ------------
  2764. Input 1 Input 2 Input 3 Input 4 Input 5
  2765. A====== SequenceIndex=1 ; Lookup match nGlyphs=1
  2766. B=================== SequenceIndex=1 ; Lookup match nGlyphs=2
  2767. C=============================== SequenceIndex=1 ; Lookup match nGlyphs=3
  2768. D======================= SequenceIndex=2 ; Lookup match nGlyphs=2
  2769. E===================================== SequenceIndex=2 ; Lookup match nGlyphs=3
  2770. F====================== SequenceIndex=4 ; Lookup match nGlyphs=2
  2771. All backreference numbers are + nBsubs
  2772. A - "REPL\${2} \${3}\${4} \${5}\${6} \${7}\${8} \${9}"
  2773. B - "REPL\${2}\${4} \${5}\${6} \${7}\${8} \${9}"
  2774. C - "REPL\${2}\${4}\${6} \${7}\${8} \${9}"
  2775. D - "\${1} REPL\${2}\${4}\${6} \${7}\${8} \${9}"
  2776. E - "\${1} REPL\${2}\${4}\${6}\${8} \${9}"
  2777. F - "\${1}\${2} \${3}\${4} \${5} REPL\${6}\${8}"
  2778. */
  2779. function _makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex)
  2780. {
  2781. // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
  2782. // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
  2783. // $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context
  2784. // $lookupGlyphs = array of glyphs (single Glyphs) making up Lookup Input sequence
  2785. $mLen = count($lookupGlyphs); // nGlyphs in the secondary Lookup match
  2786. $nInput = count($inputGlyphs); // nGlyphs in the Primary Input sequence
  2787. $str = "";
  2788. for ($i = 0; $i < $nInput; $i++) {
  2789. if ($i > 0) {
  2790. $str .= $ignore . " ";
  2791. }
  2792. if ($i >= $seqIndex && $i < ($seqIndex + $mLen)) {
  2793. $str .= "(" . $lookupGlyphs[($i - $seqIndex)] . ")";
  2794. } else {
  2795. $str .= "(" . $inputGlyphs[($i)] . ")";
  2796. }
  2797. }
  2798. return $str;
  2799. }
  2800. function _makeGSUBinputMatch($inputGlyphs, $ignore)
  2801. {
  2802. // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
  2803. // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
  2804. // $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context
  2805. // $lookupGlyphs = array of glyphs making up Lookup Input sequence - if applicable
  2806. $str = "";
  2807. for ($i = 1; $i <= count($inputGlyphs); $i++) {
  2808. if ($i > 1) {
  2809. $str .= $ignore . " ";
  2810. }
  2811. $str .= "(" . $inputGlyphs[($i - 1)] . ")";
  2812. }
  2813. return $str;
  2814. }
  2815. function _makeGSUBbacktrackMatch($backtrackGlyphs, $ignore)
  2816. {
  2817. // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
  2818. // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦
  2819. // $backtrackGlyphs = array of glyphstrings making up Backtrack sequence
  2820. // 3 2 1 0
  2821. // each item being e.g. E0AD|E0AF|F1FD
  2822. $str = "";
  2823. for ($i = (count($backtrackGlyphs) - 1); $i >= 0; $i--) {
  2824. $str .= "(" . $backtrackGlyphs[$i] . ")" . $ignore . " ";
  2825. }
  2826. return $str;
  2827. }
  2828. function _makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore)
  2829. {
  2830. // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
  2831. // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦
  2832. // $lookaheadGlyphs = array of glyphstrings making up Lookahead sequence
  2833. // 0 1 2 3
  2834. // each item being e.g. E0AD|E0AF|F1FD
  2835. $str = "";
  2836. for ($i = 0; $i < count($lookaheadGlyphs); $i++) {
  2837. $str .= $ignore . " (" . $lookaheadGlyphs[$i] . ")";
  2838. }
  2839. return $str;
  2840. }
  2841. function _makeGSUBinputReplacement($nInput, $REPL, $ignore, $nBsubs, $mLen, $seqIndex)
  2842. {
  2843. // Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}"
  2844. // $nInput nGlyphs in the Primary Input sequence
  2845. // $REPL replacement glyphs from secondary lookup
  2846. // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
  2847. // $nBsubs Number of Backtrack substitutions (= 2x Number of Backtrack glyphs)
  2848. // $mLen nGlyphs in the secondary Lookup match - if no secondary lookup, should=$nInput
  2849. // $seqIndex Sequence Index to apply the secondary match
  2850. if ($ignore == "()") {
  2851. $ign = false;
  2852. } else {
  2853. $ign = true;
  2854. }
  2855. $str = "";
  2856. if ($nInput == 1) {
  2857. $str = $REPL;
  2858. } else if ($nInput > 1) {
  2859. if ($mLen == $nInput) { // whole string replaced
  2860. $str = $REPL;
  2861. if ($ign) {
  2862. // for every nInput over 1, add another replacement backreference, to move IGNORES after replacement
  2863. for ($x = 2; $x <= $nInput; $x++) {
  2864. $str .= '\\' . ($nBsubs + (2 * ($x - 1)));
  2865. }
  2866. }
  2867. } else { // if only part of string replaced:
  2868. for ($x = 1; $x < ($seqIndex + 1); $x++) {
  2869. if ($x == 1) {
  2870. $str .= '\\' . ($nBsubs + 1);
  2871. } else {
  2872. if ($ign) {
  2873. $str .= '\\' . ($nBsubs + (2 * ($x - 1)));
  2874. }
  2875. $str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1)));
  2876. }
  2877. }
  2878. if ($seqIndex > 0) {
  2879. $str .= " ";
  2880. }
  2881. $str .= $REPL;
  2882. if ($ign) {
  2883. for ($x = (max(($seqIndex + 1), 2)); $x < ($seqIndex + 1 + $mLen); $x++) { // move IGNORES after replacement
  2884. $str .= '\\' . ($nBsubs + (2 * ($x - 1)));
  2885. }
  2886. }
  2887. for ($x = ($seqIndex + 1 + $mLen); $x <= $nInput; $x++) {
  2888. if ($ign) {
  2889. $str .= '\\' . ($nBsubs + (2 * ($x - 1)));
  2890. }
  2891. $str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1)));
  2892. }
  2893. }
  2894. }
  2895. return $str;
  2896. }
  2897. //////////////////////////////////////////////////////////////////////////////////
  2898. function _getCoverage($convert2hex = true, $mode = 1)
  2899. {
  2900. $g = array();
  2901. $ctr = 0;
  2902. $CoverageFormat = $this->read_ushort();
  2903. if ($CoverageFormat == 1) {
  2904. $CoverageGlyphCount = $this->read_ushort();
  2905. for ($gid = 0; $gid < $CoverageGlyphCount; $gid++) {
  2906. $glyphID = $this->read_ushort();
  2907. $uni = $this->glyphToChar[$glyphID][0];
  2908. if ($convert2hex) {
  2909. $g[] = unicode_hex($uni);
  2910. } else if ($mode == 2) {
  2911. $g[$uni] = $ctr;
  2912. $ctr++;
  2913. } else {
  2914. $g[] = $glyphID;
  2915. }
  2916. }
  2917. }
  2918. if ($CoverageFormat == 2) {
  2919. $RangeCount = $this->read_ushort();
  2920. for ($r = 0; $r < $RangeCount; $r++) {
  2921. $start = $this->read_ushort();
  2922. $end = $this->read_ushort();
  2923. $StartCoverageIndex = $this->read_ushort(); // n/a
  2924. for ($glyphID = $start; $glyphID <= $end; $glyphID++) {
  2925. $uni = $this->glyphToChar[$glyphID][0];
  2926. if ($convert2hex) {
  2927. $g[] = unicode_hex($uni);
  2928. } else if ($mode == 2) {
  2929. $uni = $g[$uni] = $ctr;
  2930. $ctr++;
  2931. } else {
  2932. $g[] = $glyphID;
  2933. }
  2934. }
  2935. }
  2936. }
  2937. return $g;
  2938. }
  2939. //////////////////////////////////////////////////////////////////////////////////
  2940. function _getClasses($offset)
  2941. {
  2942. $this->seek($offset);
  2943. $ClassFormat = $this->read_ushort();
  2944. $GlyphByClass = array();
  2945. if ($ClassFormat == 1) {
  2946. $StartGlyph = $this->read_ushort();
  2947. $GlyphCount = $this->read_ushort();
  2948. for ($i = 0; $i < $GlyphCount; $i++) {
  2949. $startGlyphID = $StartGlyph + $i;
  2950. $endGlyphID = $StartGlyph + $i;
  2951. $class = $this->read_ushort();
  2952. for ($g = $startGlyphID; $g <= $endGlyphID; $g++) {
  2953. if (isset($this->glyphToChar[$g][0])) {
  2954. $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]);
  2955. }
  2956. }
  2957. }
  2958. } else if ($ClassFormat == 2) {
  2959. $tableCount = $this->read_ushort();
  2960. for ($i = 0; $i < $tableCount; $i++) {
  2961. $startGlyphID = $this->read_ushort();
  2962. $endGlyphID = $this->read_ushort();
  2963. $class = $this->read_ushort();
  2964. for ($g = $startGlyphID; $g <= $endGlyphID; $g++) {
  2965. if ($this->glyphToChar[$g][0]) {
  2966. $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]);
  2967. }
  2968. }
  2969. }
  2970. }
  2971. $gbc = array();
  2972. foreach ($GlyphByClass AS $class => $garr) {
  2973. $gbc[$class] = implode('|', $garr);
  2974. }
  2975. return $gbc;
  2976. }
  2977. //////////////////////////////////////////////////////////////////////////////////
  2978. //////////////////////////////////////////////////////////////////////////////////
  2979. //////////////////////////////////////////////////////////////////////////////////
  2980. //////////////////////////////////////////////////////////////////////////////////
  2981. //////////////////////////////////////////////////////////////////////////////////
  2982. function _getGPOStables()
  2983. {
  2984. ///////////////////////////////////
  2985. // GPOS - Glyph Positioning
  2986. ///////////////////////////////////
  2987. if (isset($this->tables["GPOS"])) {
  2988. $ffeats = array();
  2989. $gpos_offset = $this->seek_table("GPOS");
  2990. $this->skip(4);
  2991. $ScriptList_offset = $gpos_offset + $this->read_ushort();
  2992. $FeatureList_offset = $gpos_offset + $this->read_ushort();
  2993. $LookupList_offset = $gpos_offset + $this->read_ushort();
  2994. // ScriptList
  2995. $this->seek($ScriptList_offset);
  2996. $ScriptCount = $this->read_ushort();
  2997. for ($i = 0; $i < $ScriptCount; $i++) {
  2998. $ScriptTag = $this->read_tag(); // = "beng", "deva" etc.
  2999. $ScriptTableOffset = $this->read_ushort();
  3000. $ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset;
  3001. }
  3002. // Script Table
  3003. foreach ($ffeats AS $t => $o) {
  3004. $ls = array();
  3005. $this->seek($o);
  3006. $DefLangSys_offset = $this->read_ushort();
  3007. if ($DefLangSys_offset > 0) {
  3008. $ls['DFLT'] = $DefLangSys_offset + $o;
  3009. }
  3010. $LangSysCount = $this->read_ushort();
  3011. for ($i = 0; $i < $LangSysCount; $i++) {
  3012. $LangTag = $this->read_tag(); // =
  3013. $LangTableOffset = $this->read_ushort();
  3014. $ls[$LangTag] = $o + $LangTableOffset;
  3015. }
  3016. $ffeats[$t] = $ls;
  3017. }
  3018. // Get FeatureIndexList
  3019. // LangSys Table - from first listed langsys
  3020. foreach ($ffeats AS $st => $scripts) {
  3021. foreach ($scripts AS $t => $o) {
  3022. $FeatureIndex = array();
  3023. $langsystable_offset = $o;
  3024. $this->seek($langsystable_offset);
  3025. $LookUpOrder = $this->read_ushort(); //==NULL
  3026. $ReqFeatureIndex = $this->read_ushort();
  3027. if ($ReqFeatureIndex != 0xFFFF) {
  3028. $FeatureIndex[] = $ReqFeatureIndex;
  3029. }
  3030. $FeatureCount = $this->read_ushort();
  3031. for ($i = 0; $i < $FeatureCount; $i++) {
  3032. $FeatureIndex[] = $this->read_ushort(); // = index of feature
  3033. }
  3034. $ffeats[$st][$t] = $FeatureIndex;
  3035. }
  3036. }
  3037. //print_r($ffeats); exit;
  3038. // Feauture List => LookupListIndex es
  3039. $this->seek($FeatureList_offset);
  3040. $FeatureCount = $this->read_ushort();
  3041. $Feature = array();
  3042. for ($i = 0; $i < $FeatureCount; $i++) {
  3043. $tag = $this->read_tag();
  3044. if ($tag == 'kern') {
  3045. $this->haskernGPOS = true;
  3046. }
  3047. $Feature[$i] = array('tag' => $tag);
  3048. $Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort();
  3049. }
  3050. for ($i = 0; $i < $FeatureCount; $i++) {
  3051. $this->seek($Feature[$i]['offset']);
  3052. $this->read_ushort(); // null
  3053. $Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort();
  3054. $Feature[$i]['LookupListIndex'] = array();
  3055. for ($c = 0; $c < $Lookupcount; $c++) {
  3056. $Feature[$i]['LookupListIndex'][] = $this->read_ushort();
  3057. }
  3058. }
  3059. foreach ($ffeats AS $st => $scripts) {
  3060. foreach ($scripts AS $t => $o) {
  3061. $FeatureIndex = $ffeats[$st][$t];
  3062. foreach ($FeatureIndex AS $k => $fi) {
  3063. $ffeats[$st][$t][$k] = $Feature[$fi];
  3064. }
  3065. }
  3066. }
  3067. //print_r($ffeats); exit;
  3068. //=====================================================================================
  3069. $gpos = array();
  3070. $GPOSScriptLang = array();
  3071. foreach ($ffeats AS $st => $scripts) {
  3072. foreach ($scripts AS $t => $langsys) {
  3073. $lg = array();
  3074. foreach ($langsys AS $ft) {
  3075. $lg[$ft['LookupListIndex'][0]] = $ft;
  3076. }
  3077. // list of Lookups in order they need to be run i.e. order listed in Lookup table
  3078. ksort($lg);
  3079. foreach ($lg AS $ft) {
  3080. $gpos[$st][$t][$ft['tag']] = $ft['LookupListIndex'];
  3081. }
  3082. if (!isset($GPOSScriptLang[$st])) {
  3083. $GPOSScriptLang[$st] = '';
  3084. }
  3085. $GPOSScriptLang[$st] .= $t . ' ';
  3086. }
  3087. }
  3088. //=====================================================================================
  3089. // Get metadata and offsets for whole Lookup List table
  3090. $this->seek($LookupList_offset);
  3091. $LookupCount = $this->read_ushort();
  3092. $Lookup = array();
  3093. $Offsets = array();
  3094. $SubtableCount = array();
  3095. for ($i = 0; $i < $LookupCount; $i++) {
  3096. $Offsets[$i] = $LookupList_offset + $this->read_ushort();
  3097. }
  3098. for ($i = 0; $i < $LookupCount; $i++) {
  3099. $this->seek($Offsets[$i]);
  3100. $Lookup[$i]['Type'] = $this->read_ushort();
  3101. $Lookup[$i]['Flag'] = $flag = $this->read_ushort();
  3102. $Lookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort();
  3103. for ($c = 0; $c < $SubtableCount[$i]; $c++) {
  3104. $Lookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort();
  3105. }
  3106. // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure
  3107. if (($flag & 0x0010) == 0x0010) {
  3108. $Lookup[$i]['MarkFilteringSet'] = $this->read_ushort();
  3109. } else {
  3110. $Lookup[$i]['MarkFilteringSet'] = '';
  3111. }
  3112. // Lookup Type 9: Extension
  3113. if ($Lookup[$i]['Type'] == 9) {
  3114. // Overwrites new offset (32-bit) for each subtable, and a new lookup Type
  3115. for ($c = 0; $c < $SubtableCount[$i]; $c++) {
  3116. $this->seek($Lookup[$i]['Subtables'][$c]);
  3117. $ExtensionPosFormat = $this->read_ushort();
  3118. $type = $this->read_ushort();
  3119. $Lookup[$i]['Subtables'][$c] = $Lookup[$i]['Subtables'][$c] + $this->read_ulong();
  3120. }
  3121. $Lookup[$i]['Type'] = $type;
  3122. }
  3123. }
  3124. //=====================================================================================
  3125. // Process Whole LookupList - Get LuCoverage = Lookup coverage just for first glyph
  3126. $this->LuCoverage = array();
  3127. for ($i = 0; $i < $LookupCount; $i++) {
  3128. for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
  3129. $this->seek($Lookup[$i]['Subtables'][$c]);
  3130. $PosFormat = $this->read_ushort();
  3131. if ($Lookup[$i]['Type'] == 7 && $PosFormat == 3) {
  3132. $this->skip(4);
  3133. } else if ($Lookup[$i]['Type'] == 8 && $PosFormat == 3) {
  3134. $BacktrackGlyphCount = $this->read_ushort();
  3135. $this->skip(2 * $BacktrackGlyphCount + 2);
  3136. }
  3137. // NB Coverage only looks at glyphs for position 1 (i.e. 7.3 and 8.3) // NEEDS TO READ ALL ********************
  3138. // NB For e.g. Type 4, this may be the Coverage for the Mark
  3139. $Coverage = $Lookup[$i]['Subtables'][$c] + $this->read_ushort();
  3140. $this->seek($Coverage);
  3141. $glyphs = $this->_getCoverage(false, 2);
  3142. $this->LuCoverage[$i][$c] = $glyphs;
  3143. }
  3144. }
  3145. //=====================================================================================
  3146. //print_r($GPOSScriptLang); exit;
  3147. //print_r($gpos); exit;
  3148. //print_r($Lookup); exit;
  3149. $s = '<?php
  3150. $LuCoverage = ' . var_export($this->LuCoverage, true) . ';
  3151. ?>';
  3152. file_put_contents(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GPOSdata.php', $s);
  3153. return array($GPOSScriptLang, $gpos, $Lookup);
  3154. } // end if GPOS
  3155. }
  3156. //////////////////////////////////////////////////////////////////////////////////
  3157. //=====================================================================================
  3158. //=====================================================================================
  3159. //=====================================================================================
  3160. //=====================================================================================
  3161. //=====================================================================================
  3162. //=====================================================================================
  3163. function makeSubset($file, &$subset, $TTCfontID = 0, $debug = false, $useOTL = false)
  3164. { // mPDF 5.7.1
  3165. $this->useOTL = $useOTL; // mPDF 5.7.1
  3166. $this->filename = $file;
  3167. $this->fh = fopen($file, 'rb');
  3168. if (!$this->fh) {
  3169. throw new MpdfException('Can\'t open file ' . $file);
  3170. }
  3171. $this->_pos = 0;
  3172. $this->charWidths = '';
  3173. $this->glyphPos = array();
  3174. $this->charToGlyph = array();
  3175. $this->tables = array();
  3176. $this->otables = array();
  3177. $this->ascent = 0;
  3178. $this->descent = 0;
  3179. $this->strikeoutSize = 0;
  3180. $this->strikeoutPosition = 0;
  3181. $this->numTTCFonts = 0;
  3182. $this->TTCFonts = array();
  3183. $this->skip(4);
  3184. $this->maxUni = 0;
  3185. if ($TTCfontID > 0) {
  3186. $this->version = $version = $this->read_ulong(); // TTC Header version now
  3187. if (!in_array($version, array(0x00010000, 0x00020000))) {
  3188. throw new MpdfException("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
  3189. }
  3190. $this->numTTCFonts = $this->read_ulong();
  3191. for ($i = 1; $i <= $this->numTTCFonts; $i++) {
  3192. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  3193. }
  3194. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  3195. $this->version = $version = $this->read_ulong(); // TTFont version again now
  3196. }
  3197. $this->readTableDirectory($debug);
  3198. ///////////////////////////////////
  3199. // head - Font header table
  3200. ///////////////////////////////////
  3201. $this->seek_table("head");
  3202. $this->skip(50);
  3203. $indexToLocFormat = $this->read_ushort();
  3204. $glyphDataFormat = $this->read_ushort();
  3205. ///////////////////////////////////
  3206. // hhea - Horizontal header table
  3207. ///////////////////////////////////
  3208. $this->seek_table("hhea");
  3209. $this->skip(32);
  3210. $metricDataFormat = $this->read_ushort();
  3211. $orignHmetrics = $numberOfHMetrics = $this->read_ushort();
  3212. ///////////////////////////////////
  3213. // maxp - Maximum profile table
  3214. ///////////////////////////////////
  3215. $this->seek_table("maxp");
  3216. $this->skip(4);
  3217. $numGlyphs = $this->read_ushort();
  3218. ///////////////////////////////////
  3219. // cmap - Character to glyph index mapping table
  3220. ///////////////////////////////////
  3221. $cmap_offset = $this->seek_table("cmap");
  3222. $this->skip(2);
  3223. $cmapTableCount = $this->read_ushort();
  3224. $unicode_cmap_offset = 0;
  3225. for ($i = 0; $i < $cmapTableCount; $i++) {
  3226. $platformID = $this->read_ushort();
  3227. $encodingID = $this->read_ushort();
  3228. $offset = $this->read_ulong();
  3229. $save_pos = $this->_pos;
  3230. if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
  3231. $format = $this->get_ushort($cmap_offset + $offset);
  3232. if ($format == 4) {
  3233. $unicode_cmap_offset = $cmap_offset + $offset;
  3234. break;
  3235. }
  3236. }
  3237. $this->seek($save_pos);
  3238. }
  3239. if (!$unicode_cmap_offset) {
  3240. throw new MpdfException('Font (' . $this->filename . ') does not have Unicode cmap (platform 3, encoding 1, format 4, or platform 0 [any encoding] format 4)');
  3241. }
  3242. $glyphToChar = array();
  3243. $charToGlyph = array();
  3244. $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph);
  3245. ///////////////////////////////////
  3246. // mPDF 5.7.1
  3247. // Map Unmapped glyphs - from $numGlyphs
  3248. if ($useOTL) {
  3249. $bctr = 0xE000;
  3250. for ($gid = 1; $gid < $numGlyphs; $gid++) {
  3251. if (!isset($glyphToChar[$gid])) {
  3252. while (isset($charToGlyph[$bctr])) {
  3253. $bctr++;
  3254. } // Avoid overwriting a glyph already mapped in PUA
  3255. if ($bctr > 0xF8FF) {
  3256. throw new MpdfException($file . " : WARNING - Font cannot map all included glyphs into Private Use Area U+E000 - U+F8FF; cannot use useOTL on this font");
  3257. }
  3258. $glyphToChar[$gid][] = $bctr;
  3259. $charToGlyph[$bctr] = $gid;
  3260. $bctr++;
  3261. }
  3262. }
  3263. }
  3264. ///////////////////////////////////
  3265. $this->charToGlyph = $charToGlyph;
  3266. $this->glyphToChar = $glyphToChar;
  3267. ///////////////////////////////////
  3268. // hmtx - Horizontal metrics table
  3269. ///////////////////////////////////
  3270. $scale = 1; // not used
  3271. $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
  3272. ///////////////////////////////////
  3273. // loca - Index to location
  3274. ///////////////////////////////////
  3275. $this->getLOCA($indexToLocFormat, $numGlyphs);
  3276. $subsetglyphs = array(0 => 0, 1 => 1, 2 => 2);
  3277. $subsetCharToGlyph = array();
  3278. foreach ($subset AS $code) {
  3279. if (isset($this->charToGlyph[$code])) {
  3280. $subsetglyphs[$this->charToGlyph[$code]] = $code; // Old Glyph ID => Unicode
  3281. $subsetCharToGlyph[$code] = $this->charToGlyph[$code]; // Unicode to old GlyphID
  3282. }
  3283. $this->maxUni = max($this->maxUni, $code);
  3284. }
  3285. list($start, $dummy) = $this->get_table_pos('glyf');
  3286. $glyphSet = array();
  3287. ksort($subsetglyphs);
  3288. $n = 0;
  3289. $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.
  3290. foreach ($subsetglyphs AS $originalGlyphIdx => $uni) {
  3291. $fsLastCharIndex = max($fsLastCharIndex, $uni);
  3292. $glyphSet[$originalGlyphIdx] = $n; // old glyphID to new glyphID
  3293. $n++;
  3294. }
  3295. ksort($subsetCharToGlyph);
  3296. foreach ($subsetCharToGlyph AS $uni => $originalGlyphIdx) {
  3297. $codeToGlyph[$uni] = $glyphSet[$originalGlyphIdx];
  3298. }
  3299. $this->codeToGlyph = $codeToGlyph;
  3300. ksort($subsetglyphs);
  3301. foreach ($subsetglyphs AS $originalGlyphIdx => $uni) {
  3302. $this->getGlyphs($originalGlyphIdx, $start, $glyphSet, $subsetglyphs);
  3303. }
  3304. $numGlyphs = $numberOfHMetrics = count($subsetglyphs);
  3305. ///////////////////////////////////
  3306. // name - table copied from the original
  3307. ///////////////////////////////////
  3308. // MS spec says that "Platform and encoding ID's in the name table should be consistent with those in the cmap table.
  3309. // If they are not, the font will not load in Windows"
  3310. // Doesn't seem to be a problem?
  3311. ///////////////////////////////////
  3312. $this->add('name', $this->get_table('name'));
  3313. ///////////////////////////////////
  3314. //tables copied from the original
  3315. ///////////////////////////////////
  3316. $tags = array('cvt ', 'fpgm', 'prep', 'gasp');
  3317. foreach ($tags AS $tag) {
  3318. if (isset($this->tables[$tag])) {
  3319. $this->add($tag, $this->get_table($tag));
  3320. }
  3321. }
  3322. ///////////////////////////////////
  3323. // post - PostScript
  3324. ///////////////////////////////////
  3325. if (isset($this->tables['post'])) {
  3326. $opost = $this->get_table('post');
  3327. $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";
  3328. $this->add('post', $post);
  3329. }
  3330. ///////////////////////////////////
  3331. // Sort CID2GID map into segments of contiguous codes
  3332. ///////////////////////////////////
  3333. ksort($codeToGlyph);
  3334. unset($codeToGlyph[0]);
  3335. //unset($codeToGlyph[65535]);
  3336. $rangeid = 0;
  3337. $range = array();
  3338. $prevcid = -2;
  3339. $prevglidx = -1;
  3340. // for each character
  3341. foreach ($codeToGlyph as $cid => $glidx) {
  3342. if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) {
  3343. $range[$rangeid][] = $glidx;
  3344. } else {
  3345. // new range
  3346. $rangeid = $cid;
  3347. $range[$rangeid] = array();
  3348. $range[$rangeid][] = $glidx;
  3349. }
  3350. $prevcid = $cid;
  3351. $prevglidx = $glidx;
  3352. }
  3353. ///////////////////////////////////
  3354. // CMap table
  3355. ///////////////////////////////////
  3356. // cmap - Character to glyph mapping
  3357. $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF
  3358. $searchRange = 1;
  3359. $entrySelector = 0;
  3360. while ($searchRange * 2 <= $segCount) {
  3361. $searchRange = $searchRange * 2;
  3362. $entrySelector = $entrySelector + 1;
  3363. }
  3364. $searchRange = $searchRange * 2;
  3365. $rangeShift = $segCount * 2 - $searchRange;
  3366. $length = 16 + (8 * $segCount ) + ($numGlyphs + 1);
  3367. $cmap = array(0, 3, // Index : version, number of encoding subtables
  3368. 0, 0, // Encoding Subtable : platform (UNI=0), encoding 0
  3369. 0, 28, // Encoding Subtable : offset (hi,lo)
  3370. 0, 3, // Encoding Subtable : platform (UNI=0), encoding 3
  3371. 0, 28, // Encoding Subtable : offset (hi,lo)
  3372. 3, 1, // Encoding Subtable : platform (MS=3), encoding 1
  3373. 0, 28, // Encoding Subtable : offset (hi,lo)
  3374. 4, $length, 0, // Format 4 Mapping subtable: format, length, language
  3375. $segCount * 2,
  3376. $searchRange,
  3377. $entrySelector,
  3378. $rangeShift);
  3379. // endCode(s)
  3380. foreach ($range AS $start => $subrange) {
  3381. $endCode = $start + (count($subrange) - 1);
  3382. $cmap[] = $endCode; // endCode(s)
  3383. }
  3384. $cmap[] = 0xFFFF; // endCode of last Segment
  3385. $cmap[] = 0; // reservedPad
  3386. // startCode(s)
  3387. foreach ($range AS $start => $subrange) {
  3388. $cmap[] = $start; // startCode(s)
  3389. }
  3390. $cmap[] = 0xFFFF; // startCode of last Segment
  3391. // idDelta(s)
  3392. foreach ($range AS $start => $subrange) {
  3393. $idDelta = -($start - $subrange[0]);
  3394. $n += count($subrange);
  3395. $cmap[] = $idDelta; // idDelta(s)
  3396. }
  3397. $cmap[] = 1; // idDelta of last Segment
  3398. // idRangeOffset(s)
  3399. foreach ($range AS $subrange) {
  3400. $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
  3401. }
  3402. $cmap[] = 0; // idRangeOffset of last Segment
  3403. foreach ($range AS $subrange) {
  3404. foreach ($subrange AS $glidx) {
  3405. $cmap[] = $glidx;
  3406. }
  3407. }
  3408. $cmap[] = 0; // Mapping for last character
  3409. $cmapstr = '';
  3410. foreach ($cmap AS $cm) {
  3411. $cmapstr .= pack("n", $cm);
  3412. }
  3413. $this->add('cmap', $cmapstr);
  3414. ///////////////////////////////////
  3415. // glyf - Glyph data
  3416. ///////////////////////////////////
  3417. list($glyfOffset, $glyfLength) = $this->get_table_pos('glyf');
  3418. if ($glyfLength < $this->maxStrLenRead) {
  3419. $glyphData = $this->get_table('glyf');
  3420. }
  3421. $offsets = array();
  3422. $glyf = '';
  3423. $pos = 0;
  3424. $hmtxstr = '';
  3425. $xMinT = 0;
  3426. $yMinT = 0;
  3427. $xMaxT = 0;
  3428. $yMaxT = 0;
  3429. $advanceWidthMax = 0;
  3430. $minLeftSideBearing = 0;
  3431. $minRightSideBearing = 0;
  3432. $xMaxExtent = 0;
  3433. $maxPoints = 0; // points in non-compound glyph
  3434. $maxContours = 0; // contours in non-compound glyph
  3435. $maxComponentPoints = 0; // points in compound glyph
  3436. $maxComponentContours = 0; // contours in compound glyph
  3437. $maxComponentElements = 0; // number of glyphs referenced at top level
  3438. $maxComponentDepth = 0; // levels of recursion, set to 0 if font has only simple glyphs
  3439. $this->glyphdata = array();
  3440. foreach ($subsetglyphs AS $originalGlyphIdx => $uni) {
  3441. // hmtx - Horizontal Metrics
  3442. $hm = $this->getHMetric($orignHmetrics, $originalGlyphIdx);
  3443. $hmtxstr .= $hm;
  3444. $offsets[] = $pos;
  3445. $glyphPos = $this->glyphPos[$originalGlyphIdx];
  3446. $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
  3447. if ($glyfLength < $this->maxStrLenRead) {
  3448. $data = substr($glyphData, $glyphPos, $glyphLen);
  3449. } else {
  3450. if ($glyphLen > 0)
  3451. $data = $this->get_chunk($glyfOffset + $glyphPos, $glyphLen);
  3452. else
  3453. $data = '';
  3454. }
  3455. if ($glyphLen > 0) {
  3456. if (_RECALC_PROFILE) {
  3457. $xMin = $this->unpack_short(substr($data, 2, 2));
  3458. $yMin = $this->unpack_short(substr($data, 4, 2));
  3459. $xMax = $this->unpack_short(substr($data, 6, 2));
  3460. $yMax = $this->unpack_short(substr($data, 8, 2));
  3461. $xMinT = min($xMinT, $xMin);
  3462. $yMinT = min($yMinT, $yMin);
  3463. $xMaxT = max($xMaxT, $xMax);
  3464. $yMaxT = max($yMaxT, $yMax);
  3465. $aw = $this->unpack_short(substr($hm, 0, 2));
  3466. $lsb = $this->unpack_short(substr($hm, 2, 2));
  3467. $advanceWidthMax = max($advanceWidthMax, $aw);
  3468. $minLeftSideBearing = min($minLeftSideBearing, $lsb);
  3469. $minRightSideBearing = min($minRightSideBearing, ($aw - $lsb - ($xMax - $xMin)));
  3470. $xMaxExtent = max($xMaxExtent, ($lsb + ($xMax - $xMin)));
  3471. }
  3472. $up = unpack("n", substr($data, 0, 2));
  3473. }
  3474. if ($glyphLen > 2 && ($up[1] & (1 << 15))) { // If number of contours <= -1 i.e. composiste glyph
  3475. $pos_in_glyph = 10;
  3476. $flags = GF_MORE;
  3477. $nComponentElements = 0;
  3478. while ($flags & GF_MORE) {
  3479. $nComponentElements += 1; // number of glyphs referenced at top level
  3480. $up = unpack("n", substr($data, $pos_in_glyph, 2));
  3481. $flags = $up[1];
  3482. $up = unpack("n", substr($data, $pos_in_glyph + 2, 2));
  3483. $glyphIdx = $up[1];
  3484. $this->glyphdata[$originalGlyphIdx]['compGlyphs'][] = $glyphIdx;
  3485. $data = $this->_set_ushort($data, $pos_in_glyph + 2, $glyphSet[$glyphIdx]);
  3486. $pos_in_glyph += 4;
  3487. if ($flags & GF_WORDS) {
  3488. $pos_in_glyph += 4;
  3489. } else {
  3490. $pos_in_glyph += 2;
  3491. }
  3492. if ($flags & GF_SCALE) {
  3493. $pos_in_glyph += 2;
  3494. } else if ($flags & GF_XYSCALE) {
  3495. $pos_in_glyph += 4;
  3496. } else if ($flags & GF_TWOBYTWO) {
  3497. $pos_in_glyph += 8;
  3498. }
  3499. }
  3500. $maxComponentElements = max($maxComponentElements, $nComponentElements);
  3501. }
  3502. // Simple Glyph
  3503. else if (_RECALC_PROFILE && $glyphLen > 2 && $up[1] < (1 << 15) && $up[1] > 0) { // Number of contours > 0 simple glyph
  3504. $nContours = $up[1];
  3505. $this->glyphdata[$originalGlyphIdx]['nContours'] = $nContours;
  3506. $maxContours = max($maxContours, $nContours);
  3507. // Count number of points in simple glyph
  3508. $pos_in_glyph = 10 + ($nContours * 2) - 2; // Last endContourPoint
  3509. $up = unpack("n", substr($data, $pos_in_glyph, 2));
  3510. $points = $up[1] + 1;
  3511. $this->glyphdata[$originalGlyphIdx]['nPoints'] = $points;
  3512. $maxPoints = max($maxPoints, $points);
  3513. }
  3514. $glyf .= $data;
  3515. $pos += $glyphLen;
  3516. if ($pos % 4 != 0) {
  3517. $padding = 4 - ($pos % 4);
  3518. $glyf .= str_repeat("\0", $padding);
  3519. $pos += $padding;
  3520. }
  3521. }
  3522. if (_RECALC_PROFILE) {
  3523. foreach ($this->glyphdata AS $originalGlyphIdx => $val) {
  3524. $maxdepth = $depth = -1;
  3525. $points = 0;
  3526. $contours = 0;
  3527. $this->getGlyphData($originalGlyphIdx, $maxdepth, $depth, $points, $contours);
  3528. $maxComponentDepth = max($maxComponentDepth, $maxdepth);
  3529. $maxComponentPoints = max($maxComponentPoints, $points);
  3530. $maxComponentContours = max($maxComponentContours, $contours);
  3531. }
  3532. }
  3533. $offsets[] = $pos;
  3534. $this->add('glyf', $glyf);
  3535. ///////////////////////////////////
  3536. // hmtx - Horizontal Metrics
  3537. ///////////////////////////////////
  3538. $this->add('hmtx', $hmtxstr);
  3539. ///////////////////////////////////
  3540. // loca - Index to location
  3541. ///////////////////////////////////
  3542. $locastr = '';
  3543. if ((($pos + 1) >> 1) > 0xFFFF) {
  3544. $indexToLocFormat = 1; // long format
  3545. foreach ($offsets AS $offset) {
  3546. $locastr .= pack("N", $offset);
  3547. }
  3548. } else {
  3549. $indexToLocFormat = 0; // short format
  3550. foreach ($offsets AS $offset) {
  3551. $locastr .= pack("n", ($offset / 2));
  3552. }
  3553. }
  3554. $this->add('loca', $locastr);
  3555. ///////////////////////////////////
  3556. // head - Font header
  3557. ///////////////////////////////////
  3558. $head = $this->get_table('head');
  3559. $head = $this->_set_ushort($head, 50, $indexToLocFormat);
  3560. if (_RECALC_PROFILE) {
  3561. $head = $this->_set_short($head, 36, $xMinT); // for all glyph bounding boxes
  3562. $head = $this->_set_short($head, 38, $yMinT); // for all glyph bounding boxes
  3563. $head = $this->_set_short($head, 40, $xMaxT); // for all glyph bounding boxes
  3564. $head = $this->_set_short($head, 42, $yMaxT); // for all glyph bounding boxes
  3565. $head[17] = chr($head[17] & ~(1 << 4)); // Unset Bit 4 (as hdmx/LTSH tables not included)
  3566. }
  3567. $this->add('head', $head);
  3568. ///////////////////////////////////
  3569. // hhea - Horizontal Header
  3570. ///////////////////////////////////
  3571. $hhea = $this->get_table('hhea');
  3572. $hhea = $this->_set_ushort($hhea, 34, $numberOfHMetrics);
  3573. if (_RECALC_PROFILE) {
  3574. $hhea = $this->_set_ushort($hhea, 10, $advanceWidthMax);
  3575. $hhea = $this->_set_short($hhea, 12, $minLeftSideBearing);
  3576. $hhea = $this->_set_short($hhea, 14, $minRightSideBearing);
  3577. $hhea = $this->_set_short($hhea, 16, $xMaxExtent);
  3578. }
  3579. $this->add('hhea', $hhea);
  3580. ///////////////////////////////////
  3581. // maxp - Maximum Profile
  3582. ///////////////////////////////////
  3583. $maxp = $this->get_table('maxp');
  3584. $maxp = $this->_set_ushort($maxp, 4, $numGlyphs);
  3585. if (_RECALC_PROFILE) {
  3586. $maxp = $this->_set_ushort($maxp, 6, $maxPoints); // points in non-compound glyph
  3587. $maxp = $this->_set_ushort($maxp, 8, $maxContours); // contours in non-compound glyph
  3588. $maxp = $this->_set_ushort($maxp, 10, $maxComponentPoints); // points in compound glyph
  3589. $maxp = $this->_set_ushort($maxp, 12, $maxComponentContours); // contours in compound glyph
  3590. $maxp = $this->_set_ushort($maxp, 28, $maxComponentElements); // number of glyphs referenced at top level
  3591. $maxp = $this->_set_ushort($maxp, 30, $maxComponentDepth); // levels of recursion, set to 0 if font has only simple glyphs
  3592. }
  3593. $this->add('maxp', $maxp);
  3594. ///////////////////////////////////
  3595. // OS/2 - OS/2
  3596. ///////////////////////////////////
  3597. if (isset($this->tables['OS/2'])) {
  3598. $os2_offset = $this->seek_table("OS/2");
  3599. if (_RECALC_PROFILE) {
  3600. $fsSelection = $this->get_ushort($os2_offset + 62);
  3601. $fsSelection = ($fsSelection & ~(1 << 6)); // 2-byte bit field containing information concerning the nature of the font patterns
  3602. // bit#0 = Italic; bit#5=Bold
  3603. // Match name table's font subfamily string
  3604. // Clear bit#6 used for 'Regular' and optional
  3605. }
  3606. // NB Currently this method never subsets characters above BMP
  3607. // Could set nonBMP bit according to $this->maxUni
  3608. $nonBMP = $this->get_ushort($os2_offset + 46);
  3609. $nonBMP = ($nonBMP & ~(1 << 9)); // Unset Bit 57 (indicates non-BMP) - for interactive forms
  3610. $os2 = $this->get_table('OS/2');
  3611. if (_RECALC_PROFILE) {
  3612. $os2 = $this->_set_ushort($os2, 62, $fsSelection);
  3613. $os2 = $this->_set_ushort($os2, 66, $fsLastCharIndex);
  3614. $os2 = $this->_set_ushort($os2, 42, 0x0000); // ulCharRange (ulUnicodeRange) bits 24-31 | 16-23
  3615. $os2 = $this->_set_ushort($os2, 44, 0x0000); // ulCharRange (Unicode ranges) bits 8-15 | 0-7
  3616. $os2 = $this->_set_ushort($os2, 46, $nonBMP); // ulCharRange (Unicode ranges) bits 56-63 | 48-55
  3617. $os2 = $this->_set_ushort($os2, 48, 0x0000); // ulCharRange (Unicode ranges) bits 40-47 | 32-39
  3618. $os2 = $this->_set_ushort($os2, 50, 0x0000); // ulCharRange (Unicode ranges) bits 88-95 | 80-87
  3619. $os2 = $this->_set_ushort($os2, 52, 0x0000); // ulCharRange (Unicode ranges) bits 72-79 | 64-71
  3620. $os2 = $this->_set_ushort($os2, 54, 0x0000); // ulCharRange (Unicode ranges) bits 120-127 | 112-119
  3621. $os2 = $this->_set_ushort($os2, 56, 0x0000); // ulCharRange (Unicode ranges) bits 104-111 | 96-103
  3622. }
  3623. $os2 = $this->_set_ushort($os2, 46, $nonBMP); // Unset Bit 57 (indicates non-BMP) - for interactive forms
  3624. $this->add('OS/2', $os2);
  3625. }
  3626. fclose($this->fh);
  3627. // Put the TTF file together
  3628. $stm = '';
  3629. $this->endTTFile($stm);
  3630. //file_put_contents('testfont.ttf', $stm); exit;
  3631. return $stm;
  3632. }
  3633. //================================================================================
  3634. // Also does SMP
  3635. function makeSubsetSIP($file, &$subset, $TTCfontID = 0, $debug = false, $useOTL = 0)
  3636. { // mPDF 5.7.1
  3637. $this->fh = fopen($file, 'rb');
  3638. if (!$this->fh) {
  3639. throw new MpdfException('Can\'t open file ' . $file);
  3640. }
  3641. $this->filename = $file;
  3642. $this->_pos = 0;
  3643. $this->useOTL = $useOTL; // mPDF 5.7.1
  3644. $this->charWidths = '';
  3645. $this->glyphPos = array();
  3646. $this->charToGlyph = array();
  3647. $this->tables = array();
  3648. $this->otables = array();
  3649. $this->ascent = 0;
  3650. $this->descent = 0;
  3651. $this->strikeoutSize = 0;
  3652. $this->strikeoutPosition = 0;
  3653. $this->numTTCFonts = 0;
  3654. $this->TTCFonts = array();
  3655. $this->skip(4);
  3656. if ($TTCfontID > 0) {
  3657. $this->version = $version = $this->read_ulong(); // TTC Header version now
  3658. if (!in_array($version, array(0x00010000, 0x00020000))) {
  3659. throw new MpdfException("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
  3660. }
  3661. $this->numTTCFonts = $this->read_ulong();
  3662. for ($i = 1; $i <= $this->numTTCFonts; $i++) {
  3663. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  3664. }
  3665. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  3666. $this->version = $version = $this->read_ulong(); // TTFont version again now
  3667. }
  3668. $this->readTableDirectory($debug);
  3669. ///////////////////////////////////
  3670. // head - Font header table
  3671. ///////////////////////////////////
  3672. $this->seek_table("head");
  3673. $this->skip(50);
  3674. $indexToLocFormat = $this->read_ushort();
  3675. $glyphDataFormat = $this->read_ushort();
  3676. ///////////////////////////////////
  3677. // hhea - Horizontal header table
  3678. ///////////////////////////////////
  3679. $this->seek_table("hhea");
  3680. $this->skip(32);
  3681. $metricDataFormat = $this->read_ushort();
  3682. $orignHmetrics = $numberOfHMetrics = $this->read_ushort();
  3683. ///////////////////////////////////
  3684. // maxp - Maximum profile table
  3685. ///////////////////////////////////
  3686. $this->seek_table("maxp");
  3687. $this->skip(4);
  3688. $numGlyphs = $this->read_ushort();
  3689. ///////////////////////////////////
  3690. // cmap - Character to glyph index mapping table
  3691. ///////////////////////////////////
  3692. $cmap_offset = $this->seek_table("cmap");
  3693. $this->skip(2);
  3694. $cmapTableCount = $this->read_ushort();
  3695. $unicode_cmap_offset = 0;
  3696. for ($i = 0; $i < $cmapTableCount; $i++) {
  3697. $platformID = $this->read_ushort();
  3698. $encodingID = $this->read_ushort();
  3699. $offset = $this->read_ulong();
  3700. $save_pos = $this->_pos;
  3701. if (($platformID == 3 && $encodingID == 10) || $platformID == 0) { // Microsoft, Unicode Format 12 table HKCS
  3702. $format = $this->get_ushort($cmap_offset + $offset);
  3703. if ($format == 12) {
  3704. $unicode_cmap_offset = $cmap_offset + $offset;
  3705. break;
  3706. }
  3707. }
  3708. // mPDF 5.7.1
  3709. if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
  3710. $format = $this->get_ushort($cmap_offset + $offset);
  3711. if ($format == 4) {
  3712. $unicode_cmap_offset = $cmap_offset + $offset;
  3713. }
  3714. }
  3715. $this->seek($save_pos);
  3716. }
  3717. if (!$unicode_cmap_offset) {
  3718. throw new MpdfException('Font does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
  3719. }
  3720. // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
  3721. if ($format == 12) {
  3722. $this->maxUniChar = 0;
  3723. $this->seek($unicode_cmap_offset + 4);
  3724. $length = $this->read_ulong();
  3725. $limit = $unicode_cmap_offset + $length;
  3726. $this->skip(4);
  3727. $nGroups = $this->read_ulong();
  3728. $glyphToChar = array();
  3729. $charToGlyph = array();
  3730. for ($i = 0; $i < $nGroups; $i++) {
  3731. $startCharCode = $this->read_ulong();
  3732. $endCharCode = $this->read_ulong();
  3733. $startGlyphCode = $this->read_ulong();
  3734. $offset = 0;
  3735. for ($unichar = $startCharCode; $unichar <= $endCharCode; $unichar++) {
  3736. $glyph = $startGlyphCode + $offset;
  3737. $offset++;
  3738. // ZZZ98
  3739. if ($unichar < 0x30000) {
  3740. $charToGlyph[$unichar] = $glyph;
  3741. $this->maxUniChar = max($unichar, $this->maxUniChar);
  3742. $glyphToChar[$glyph][] = $unichar;
  3743. }
  3744. }
  3745. }
  3746. }
  3747. // mPDF 5.7.1
  3748. else {
  3749. $glyphToChar = array();
  3750. $charToGlyph = array();
  3751. $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph);
  3752. }
  3753. ///////////////////////////////////
  3754. // mPDF 5.7.1
  3755. // Map Unmapped glyphs - from $numGlyphs
  3756. if ($useOTL) {
  3757. $bctr = 0xE000;
  3758. for ($gid = 1; $gid < $numGlyphs; $gid++) {
  3759. if (!isset($glyphToChar[$gid])) {
  3760. while (isset($charToGlyph[$bctr])) {
  3761. $bctr++;
  3762. } // Avoid overwriting a glyph already mapped in PUA
  3763. // ZZZ98
  3764. if ($bctr > 0xF8FF && $bctr < 0x2CEB0) {
  3765. $bctr = 0x2CEB0;
  3766. while (isset($charToGlyph[$bctr])) {
  3767. $bctr++;
  3768. }
  3769. }
  3770. $glyphToChar[$gid][] = $bctr;
  3771. $charToGlyph[$bctr] = $gid;
  3772. $this->maxUniChar = max($bctr, $this->maxUniChar);
  3773. $bctr++;
  3774. }
  3775. }
  3776. }
  3777. ///////////////////////////////////
  3778. ///////////////////////////////////
  3779. // hmtx - Horizontal metrics table
  3780. ///////////////////////////////////
  3781. $scale = 1; // not used here
  3782. $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
  3783. ///////////////////////////////////
  3784. // loca - Index to location
  3785. ///////////////////////////////////
  3786. $this->getLOCA($indexToLocFormat, $numGlyphs);
  3787. ///////////////////////////////////////////////////////////////////
  3788. $glyphMap = array(0 => 0);
  3789. $glyphSet = array(0 => 0);
  3790. $codeToGlyph = array();
  3791. // Set a substitute if ASCII characters do not have glyphs
  3792. if (isset($charToGlyph[0x3F])) {
  3793. $subs = $charToGlyph[0x3F];
  3794. } // Question mark
  3795. else {
  3796. $subs = $charToGlyph[32];
  3797. }
  3798. foreach ($subset AS $code) {
  3799. if (isset($charToGlyph[$code]))
  3800. $originalGlyphIdx = $charToGlyph[$code];
  3801. else if ($code < 128) {
  3802. $originalGlyphIdx = $subs;
  3803. } else {
  3804. $originalGlyphIdx = 0;
  3805. }
  3806. if (!isset($glyphSet[$originalGlyphIdx])) {
  3807. $glyphSet[$originalGlyphIdx] = count($glyphMap);
  3808. $glyphMap[] = $originalGlyphIdx;
  3809. }
  3810. $codeToGlyph[$code] = $glyphSet[$originalGlyphIdx];
  3811. }
  3812. list($start, $dummy) = $this->get_table_pos('glyf');
  3813. $n = 0;
  3814. while ($n < count($glyphMap)) {
  3815. $originalGlyphIdx = $glyphMap[$n];
  3816. $glyphPos = $this->glyphPos[$originalGlyphIdx];
  3817. $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
  3818. $n += 1;
  3819. if (!$glyphLen)
  3820. continue;
  3821. $this->seek($start + $glyphPos);
  3822. $numberOfContours = $this->read_short();
  3823. if ($numberOfContours < 0) {
  3824. $this->skip(8);
  3825. $flags = GF_MORE;
  3826. while ($flags & GF_MORE) {
  3827. $flags = $this->read_ushort();
  3828. $glyphIdx = $this->read_ushort();
  3829. if (!isset($glyphSet[$glyphIdx])) {
  3830. $glyphSet[$glyphIdx] = count($glyphMap);
  3831. $glyphMap[] = $glyphIdx;
  3832. }
  3833. if ($flags & GF_WORDS)
  3834. $this->skip(4);
  3835. else
  3836. $this->skip(2);
  3837. if ($flags & GF_SCALE)
  3838. $this->skip(2);
  3839. else if ($flags & GF_XYSCALE)
  3840. $this->skip(4);
  3841. else if ($flags & GF_TWOBYTWO)
  3842. $this->skip(8);
  3843. }
  3844. }
  3845. }
  3846. $numGlyphs = $n = count($glyphMap);
  3847. $numberOfHMetrics = $n;
  3848. ///////////////////////////////////
  3849. // name
  3850. ///////////////////////////////////
  3851. // MS spec says that "Platform and encoding ID's in the name table should be consistent with those in the cmap table.
  3852. // If they are not, the font will not load in Windows"
  3853. // Doesn't seem to be a problem?
  3854. ///////////////////////////////////
  3855. // Needs to have a name entry in 3,0 (e.g. symbol) - original font will be 3,1 (i.e. Unicode)
  3856. $name = $this->get_table('name');
  3857. $name_offset = $this->seek_table("name");
  3858. $format = $this->read_ushort();
  3859. $numRecords = $this->read_ushort();
  3860. $string_data_offset = $name_offset + $this->read_ushort();
  3861. for ($i = 0; $i < $numRecords; $i++) {
  3862. $platformId = $this->read_ushort();
  3863. $encodingId = $this->read_ushort();
  3864. if ($platformId == 3 && $encodingId == 1) {
  3865. $pos = 6 + ($i * 12) + 2;
  3866. $name = $this->_set_ushort($name, $pos, 0x00); // Change encoding to 3,0 rather than 3,1
  3867. }
  3868. $this->skip(8);
  3869. }
  3870. $this->add('name', $name);
  3871. ///////////////////////////////////
  3872. // OS/2
  3873. ///////////////////////////////////
  3874. if (isset($this->tables['OS/2'])) {
  3875. $os2 = $this->get_table('OS/2');
  3876. $os2 = $this->_set_ushort($os2, 42, 0x00); // ulCharRange (Unicode ranges)
  3877. $os2 = $this->_set_ushort($os2, 44, 0x00); // ulCharRange (Unicode ranges)
  3878. $os2 = $this->_set_ushort($os2, 46, 0x00); // ulCharRange (Unicode ranges)
  3879. $os2 = $this->_set_ushort($os2, 48, 0x00); // ulCharRange (Unicode ranges)
  3880. $os2 = $this->_set_ushort($os2, 50, 0x00); // ulCharRange (Unicode ranges)
  3881. $os2 = $this->_set_ushort($os2, 52, 0x00); // ulCharRange (Unicode ranges)
  3882. $os2 = $this->_set_ushort($os2, 54, 0x00); // ulCharRange (Unicode ranges)
  3883. $os2 = $this->_set_ushort($os2, 56, 0x00); // ulCharRange (Unicode ranges)
  3884. // Set Symbol character only in ulCodePageRange
  3885. $os2 = $this->_set_ushort($os2, 78, 0x8000); // ulCodePageRange = Bit #31 Symbol **** 78 = Bit 16-31
  3886. $os2 = $this->_set_ushort($os2, 80, 0x0000); // ulCodePageRange = Bit #31 Symbol **** 80 = Bit 0-15
  3887. $os2 = $this->_set_ushort($os2, 82, 0x0000); // ulCodePageRange = Bit #32- Symbol **** 82 = Bits 48-63
  3888. $os2 = $this->_set_ushort($os2, 84, 0x0000); // ulCodePageRange = Bit #32- Symbol **** 84 = Bits 32-47
  3889. $os2 = $this->_set_ushort($os2, 64, 0x01); // FirstCharIndex
  3890. $os2 = $this->_set_ushort($os2, 66, count($subset)); // LastCharIndex
  3891. // Set PANOSE first bit to 5 for Symbol
  3892. $os2 = $this->splice($os2, 32, chr(5) . chr(0) . chr(1) . chr(0) . chr(1) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0));
  3893. $this->add('OS/2', $os2);
  3894. }
  3895. ///////////////////////////////////
  3896. //tables copied from the original
  3897. ///////////////////////////////////
  3898. $tags = array('cvt ', 'fpgm', 'prep', 'gasp');
  3899. foreach ($tags AS $tag) { // 1.02
  3900. if (isset($this->tables[$tag])) {
  3901. $this->add($tag, $this->get_table($tag));
  3902. }
  3903. }
  3904. ///////////////////////////////////
  3905. // post - PostScript
  3906. ///////////////////////////////////
  3907. if (isset($this->tables['post'])) {
  3908. $opost = $this->get_table('post');
  3909. $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";
  3910. }
  3911. $this->add('post', $post);
  3912. ///////////////////////////////////
  3913. // hhea - Horizontal Header
  3914. ///////////////////////////////////
  3915. $hhea = $this->get_table('hhea');
  3916. $hhea = $this->_set_ushort($hhea, 34, $numberOfHMetrics);
  3917. $this->add('hhea', $hhea);
  3918. ///////////////////////////////////
  3919. // maxp - Maximum Profile
  3920. ///////////////////////////////////
  3921. $maxp = $this->get_table('maxp');
  3922. $maxp = $this->_set_ushort($maxp, 4, $numGlyphs);
  3923. $this->add('maxp', $maxp);
  3924. ///////////////////////////////////
  3925. // CMap table Formats [1,0,]6 and [3,0,]4
  3926. ///////////////////////////////////
  3927. ///////////////////////////////////
  3928. // Sort CID2GID map into segments of contiguous codes
  3929. ///////////////////////////////////
  3930. $rangeid = 0;
  3931. $range = array();
  3932. $prevcid = -2;
  3933. $prevglidx = -1;
  3934. // for each character
  3935. foreach ($subset as $cid => $code) {
  3936. $glidx = $codeToGlyph[$code];
  3937. if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) {
  3938. $range[$rangeid][] = $glidx;
  3939. } else {
  3940. // new range
  3941. $rangeid = $cid;
  3942. $range[$rangeid] = array();
  3943. $range[$rangeid][] = $glidx;
  3944. }
  3945. $prevcid = $cid;
  3946. $prevglidx = $glidx;
  3947. }
  3948. // cmap - Character to glyph mapping
  3949. $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF
  3950. $searchRange = 1;
  3951. $entrySelector = 0;
  3952. while ($searchRange * 2 <= $segCount) {
  3953. $searchRange = $searchRange * 2;
  3954. $entrySelector = $entrySelector + 1;
  3955. }
  3956. $searchRange = $searchRange * 2;
  3957. $rangeShift = $segCount * 2 - $searchRange;
  3958. $length = 16 + (8 * $segCount ) + ($numGlyphs + 1);
  3959. $cmap = array(
  3960. 4, $length, 0, // Format 4 Mapping subtable: format, length, language
  3961. $segCount * 2,
  3962. $searchRange,
  3963. $entrySelector,
  3964. $rangeShift);
  3965. // endCode(s)
  3966. foreach ($range AS $start => $subrange) {
  3967. $endCode = $start + (count($subrange) - 1);
  3968. $cmap[] = $endCode; // endCode(s)
  3969. }
  3970. $cmap[] = 0xFFFF; // endCode of last Segment
  3971. $cmap[] = 0; // reservedPad
  3972. // startCode(s)
  3973. foreach ($range AS $start => $subrange) {
  3974. $cmap[] = $start; // startCode(s)
  3975. }
  3976. $cmap[] = 0xFFFF; // startCode of last Segment
  3977. // idDelta(s)
  3978. foreach ($range AS $start => $subrange) {
  3979. $idDelta = -($start - $subrange[0]);
  3980. $n += count($subrange);
  3981. $cmap[] = $idDelta; // idDelta(s)
  3982. }
  3983. $cmap[] = 1; // idDelta of last Segment
  3984. // idRangeOffset(s)
  3985. foreach ($range AS $subrange) {
  3986. $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
  3987. }
  3988. $cmap[] = 0; // idRangeOffset of last Segment
  3989. foreach ($range AS $subrange) {
  3990. foreach ($subrange AS $glidx) {
  3991. $cmap[] = $glidx;
  3992. }
  3993. }
  3994. $cmap[] = 0; // Mapping for last character
  3995. $cmapstr4 = '';
  3996. foreach ($cmap AS $cm) {
  3997. $cmapstr4 .= pack("n", $cm);
  3998. }
  3999. ///////////////////////////////////
  4000. // cmap - Character to glyph mapping
  4001. ///////////////////////////////////
  4002. $entryCount = count($subset);
  4003. $length = 10 + $entryCount * 2;
  4004. $off = 20 + $length;
  4005. $hoff = $off >> 16;
  4006. $loff = $off & 0xFFFF;
  4007. $cmap = array(0, 2, // Index : version, number of subtables
  4008. 1, 0, // Subtable : platform, encoding
  4009. 0, 20, // offset (hi,lo)
  4010. 3, 0, // Subtable : platform, encoding // See note above for 'name'
  4011. $hoff, $loff, // offset (hi,lo)
  4012. 6, $length, // Format 6 Mapping table: format, length
  4013. 0, 1, // language, First char code
  4014. $entryCount
  4015. );
  4016. $cmapstr = '';
  4017. foreach ($subset AS $code) {
  4018. $cmap[] = $codeToGlyph[$code];
  4019. }
  4020. foreach ($cmap AS $cm) {
  4021. $cmapstr .= pack("n", $cm);
  4022. }
  4023. $cmapstr .= $cmapstr4;
  4024. $this->add('cmap', $cmapstr);
  4025. ///////////////////////////////////
  4026. // hmtx - Horizontal Metrics
  4027. ///////////////////////////////////
  4028. $hmtxstr = '';
  4029. for ($n = 0; $n < $numGlyphs; $n++) {
  4030. $originalGlyphIdx = $glyphMap[$n];
  4031. $hm = $this->getHMetric($orignHmetrics, $originalGlyphIdx);
  4032. $hmtxstr .= $hm;
  4033. }
  4034. $this->add('hmtx', $hmtxstr);
  4035. ///////////////////////////////////
  4036. // glyf - Glyph data
  4037. ///////////////////////////////////
  4038. list($glyfOffset, $glyfLength) = $this->get_table_pos('glyf');
  4039. if ($glyfLength < $this->maxStrLenRead) {
  4040. $glyphData = $this->get_table('glyf');
  4041. }
  4042. $offsets = array();
  4043. $glyf = '';
  4044. $pos = 0;
  4045. for ($n = 0; $n < $numGlyphs; $n++) {
  4046. $offsets[] = $pos;
  4047. $originalGlyphIdx = $glyphMap[$n];
  4048. $glyphPos = $this->glyphPos[$originalGlyphIdx];
  4049. $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
  4050. if ($glyfLength < $this->maxStrLenRead) {
  4051. $data = substr($glyphData, $glyphPos, $glyphLen);
  4052. } else {
  4053. if ($glyphLen > 0)
  4054. $data = $this->get_chunk($glyfOffset + $glyphPos, $glyphLen);
  4055. else
  4056. $data = '';
  4057. }
  4058. if ($glyphLen > 0)
  4059. $up = unpack("n", substr($data, 0, 2));
  4060. if ($glyphLen > 2 && ($up[1] & (1 << 15))) {
  4061. $pos_in_glyph = 10;
  4062. $flags = GF_MORE;
  4063. while ($flags & GF_MORE) {
  4064. $up = unpack("n", substr($data, $pos_in_glyph, 2));
  4065. $flags = $up[1];
  4066. $up = unpack("n", substr($data, $pos_in_glyph + 2, 2));
  4067. $glyphIdx = $up[1];
  4068. $data = $this->_set_ushort($data, $pos_in_glyph + 2, $glyphSet[$glyphIdx]);
  4069. $pos_in_glyph += 4;
  4070. if ($flags & GF_WORDS) {
  4071. $pos_in_glyph += 4;
  4072. } else {
  4073. $pos_in_glyph += 2;
  4074. }
  4075. if ($flags & GF_SCALE) {
  4076. $pos_in_glyph += 2;
  4077. } else if ($flags & GF_XYSCALE) {
  4078. $pos_in_glyph += 4;
  4079. } else if ($flags & GF_TWOBYTWO) {
  4080. $pos_in_glyph += 8;
  4081. }
  4082. }
  4083. }
  4084. $glyf .= $data;
  4085. $pos += $glyphLen;
  4086. if ($pos % 4 != 0) {
  4087. $padding = 4 - ($pos % 4);
  4088. $glyf .= str_repeat("\0", $padding);
  4089. $pos += $padding;
  4090. }
  4091. }
  4092. $offsets[] = $pos;
  4093. $this->add('glyf', $glyf);
  4094. ///////////////////////////////////
  4095. // loca - Index to location
  4096. ///////////////////////////////////
  4097. $locastr = '';
  4098. if ((($pos + 1) >> 1) > 0xFFFF) {
  4099. $indexToLocFormat = 1; // long format
  4100. foreach ($offsets AS $offset) {
  4101. $locastr .= pack("N", $offset);
  4102. }
  4103. } else {
  4104. $indexToLocFormat = 0; // short format
  4105. foreach ($offsets AS $offset) {
  4106. $locastr .= pack("n", ($offset / 2));
  4107. }
  4108. }
  4109. $this->add('loca', $locastr);
  4110. ///////////////////////////////////
  4111. // head - Font header
  4112. ///////////////////////////////////
  4113. $head = $this->get_table('head');
  4114. $head = $this->_set_ushort($head, 50, $indexToLocFormat);
  4115. $this->add('head', $head);
  4116. fclose($this->fh);
  4117. // Put the TTF file together
  4118. $stm = '';
  4119. $this->endTTFile($stm);
  4120. //file_put_contents('testfont.ttf', $stm); exit;
  4121. return $stm;
  4122. }
  4123. //////////////////////////////////////////////////////////////////////////////////
  4124. // Recursively get composite glyph data
  4125. function getGlyphData($originalGlyphIdx, &$maxdepth, &$depth, &$points, &$contours)
  4126. {
  4127. $depth++;
  4128. $maxdepth = max($maxdepth, $depth);
  4129. if (count($this->glyphdata[$originalGlyphIdx]['compGlyphs'])) {
  4130. foreach ($this->glyphdata[$originalGlyphIdx]['compGlyphs'] AS $glyphIdx) {
  4131. $this->getGlyphData($glyphIdx, $maxdepth, $depth, $points, $contours);
  4132. }
  4133. } else if (($this->glyphdata[$originalGlyphIdx]['nContours'] > 0) && $depth > 0) { // simple
  4134. $contours += $this->glyphdata[$originalGlyphIdx]['nContours'];
  4135. $points += $this->glyphdata[$originalGlyphIdx]['nPoints'];
  4136. }
  4137. $depth--;
  4138. }
  4139. //////////////////////////////////////////////////////////////////////////////////
  4140. // Recursively get composite glyphs
  4141. function getGlyphs($originalGlyphIdx, &$start, &$glyphSet, &$subsetglyphs)
  4142. {
  4143. $glyphPos = $this->glyphPos[$originalGlyphIdx];
  4144. $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
  4145. if (!$glyphLen) {
  4146. return;
  4147. }
  4148. $this->seek($start + $glyphPos);
  4149. $numberOfContours = $this->read_short();
  4150. if ($numberOfContours < 0) {
  4151. $this->skip(8);
  4152. $flags = GF_MORE;
  4153. while ($flags & GF_MORE) {
  4154. $flags = $this->read_ushort();
  4155. $glyphIdx = $this->read_ushort();
  4156. if (!isset($glyphSet[$glyphIdx])) {
  4157. $glyphSet[$glyphIdx] = count($subsetglyphs); // old glyphID to new glyphID
  4158. $subsetglyphs[$glyphIdx] = true;
  4159. }
  4160. $savepos = ftell($this->fh);
  4161. $this->getGlyphs($glyphIdx, $start, $glyphSet, $subsetglyphs);
  4162. $this->seek($savepos);
  4163. if ($flags & GF_WORDS)
  4164. $this->skip(4);
  4165. else
  4166. $this->skip(2);
  4167. if ($flags & GF_SCALE)
  4168. $this->skip(2);
  4169. else if ($flags & GF_XYSCALE)
  4170. $this->skip(4);
  4171. else if ($flags & GF_TWOBYTWO)
  4172. $this->skip(8);
  4173. }
  4174. }
  4175. }
  4176. //////////////////////////////////////////////////////////////////////////////////
  4177. function getHMTX($numberOfHMetrics, $numGlyphs, &$glyphToChar, $scale)
  4178. {
  4179. $start = $this->seek_table("hmtx");
  4180. $aw = 0;
  4181. $this->charWidths = str_pad('', 256 * 256 * 2, "\x00");
  4182. if ($this->maxUniChar > 65536) {
  4183. $this->charWidths .= str_pad('', 256 * 256 * 2, "\x00");
  4184. } // Plane 1 SMP
  4185. if ($this->maxUniChar > 131072) {
  4186. $this->charWidths .= str_pad('', 256 * 256 * 2, "\x00");
  4187. } // Plane 2 SMP
  4188. $nCharWidths = 0;
  4189. if (($numberOfHMetrics * 4) < $this->maxStrLenRead) {
  4190. $data = $this->get_chunk($start, ($numberOfHMetrics * 4));
  4191. $arr = unpack("n*", $data);
  4192. } else {
  4193. $this->seek($start);
  4194. }
  4195. for ($glyph = 0; $glyph < $numberOfHMetrics; $glyph++) {
  4196. if (($numberOfHMetrics * 4) < $this->maxStrLenRead) {
  4197. $aw = $arr[($glyph * 2) + 1];
  4198. } else {
  4199. $aw = $this->read_ushort();
  4200. $lsb = $this->read_ushort();
  4201. }
  4202. if (isset($glyphToChar[$glyph]) || $glyph == 0) {
  4203. if ($aw >= (1 << 15)) {
  4204. $aw = 0;
  4205. } // 1.03 Some (arabic) fonts have -ve values for width
  4206. // although should be unsigned value - comes out as e.g. 65108 (intended -50)
  4207. if ($glyph == 0) {
  4208. $this->defaultWidth = $scale * $aw;
  4209. continue;
  4210. }
  4211. foreach ($glyphToChar[$glyph] AS $char) {
  4212. if ($char != 0 && $char != 65535) {
  4213. $w = intval(round($scale * $aw));
  4214. if ($w == 0) {
  4215. $w = 65535;
  4216. }
  4217. if ($char < 196608) {
  4218. $this->charWidths[$char * 2] = chr($w >> 8);
  4219. $this->charWidths[$char * 2 + 1] = chr($w & 0xFF);
  4220. $nCharWidths++;
  4221. }
  4222. }
  4223. }
  4224. }
  4225. }
  4226. $data = $this->get_chunk(($start + $numberOfHMetrics * 4), ($numGlyphs * 2));
  4227. $arr = unpack("n*", $data);
  4228. $diff = $numGlyphs - $numberOfHMetrics;
  4229. $w = intval(round($scale * $aw));
  4230. if ($w == 0) {
  4231. $w = 65535;
  4232. }
  4233. for ($pos = 0; $pos < $diff; $pos++) {
  4234. $glyph = $pos + $numberOfHMetrics;
  4235. if (isset($glyphToChar[$glyph])) {
  4236. foreach ($glyphToChar[$glyph] AS $char) {
  4237. if ($char != 0 && $char != 65535) {
  4238. if ($char < 196608) {
  4239. $this->charWidths[$char * 2] = chr($w >> 8);
  4240. $this->charWidths[$char * 2 + 1] = chr($w & 0xFF);
  4241. $nCharWidths++;
  4242. }
  4243. }
  4244. }
  4245. }
  4246. }
  4247. // NB 65535 is a set width of 0
  4248. // First bytes define number of chars in font
  4249. $this->charWidths[0] = chr($nCharWidths >> 8);
  4250. $this->charWidths[1] = chr($nCharWidths & 0xFF);
  4251. }
  4252. function getHMetric($numberOfHMetrics, $gid)
  4253. {
  4254. $start = $this->seek_table("hmtx");
  4255. if ($gid < $numberOfHMetrics) {
  4256. $this->seek($start + ($gid * 4));
  4257. $hm = fread($this->fh, 4);
  4258. } else {
  4259. $this->seek($start + (($numberOfHMetrics - 1) * 4));
  4260. $hm = fread($this->fh, 2);
  4261. $this->seek($start + ($numberOfHMetrics * 2) + ($gid * 2));
  4262. $hm .= fread($this->fh, 2);
  4263. }
  4264. return $hm;
  4265. }
  4266. function getLOCA($indexToLocFormat, $numGlyphs)
  4267. {
  4268. $start = $this->seek_table('loca');
  4269. $this->glyphPos = array();
  4270. if ($indexToLocFormat == 0) {
  4271. $data = $this->get_chunk($start, ($numGlyphs * 2) + 2);
  4272. $arr = unpack("n*", $data);
  4273. for ($n = 0; $n <= $numGlyphs; $n++) {
  4274. $this->glyphPos[] = ($arr[$n + 1] * 2);
  4275. }
  4276. } else if ($indexToLocFormat == 1) {
  4277. $data = $this->get_chunk($start, ($numGlyphs * 4) + 4);
  4278. $arr = unpack("N*", $data);
  4279. for ($n = 0; $n <= $numGlyphs; $n++) {
  4280. $this->glyphPos[] = ($arr[$n + 1]);
  4281. }
  4282. } else {
  4283. throw new MpdfException('Unknown location table format ' . $indexToLocFormat);
  4284. }
  4285. }
  4286. // CMAP Format 4
  4287. function getCMAP4($unicode_cmap_offset, &$glyphToChar, &$charToGlyph)
  4288. {
  4289. $this->maxUniChar = 0;
  4290. $this->seek($unicode_cmap_offset + 2);
  4291. $length = $this->read_ushort();
  4292. $limit = $unicode_cmap_offset + $length;
  4293. $this->skip(2);
  4294. $segCount = $this->read_ushort() / 2;
  4295. $this->skip(6);
  4296. $endCount = array();
  4297. for ($i = 0; $i < $segCount; $i++) {
  4298. $endCount[] = $this->read_ushort();
  4299. }
  4300. $this->skip(2);
  4301. $startCount = array();
  4302. for ($i = 0; $i < $segCount; $i++) {
  4303. $startCount[] = $this->read_ushort();
  4304. }
  4305. $idDelta = array();
  4306. for ($i = 0; $i < $segCount; $i++) {
  4307. $idDelta[] = $this->read_short();
  4308. } // ???? was unsigned short
  4309. $idRangeOffset_start = $this->_pos;
  4310. $idRangeOffset = array();
  4311. for ($i = 0; $i < $segCount; $i++) {
  4312. $idRangeOffset[] = $this->read_ushort();
  4313. }
  4314. for ($n = 0; $n < $segCount; $n++) {
  4315. $endpoint = ($endCount[$n] + 1);
  4316. for ($unichar = $startCount[$n]; $unichar < $endpoint; $unichar++) {
  4317. if ($idRangeOffset[$n] == 0)
  4318. $glyph = ($unichar + $idDelta[$n]) & 0xFFFF;
  4319. else {
  4320. $offset = ($unichar - $startCount[$n]) * 2 + $idRangeOffset[$n];
  4321. $offset = $idRangeOffset_start + 2 * $n + $offset;
  4322. if ($offset >= $limit)
  4323. $glyph = 0;
  4324. else {
  4325. $glyph = $this->get_ushort($offset);
  4326. if ($glyph != 0)
  4327. $glyph = ($glyph + $idDelta[$n]) & 0xFFFF;
  4328. }
  4329. }
  4330. $charToGlyph[$unichar] = $glyph;
  4331. if ($unichar < 196608) {
  4332. $this->maxUniChar = max($unichar, $this->maxUniChar);
  4333. }
  4334. $glyphToChar[$glyph][] = $unichar;
  4335. }
  4336. }
  4337. }
  4338. // Put the TTF file together
  4339. function endTTFile(&$stm)
  4340. {
  4341. $stm = '';
  4342. $numTables = count($this->otables);
  4343. $searchRange = 1;
  4344. $entrySelector = 0;
  4345. while ($searchRange * 2 <= $numTables) {
  4346. $searchRange = $searchRange * 2;
  4347. $entrySelector = $entrySelector + 1;
  4348. }
  4349. $searchRange = $searchRange * 16;
  4350. $rangeShift = $numTables * 16 - $searchRange;
  4351. // Header
  4352. if (_TTF_MAC_HEADER) {
  4353. $stm .= (pack("Nnnnn", 0x74727565, $numTables, $searchRange, $entrySelector, $rangeShift)); // Mac
  4354. } else {
  4355. $stm .= (pack("Nnnnn", 0x00010000, $numTables, $searchRange, $entrySelector, $rangeShift)); // Windows
  4356. }
  4357. // Table directory
  4358. $tables = $this->otables;
  4359. ksort($tables);
  4360. $offset = 12 + $numTables * 16;
  4361. foreach ($tables AS $tag => $data) {
  4362. if ($tag == 'head') {
  4363. $head_start = $offset;
  4364. }
  4365. $stm .= $tag;
  4366. $checksum = $this->calcChecksum($data);
  4367. $stm .= pack("nn", $checksum[0], $checksum[1]);
  4368. $stm .= pack("NN", $offset, strlen($data));
  4369. $paddedLength = (strlen($data) + 3) & ~3;
  4370. $offset = $offset + $paddedLength;
  4371. }
  4372. // Table data
  4373. foreach ($tables AS $tag => $data) {
  4374. $data .= "\0\0\0";
  4375. $stm .= substr($data, 0, (strlen($data) & ~3));
  4376. }
  4377. $checksum = $this->calcChecksum($stm);
  4378. $checksum = $this->sub32(array(0xB1B0, 0xAFBA), $checksum);
  4379. $chk = pack("nn", $checksum[0], $checksum[1]);
  4380. $stm = $this->splice($stm, ($head_start + 8), $chk);
  4381. return $stm;
  4382. }
  4383. function repackageTTF($file, $TTCfontID = 0, $debug = false, $useOTL = false)
  4384. { // mPDF 5.7.1
  4385. // (Does not called for subsets)
  4386. $this->useOTL = $useOTL; // mPDF 5.7.1
  4387. $this->filename = $file;
  4388. $this->fh = fopen($file, 'rb');
  4389. if (!$this->fh) {
  4390. throw new MpdfException('Can\'t open file ' . $file);
  4391. }
  4392. $this->_pos = 0;
  4393. $this->charWidths = '';
  4394. $this->glyphPos = array();
  4395. $this->charToGlyph = array();
  4396. $this->tables = array();
  4397. $this->otables = array();
  4398. $this->ascent = 0;
  4399. $this->descent = 0;
  4400. $this->strikeoutSize = 0;
  4401. $this->strikeoutPosition = 0;
  4402. $this->numTTCFonts = 0;
  4403. $this->TTCFonts = array();
  4404. $this->skip(4);
  4405. $this->maxUni = 0;
  4406. if ($TTCfontID > 0) {
  4407. $this->version = $version = $this->read_ulong(); // TTC Header version now
  4408. if (!in_array($version, array(0x00010000, 0x00020000))) {
  4409. throw new MpdfException("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
  4410. }
  4411. $this->numTTCFonts = $this->read_ulong();
  4412. for ($i = 1; $i <= $this->numTTCFonts; $i++) {
  4413. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  4414. }
  4415. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  4416. $this->version = $version = $this->read_ulong(); // TTFont version again now
  4417. }
  4418. $this->readTableDirectory($debug);
  4419. $tags = array('OS/2', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'name', 'post', 'cvt ', 'fpgm', 'gasp', 'prep');
  4420. foreach ($tags AS $tag) {
  4421. if (isset($this->tables[$tag])) {
  4422. $this->add($tag, $this->get_table($tag));
  4423. }
  4424. }
  4425. // mPDF 5.7.1
  4426. if ($useOTL) {
  4427. ///////////////////////////////////
  4428. // maxp - Maximum profile table
  4429. ///////////////////////////////////
  4430. $this->seek_table("maxp");
  4431. $this->skip(4);
  4432. $numGlyphs = $this->read_ushort();
  4433. ///////////////////////////////////
  4434. // cmap - Character to glyph index mapping table
  4435. ///////////////////////////////////
  4436. $cmap_offset = $this->seek_table("cmap");
  4437. $this->skip(2);
  4438. $cmapTableCount = $this->read_ushort();
  4439. $unicode_cmap_offset = 0;
  4440. for ($i = 0; $i < $cmapTableCount; $i++) {
  4441. $platformID = $this->read_ushort();
  4442. $encodingID = $this->read_ushort();
  4443. $offset = $this->read_ulong();
  4444. $save_pos = $this->_pos;
  4445. if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
  4446. $format = $this->get_ushort($cmap_offset + $offset);
  4447. if ($format == 4) {
  4448. $unicode_cmap_offset = $cmap_offset + $offset;
  4449. break;
  4450. }
  4451. }
  4452. $this->seek($save_pos);
  4453. }
  4454. if (!$unicode_cmap_offset) {
  4455. throw new MpdfException('Font (' . $this->filename . ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
  4456. }
  4457. $glyphToChar = array();
  4458. $charToGlyph = array();
  4459. $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph);
  4460. ///////////////////////////////////
  4461. // Map Unmapped glyphs - from $numGlyphs
  4462. $bctr = 0xE000;
  4463. for ($gid = 1; $gid < $numGlyphs; $gid++) {
  4464. if (!isset($glyphToChar[$gid])) {
  4465. while (isset($charToGlyph[$bctr])) {
  4466. $bctr++;
  4467. } // Avoid overwriting a glyph already mapped in PUA (6,400)
  4468. if ($bctr > 0xF8FF) {
  4469. throw new MpdfException("Problem. Trying to repackage TF file; not enough space for unmapped glyphs");
  4470. }
  4471. $glyphToChar[$gid][] = $bctr;
  4472. $charToGlyph[$bctr] = $gid;
  4473. $bctr++;
  4474. }
  4475. }
  4476. ///////////////////////////////////
  4477. ///////////////////////////////////
  4478. // Sort CID2GID map into segments of contiguous codes
  4479. ///////////////////////////////////
  4480. unset($charToGlyph[65535]);
  4481. unset($charToGlyph[0]);
  4482. ksort($charToGlyph);
  4483. $rangeid = 0;
  4484. $range = array();
  4485. $prevcid = -2;
  4486. $prevglidx = -1;
  4487. // for each character
  4488. foreach ($charToGlyph as $cid => $glidx) {
  4489. if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) {
  4490. $range[$rangeid][] = $glidx;
  4491. } else {
  4492. // new range
  4493. $rangeid = $cid;
  4494. $range[$rangeid] = array();
  4495. $range[$rangeid][] = $glidx;
  4496. }
  4497. $prevcid = $cid;
  4498. $prevglidx = $glidx;
  4499. }
  4500. ///////////////////////////////////
  4501. // CMap table
  4502. ///////////////////////////////////
  4503. // cmap - Character to glyph mapping
  4504. $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF
  4505. $searchRange = 1;
  4506. $entrySelector = 0;
  4507. while ($searchRange * 2 <= $segCount) {
  4508. $searchRange = $searchRange * 2;
  4509. $entrySelector = $entrySelector + 1;
  4510. }
  4511. $searchRange = $searchRange * 2;
  4512. $rangeShift = $segCount * 2 - $searchRange;
  4513. $length = 16 + (8 * $segCount ) + ($numGlyphs + 1);
  4514. $cmap = array(0, 3, // Index : version, number of encoding subtables
  4515. 0, 0, // Encoding Subtable : platform (UNI=0), encoding 0
  4516. 0, 28, // Encoding Subtable : offset (hi,lo)
  4517. 0, 3, // Encoding Subtable : platform (UNI=0), encoding 3
  4518. 0, 28, // Encoding Subtable : offset (hi,lo)
  4519. 3, 1, // Encoding Subtable : platform (MS=3), encoding 1
  4520. 0, 28, // Encoding Subtable : offset (hi,lo)
  4521. 4, $length, 0, // Format 4 Mapping subtable: format, length, language
  4522. $segCount * 2,
  4523. $searchRange,
  4524. $entrySelector,
  4525. $rangeShift);
  4526. // endCode(s)
  4527. foreach ($range AS $start => $subrange) {
  4528. $endCode = $start + (count($subrange) - 1);
  4529. $cmap[] = $endCode; // endCode(s)
  4530. }
  4531. $cmap[] = 0xFFFF; // endCode of last Segment
  4532. $cmap[] = 0; // reservedPad
  4533. // startCode(s)
  4534. foreach ($range AS $start => $subrange) {
  4535. $cmap[] = $start; // startCode(s)
  4536. }
  4537. $cmap[] = 0xFFFF; // startCode of last Segment
  4538. // idDelta(s)
  4539. foreach ($range AS $start => $subrange) {
  4540. $idDelta = -($start - $subrange[0]);
  4541. //$n += count($subrange); // ?? Line not required
  4542. $cmap[] = $idDelta; // idDelta(s)
  4543. }
  4544. $cmap[] = 1; // idDelta of last Segment
  4545. // idRangeOffset(s)
  4546. foreach ($range AS $subrange) {
  4547. $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
  4548. }
  4549. $cmap[] = 0; // idRangeOffset of last Segment
  4550. foreach ($range AS $subrange) {
  4551. foreach ($subrange AS $glidx) {
  4552. $cmap[] = $glidx;
  4553. }
  4554. }
  4555. $cmap[] = 0; // Mapping for last character
  4556. $cmapstr = '';
  4557. foreach ($cmap AS $cm) {
  4558. $cmapstr .= pack("n", $cm);
  4559. }
  4560. $this->add('cmap', $cmapstr);
  4561. } else {
  4562. $this->add('cmap', $this->get_table('cmap'));
  4563. }
  4564. fclose($this->fh);
  4565. $stm = '';
  4566. $this->endTTFile($stm);
  4567. return $stm;
  4568. }
  4569. }