ttfontsuni.php 80 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373
  1. <?php
  2. /*******************************************************************************
  3. * TTFontFile class *
  4. * *
  5. * Version: 1.05 *
  6. * Date: 2011-07-21 *
  7. * Author: Ian Back <ianb@bpm1.com> *
  8. * License: LGPL *
  9. * Copyright (c) Ian Back, 2010 *
  10. * This class is based on The ReportLab Open Source PDF library *
  11. * written in Python - http://www.reportlab.com/software/opensource/ *
  12. * together with ideas from the OpenOffice source code and others. *
  13. * This header must be retained in any redistribution or *
  14. * modification of the file. *
  15. * *
  16. *******************************************************************************/
  17. // Define the value used in the "head" table of a created TTF file
  18. // 0x74727565 "true" for Mac
  19. // 0x00010000 for Windows
  20. // Either seems to work for a font embedded in a PDF file
  21. // when read by Adobe Reader on a Windows PC(!)
  22. if (!defined('_TTF_MAC_HEADER')) define("_TTF_MAC_HEADER", false);
  23. // Recalculate correct metadata/profiles when making subset fonts (not SIP/SMP)
  24. // e.g. xMin, xMax, maxNContours
  25. if (!defined('_RECALC_PROFILE')) define("_RECALC_PROFILE", false);
  26. // TrueType Font Glyph operators
  27. define("GF_WORDS",(1 << 0));
  28. define("GF_SCALE",(1 << 3));
  29. define("GF_MORE",(1 << 5));
  30. define("GF_XYSCALE",(1 << 6));
  31. define("GF_TWOBYTWO",(1 << 7));
  32. class TTFontFile {
  33. var $panose;
  34. var $maxUni;
  35. var $sFamilyClass;
  36. var $sFamilySubClass;
  37. var $sipset;
  38. var $smpset;
  39. var $_pos;
  40. var $numTables;
  41. var $searchRange;
  42. var $entrySelector;
  43. var $rangeShift;
  44. var $tables;
  45. var $otables;
  46. var $filename;
  47. var $fh;
  48. var $glyphPos;
  49. var $charToGlyph;
  50. var $ascent;
  51. var $descent;
  52. var $name;
  53. var $familyName;
  54. var $styleName;
  55. var $fullName;
  56. var $uniqueFontID;
  57. var $unitsPerEm;
  58. var $bbox;
  59. var $capHeight;
  60. var $stemV;
  61. var $italicAngle;
  62. var $flags;
  63. var $underlinePosition;
  64. var $underlineThickness;
  65. var $charWidths;
  66. var $defaultWidth;
  67. var $maxStrLenRead;
  68. var $numTTCFonts;
  69. var $TTCFonts;
  70. var $maxUniChar;
  71. var $kerninfo;
  72. function TTFontFile() {
  73. $this->maxStrLenRead = 200000; // Maximum size of glyf table to read in as string (otherwise reads each glyph from file)
  74. }
  75. function getMetrics($file, $TTCfontID=0, $debug=false, $BMPonly=false, $kerninfo=false) {
  76. $this->filename = $file;
  77. $this->fh = fopen($file,'rb') or die('Can\'t open file ' . $file);
  78. $this->_pos = 0;
  79. $this->charWidths = '';
  80. $this->glyphPos = array();
  81. $this->charToGlyph = array();
  82. $this->tables = array();
  83. $this->otables = array();
  84. $this->kerninfo = array();
  85. $this->ascent = 0;
  86. $this->descent = 0;
  87. $this->numTTCFonts = 0;
  88. $this->TTCFonts = array();
  89. $this->version = $version = $this->read_ulong();
  90. $this->panose = array();
  91. if ($version==0x4F54544F)
  92. die("Postscript outlines are not supported");
  93. if ($version==0x74746366 && !$TTCfontID)
  94. die("ERROR - You must define the TTCfontID for a TrueType Collection in config_fonts.php (". $file.")");
  95. if (!in_array($version, array(0x00010000,0x74727565)) && !$TTCfontID)
  96. die("Not a TrueType font: version=".$version);
  97. if ($TTCfontID > 0) {
  98. $this->version = $version = $this->read_ulong(); // TTC Header version now
  99. if (!in_array($version, array(0x00010000,0x00020000)))
  100. die("ERROR - Error parsing TrueType Collection: version=".$version." - " . $file);
  101. $this->numTTCFonts = $this->read_ulong();
  102. for ($i=1; $i<=$this->numTTCFonts; $i++) {
  103. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  104. }
  105. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  106. $this->version = $version = $this->read_ulong(); // TTFont version again now
  107. }
  108. $this->readTableDirectory($debug);
  109. $this->extractInfo($debug, $BMPonly, $kerninfo);
  110. fclose($this->fh);
  111. }
  112. function readTableDirectory($debug=false) {
  113. $this->numTables = $this->read_ushort();
  114. $this->searchRange = $this->read_ushort();
  115. $this->entrySelector = $this->read_ushort();
  116. $this->rangeShift = $this->read_ushort();
  117. $this->tables = array();
  118. for ($i=0;$i<$this->numTables;$i++) {
  119. $record = array();
  120. $record['tag'] = $this->read_tag();
  121. $record['checksum'] = array($this->read_ushort(),$this->read_ushort());
  122. $record['offset'] = $this->read_ulong();
  123. $record['length'] = $this->read_ulong();
  124. $this->tables[$record['tag']] = $record;
  125. }
  126. if ($debug) $this->checksumTables();
  127. }
  128. function checksumTables() {
  129. // Check the checksums for all tables
  130. foreach($this->tables AS $t) {
  131. if ($t['length'] > 0 && $t['length'] < $this->maxStrLenRead) { // 1.02
  132. $table = $this->get_chunk($t['offset'], $t['length']);
  133. $checksum = $this->calcChecksum($table);
  134. if ($t['tag'] == 'head') {
  135. $up = unpack('n*', substr($table,8,4));
  136. $adjustment[0] = $up[1];
  137. $adjustment[1] = $up[2];
  138. $checksum = $this->sub32($checksum, $adjustment);
  139. }
  140. $xchecksum = $t['checksum'];
  141. if ($xchecksum != $checksum)
  142. die(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])));
  143. }
  144. }
  145. }
  146. function sub32($x, $y) {
  147. $xlo = $x[1];
  148. $xhi = $x[0];
  149. $ylo = $y[1];
  150. $yhi = $y[0];
  151. if ($ylo > $xlo) { $xlo += 1 << 16; $yhi += 1; }
  152. $reslo = $xlo-$ylo;
  153. if ($yhi > $xhi) { $xhi += 1 << 16; }
  154. $reshi = $xhi-$yhi;
  155. $reshi = $reshi & 0xFFFF;
  156. return array($reshi, $reslo);
  157. }
  158. function calcChecksum($data) {
  159. if (strlen($data) % 4) { $data .= str_repeat("\0",(4-(strlen($data) % 4))); }
  160. $len = strlen($data);
  161. $hi=0x0000;
  162. $lo=0x0000;
  163. for($i=0;$i<$len;$i+=4) {
  164. $hi += (ord($data[$i])<<8) + ord($data[$i+1]);
  165. $lo += (ord($data[$i+2])<<8) + ord($data[$i+3]);
  166. $hi += ($lo >> 16) & 0xFFFF; // mPDF 5.3.07
  167. $lo = $lo & 0xFFFF;
  168. }
  169. return array($hi, $lo);
  170. }
  171. function get_table_pos($tag) {
  172. $offset = $this->tables[$tag]['offset'];
  173. $length = $this->tables[$tag]['length'];
  174. return array($offset, $length);
  175. }
  176. function seek($pos) {
  177. $this->_pos = $pos;
  178. fseek($this->fh,$this->_pos);
  179. }
  180. function skip($delta) {
  181. $this->_pos = $this->_pos + $delta;
  182. // fseek($this->fh,$this->_pos);
  183. fseek($this->fh,$delta,SEEK_CUR); // mPDF 5.3.19
  184. }
  185. function seek_table($tag, $offset_in_table = 0) {
  186. $tpos = $this->get_table_pos($tag);
  187. $this->_pos = $tpos[0] + $offset_in_table;
  188. fseek($this->fh, $this->_pos);
  189. return $this->_pos;
  190. }
  191. function read_tag() {
  192. $this->_pos += 4;
  193. return fread($this->fh,4);
  194. }
  195. function read_short() {
  196. $this->_pos += 2;
  197. $s = fread($this->fh,2);
  198. $a = (ord($s[0])<<8) + ord($s[1]);
  199. if ($a & (1 << 15) ) {
  200. $a = ($a - (1 << 16));
  201. }
  202. return $a;
  203. }
  204. function unpack_short($s) {
  205. $a = (ord($s[0])<<8) + ord($s[1]);
  206. if ($a & (1 << 15) ) {
  207. $a = ($a - (1 << 16));
  208. }
  209. return $a;
  210. }
  211. function read_ushort() {
  212. $this->_pos += 2;
  213. $s = fread($this->fh,2);
  214. return (ord($s[0])<<8) + ord($s[1]);
  215. }
  216. function read_ulong() {
  217. $this->_pos += 4;
  218. $s = fread($this->fh,4);
  219. // if large uInt32 as an integer, PHP converts it to -ve
  220. return (ord($s[0])*16777216) + (ord($s[1])<<16) + (ord($s[2])<<8) + ord($s[3]); // 16777216 = 1<<24
  221. }
  222. function get_ushort($pos) {
  223. fseek($this->fh,$pos);
  224. $s = fread($this->fh,2);
  225. return (ord($s[0])<<8) + ord($s[1]);
  226. }
  227. function get_ulong($pos) {
  228. fseek($this->fh,$pos);
  229. $s = fread($this->fh,4);
  230. // iF large uInt32 as an integer, PHP converts it to -ve
  231. return (ord($s[0])*16777216) + (ord($s[1])<<16) + (ord($s[2])<<8) + ord($s[3]); // 16777216 = 1<<24
  232. }
  233. function pack_short($val) {
  234. if ($val<0) {
  235. $val = abs($val);
  236. $val = ~$val;
  237. $val += 1;
  238. }
  239. return pack("n",$val);
  240. }
  241. function splice($stream, $offset, $value) {
  242. return substr($stream,0,$offset) . $value . substr($stream,$offset+strlen($value));
  243. }
  244. function _set_ushort($stream, $offset, $value) {
  245. $up = pack("n", $value);
  246. return $this->splice($stream, $offset, $up);
  247. }
  248. // mPDF 5.0
  249. function _set_short($stream, $offset, $val) {
  250. if ($val<0) {
  251. $val = abs($val);
  252. $val = ~$val;
  253. $val += 1;
  254. }
  255. $up = pack("n",$val);
  256. return $this->splice($stream, $offset, $up);
  257. }
  258. function get_chunk($pos, $length) {
  259. fseek($this->fh,$pos);
  260. if ($length <1) { return ''; }
  261. return (fread($this->fh,$length));
  262. }
  263. function get_table($tag) {
  264. list($pos, $length) = $this->get_table_pos($tag);
  265. if ($length == 0) { return ''; }
  266. fseek($this->fh,$pos);
  267. return (fread($this->fh,$length));
  268. }
  269. function add($tag, $data) {
  270. if ($tag == 'head') {
  271. $data = $this->splice($data, 8, "\0\0\0\0");
  272. }
  273. $this->otables[$tag] = $data;
  274. }
  275. /////////////////////////////////////////////////////////////////////////////////////////
  276. function getCTG($file, $TTCfontID=0, $debug=false) {
  277. $this->filename = $file;
  278. $this->fh = fopen($file,'rb') or die('Can\'t open file ' . $file);
  279. $this->_pos = 0;
  280. $this->charWidths = '';
  281. $this->glyphPos = array();
  282. $this->charToGlyph = array();
  283. $this->tables = array();
  284. $this->numTTCFonts = 0;
  285. $this->TTCFonts = array();
  286. $this->skip(4);
  287. if ($TTCfontID > 0) {
  288. $this->version = $version = $this->read_ulong(); // TTC Header version now
  289. if (!in_array($version, array(0x00010000,0x00020000)))
  290. die("ERROR - Error parsing TrueType Collection: version=".$version." - " . $file);
  291. $this->numTTCFonts = $this->read_ulong();
  292. for ($i=1; $i<=$this->numTTCFonts; $i++) {
  293. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  294. }
  295. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  296. $this->version = $version = $this->read_ulong(); // TTFont version again now
  297. }
  298. $this->readTableDirectory($debug);
  299. // cmap - Character to glyph index mapping table
  300. $cmap_offset = $this->seek_table("cmap");
  301. $this->skip(2);
  302. $cmapTableCount = $this->read_ushort();
  303. $unicode_cmap_offset = 0;
  304. for ($i=0;$i<$cmapTableCount;$i++) {
  305. $platformID = $this->read_ushort();
  306. $encodingID = $this->read_ushort();
  307. $offset = $this->read_ulong();
  308. $save_pos = $this->_pos;
  309. if ($platformID == 3 && $encodingID == 1) { // Microsoft, Unicode
  310. $format = $this->get_ushort($cmap_offset + $offset);
  311. if ($format == 4) {
  312. $unicode_cmap_offset = $cmap_offset + $offset;
  313. break;
  314. }
  315. }
  316. else if ($platformID == 0) { // Unicode -- assume all encodings are compatible
  317. $format = $this->get_ushort($cmap_offset + $offset);
  318. if ($format == 4) {
  319. $unicode_cmap_offset = $cmap_offset + $offset;
  320. break;
  321. }
  322. }
  323. $this->seek($save_pos );
  324. }
  325. $glyphToChar = array();
  326. $charToGlyph = array();
  327. $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph );
  328. fclose($this->fh);
  329. return ($charToGlyph);
  330. }
  331. /////////////////////////////////////////////////////////////////////////////////////////
  332. function getTTCFonts($file) {
  333. $this->filename = $file;
  334. $this->fh = fopen($file,'rb');
  335. if (!$this->fh) { return ('ERROR - Can\'t open file ' . $file); }
  336. $this->numTTCFonts = 0;
  337. $this->TTCFonts = array();
  338. $this->version = $version = $this->read_ulong();
  339. if ($version==0x74746366) {
  340. $this->version = $version = $this->read_ulong(); // TTC Header version now
  341. if (!in_array($version, array(0x00010000,0x00020000)))
  342. return("ERROR - Error parsing TrueType Collection: version=".$version." - " . $file);
  343. }
  344. else {
  345. return("ERROR - Not a TrueType Collection: version=".$version." - " . $file);
  346. }
  347. $this->numTTCFonts = $this->read_ulong();
  348. for ($i=1; $i<=$this->numTTCFonts; $i++) {
  349. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  350. }
  351. }
  352. // Used to get font information from files in directory for mPDF config
  353. function extractCoreInfo($file, $TTCfontID=0) {
  354. $this->filename = $file;
  355. $this->fh = fopen($file,'rb');
  356. if (!$this->fh) { return ('ERROR - Can\'t open file ' . $file); }
  357. $this->_pos = 0;
  358. $this->charWidths = '';
  359. $this->glyphPos = array();
  360. $this->charToGlyph = array();
  361. $this->tables = array();
  362. $this->otables = array();
  363. $this->ascent = 0;
  364. $this->descent = 0;
  365. $this->numTTCFonts = 0;
  366. $this->TTCFonts = array();
  367. $this->version = $version = $this->read_ulong();
  368. $this->panose = array(); // mPDF 5.0
  369. if ($version==0x4F54544F)
  370. return("ERROR - NOT ADDED as Postscript outlines are not supported - " . $file);
  371. if ($version==0x74746366) {
  372. if ($TTCfontID > 0) {
  373. $this->version = $version = $this->read_ulong(); // TTC Header version now
  374. if (!in_array($version, array(0x00010000,0x00020000)))
  375. return("ERROR - NOT ADDED as Error parsing TrueType Collection: version=".$version." - " . $file);
  376. }
  377. else return("ERROR - Error parsing TrueType Collection - " . $file);
  378. $this->numTTCFonts = $this->read_ulong();
  379. for ($i=1; $i<=$this->numTTCFonts; $i++) {
  380. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  381. }
  382. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  383. $this->version = $version = $this->read_ulong(); // TTFont version again now
  384. $this->readTableDirectory(false);
  385. }
  386. else {
  387. if (!in_array($version, array(0x00010000,0x74727565)))
  388. return("ERROR - NOT ADDED as Not a TrueType font: version=".$version." - " . $file);
  389. $this->readTableDirectory(false);
  390. }
  391. /* Included for testing...
  392. $cmap_offset = $this->seek_table("cmap");
  393. $this->skip(2);
  394. $cmapTableCount = $this->read_ushort();
  395. $unicode_cmap_offset = 0;
  396. for ($i=0;$i<$cmapTableCount;$i++) {
  397. $x[$i]['platformId'] = $this->read_ushort();
  398. $x[$i]['encodingId'] = $this->read_ushort();
  399. $x[$i]['offset'] = $this->read_ulong();
  400. $save_pos = $this->_pos;
  401. $x[$i]['format'] = $this->get_ushort($cmap_offset + $x[$i]['offset'] );
  402. $this->seek($save_pos );
  403. }
  404. print_r($x); exit;
  405. */
  406. ///////////////////////////////////
  407. // name - Naming table
  408. ///////////////////////////////////
  409. $name_offset = $this->seek_table("name");
  410. $format = $this->read_ushort();
  411. if ($format != 0 && $format != 1) // mPDF 5.3.73
  412. return("ERROR - NOT ADDED as Unknown name table format ".$format." - " . $file);
  413. $numRecords = $this->read_ushort();
  414. $string_data_offset = $name_offset + $this->read_ushort();
  415. $names = array(1=>'',2=>'',3=>'',4=>'',6=>'');
  416. $K = array_keys($names);
  417. $nameCount = count($names);
  418. for ($i=0;$i<$numRecords; $i++) {
  419. $platformId = $this->read_ushort();
  420. $encodingId = $this->read_ushort();
  421. $languageId = $this->read_ushort();
  422. $nameId = $this->read_ushort();
  423. $length = $this->read_ushort();
  424. $offset = $this->read_ushort();
  425. if (!in_array($nameId,$K)) continue;
  426. $N = '';
  427. if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name
  428. $opos = $this->_pos;
  429. $this->seek($string_data_offset + $offset);
  430. if ($length % 2 != 0)
  431. $length += 1;
  432. $length /= 2;
  433. $N = '';
  434. while ($length > 0) {
  435. $char = $this->read_ushort();
  436. $N .= (chr($char));
  437. $length -= 1;
  438. }
  439. $this->_pos = $opos;
  440. $this->seek($opos);
  441. }
  442. else if ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name
  443. $opos = $this->_pos;
  444. $N = $this->get_chunk($string_data_offset + $offset, $length);
  445. $this->_pos = $opos;
  446. $this->seek($opos);
  447. }
  448. if ($N && $names[$nameId]=='') {
  449. $names[$nameId] = $N;
  450. $nameCount -= 1;
  451. if ($nameCount==0) break;
  452. }
  453. }
  454. if ($names[6])
  455. $psName = preg_replace('/ /','-',$names[6]);
  456. else if ($names[4])
  457. $psName = preg_replace('/ /','-',$names[4]);
  458. else if ($names[1])
  459. $psName = preg_replace('/ /','-',$names[1]);
  460. else
  461. $psName = '';
  462. if (!$names[1] && !$psName)
  463. return("ERROR - NOT ADDED as Could not find valid font name - " . $file);
  464. $this->name = $psName;
  465. if ($names[1]) { $this->familyName = $names[1]; } else { $this->familyName = $psName; }
  466. if ($names[2]) { $this->styleName = $names[2]; } else { $this->styleName = 'Regular'; }
  467. ///////////////////////////////////
  468. // head - Font header table
  469. ///////////////////////////////////
  470. $this->seek_table("head");
  471. $ver_maj = $this->read_ushort();
  472. $ver_min = $this->read_ushort();
  473. if ($ver_maj != 1)
  474. return('ERROR - NOT ADDED as Unknown head table version '. $ver_maj .'.'. $ver_min." - " . $file);
  475. $this->fontRevision = $this->read_ushort() . $this->read_ushort();
  476. $this->skip(4);
  477. $magic = $this->read_ulong();
  478. if ($magic != 0x5F0F3CF5)
  479. return('ERROR - NOT ADDED as Invalid head table magic ' .$magic." - " . $file);
  480. $this->skip(2);
  481. $this->unitsPerEm = $unitsPerEm = $this->read_ushort();
  482. $scale = 1000 / $unitsPerEm;
  483. $this->skip(24);
  484. $macStyle = $this->read_short();
  485. $this->skip(4);
  486. $indexLocFormat = $this->read_short();
  487. ///////////////////////////////////
  488. // OS/2 - OS/2 and Windows metrics table
  489. ///////////////////////////////////
  490. $sFamily = '';
  491. $panose = '';
  492. $fsSelection = '';
  493. if (isset($this->tables["OS/2"])) {
  494. $this->seek_table("OS/2");
  495. $this->skip(30);
  496. $sF = $this->read_short();
  497. $sFamily = ($sF >> 8);
  498. $this->_pos += 10; //PANOSE = 10 byte length
  499. $panose = fread($this->fh,10);
  500. $this->panose = array();
  501. for ($p=0;$p<strlen($panose);$p++) { $this->panose[] = ord($panose[$p]); }
  502. $this->skip(20);
  503. $fsSelection = $this->read_short();
  504. }
  505. ///////////////////////////////////
  506. // post - PostScript table
  507. ///////////////////////////////////
  508. $this->seek_table("post");
  509. $this->skip(4);
  510. $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0;
  511. $this->skip(4);
  512. $isFixedPitch = $this->read_ulong();
  513. ///////////////////////////////////
  514. // cmap - Character to glyph index mapping table
  515. ///////////////////////////////////
  516. $cmap_offset = $this->seek_table("cmap");
  517. $this->skip(2);
  518. $cmapTableCount = $this->read_ushort();
  519. $unicode_cmap_offset = 0;
  520. for ($i=0;$i<$cmapTableCount;$i++) {
  521. $platformID = $this->read_ushort();
  522. $encodingID = $this->read_ushort();
  523. $offset = $this->read_ulong();
  524. $save_pos = $this->_pos;
  525. if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
  526. $format = $this->get_ushort($cmap_offset + $offset);
  527. if ($format == 4) {
  528. if (!$unicode_cmap_offset) $unicode_cmap_offset = $cmap_offset + $offset;
  529. }
  530. }
  531. else if ((($platformID == 3 && $encodingID == 10) || $platformID == 0)) { // Microsoft, Unicode Format 12 table HKCS
  532. $format = $this->get_ushort($cmap_offset + $offset);
  533. if ($format == 12) {
  534. $unicode_cmap_offset = $cmap_offset + $offset;
  535. break;
  536. }
  537. }
  538. $this->seek($save_pos );
  539. }
  540. if (!$unicode_cmap_offset)
  541. return('ERROR - Font ('.$this->filename .') NOT ADDED as it is not Unicode encoded, and cannot be used by mPDF');
  542. $rtl = false;
  543. $indic = false;
  544. $cjk = false;
  545. $sip = false;
  546. $smp = false;
  547. // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
  548. if ($format == 12) {
  549. $this->seek($unicode_cmap_offset + 4);
  550. $length = $this->read_ulong();
  551. $limit = $unicode_cmap_offset + $length;
  552. $this->skip(4);
  553. $nGroups = $this->read_ulong();
  554. for($i=0; $i<$nGroups ; $i++) {
  555. $startCharCode = $this->read_ulong();
  556. $endCharCode = $this->read_ulong();
  557. $startGlyphCode = $this->read_ulong();
  558. if (($endCharCode > 0x20000 && $endCharCode < 0x2A6DF) || ($endCharCode > 0x2F800 && $endCharCode < 0x2FA1F)) {
  559. $sip = true;
  560. }
  561. else if ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) {
  562. $smp = true;
  563. }
  564. else if (($endCharCode > 0x0590 && $endCharCode < 0x077F) || ($endCharCode > 0xFE70 && $endCharCode < 0xFEFF) || ($endCharCode > 0xFB50 && $endCharCode < 0xFDFF)) {
  565. $rtl = true;
  566. }
  567. else if ($endCharCode > 0x0900 && $endCharCode < 0x0DFF) {
  568. $indic = true;
  569. }
  570. else if (($endCharCode > 0x2E80 && $endCharCode < 0x4DC0) || ($endCharCode > 0x4E00 && $endCharCode < 0xA4CF) || ($endCharCode > 0xAC00 && $endCharCode < 0xD7AF) || ($endCharCode > 0xF900 && $endCharCode < 0xFAFF) || ($endCharCode > 0xFE30 && $endCharCode < 0xFE4F)) {
  571. $cjk = true;
  572. }
  573. }
  574. }
  575. else { // Format 4 CMap
  576. $this->seek($unicode_cmap_offset + 2);
  577. $length = $this->read_ushort();
  578. $limit = $unicode_cmap_offset + $length;
  579. $this->skip(2);
  580. $segCount = $this->read_ushort() / 2;
  581. $this->skip(6);
  582. $endCount = array();
  583. for($i=0; $i<$segCount; $i++) { $endCount[] = $this->read_ushort(); }
  584. $this->skip(2);
  585. $startCount = array();
  586. for($i=0; $i<$segCount; $i++) { $startCount[] = $this->read_ushort(); }
  587. $idDelta = array();
  588. for($i=0; $i<$segCount; $i++) { $idDelta[] = $this->read_short(); } // ???? was unsigned short
  589. $idRangeOffset_start = $this->_pos;
  590. $idRangeOffset = array();
  591. for($i=0; $i<$segCount; $i++) { $idRangeOffset[] = $this->read_ushort(); }
  592. for ($n=0;$n<$segCount;$n++) {
  593. if (($endCount[$n] > 0x0590 && $endCount[$n] < 0x077F) || ($endCount[$n] > 0xFE70 && $endCount[$n] < 0xFEFF) || ($endCount[$n] > 0xFB50 && $endCount[$n] < 0xFDFF)) {
  594. $rtl = true;
  595. }
  596. else if ($endCount[$n] > 0x0900 && $endCount[$n] < 0x0DFF) {
  597. $indic = true;
  598. }
  599. else if (($endCount[$n] > 0x2E80 && $endCount[$n] < 0x4DC0) || ($endCount[$n] > 0x4E00 && $endCount[$n] < 0xA4CF) || ($endCount[$n] > 0xAC00 && $endCount[$n] < 0xD7AF) || ($endCount[$n] > 0xF900 && $endCount[$n] < 0xFAFF) || ($endCount[$n] > 0xFE30 && $endCount[$n] < 0xFE4F)) {
  600. $cjk = true;
  601. }
  602. }
  603. }
  604. $bold = false;
  605. $italic = false;
  606. $ftype = '';
  607. if ($macStyle & (1 << 0)) { $bold = true; } // bit 0 bold
  608. else if ($fsSelection & (1 << 5)) { $bold = true; } // 5 BOLD Characters are emboldened
  609. if ($macStyle & (1 << 1)) { $italic = true; } // bit 1 italic
  610. else if ($fsSelection & (1 << 0)) { $italic = true; } // 0 ITALIC Font contains Italic characters, otherwise they are upright
  611. else if ($this->italicAngle <> 0) { $italic = true; }
  612. if ($isFixedPitch ) { $ftype = 'mono'; }
  613. else if ($sFamily >0 && $sFamily <8) { $ftype = 'serif'; }
  614. else if ($sFamily ==8) { $ftype = 'sans'; }
  615. else if ($sFamily ==10) { $ftype = 'cursive'; }
  616. // Use PANOSE
  617. if ($panose) {
  618. $bFamilyType=ord($panose[0]);
  619. if ($bFamilyType==2) {
  620. $bSerifStyle=ord($panose[1]);
  621. if (!$ftype) {
  622. if ($bSerifStyle>1 && $bSerifStyle<11) { $ftype = 'serif'; }
  623. else if ($bSerifStyle>10) { $ftype = 'sans'; }
  624. }
  625. $bProportion=ord($panose[3]);
  626. if ($bProportion==9 || $bProportion==1) { $ftype = 'mono'; } // ==1 i.e. No Fit needed for OCR-a and -b
  627. }
  628. else if ($bFamilyType==3) {
  629. $ftype = 'cursive';
  630. }
  631. }
  632. fclose($this->fh);
  633. return array($this->familyName, $bold, $italic, $ftype, $TTCfontID, $rtl, $indic, $cjk, $sip, $smp);
  634. }
  635. /////////////////////////////////////////////////////////////////////////////////////////
  636. /////////////////////////////////////////////////////////////////////////////////////////
  637. function extractInfo($debug=false, $BMPonly=false, $kerninfo=false) {
  638. ///////////////////////////////////
  639. // name - Naming table
  640. ///////////////////////////////////
  641. $this->panose = array();
  642. $this->sFamilyClass = 0;
  643. $this->sFamilySubClass = 0;
  644. /* Test purposes - displays table of names
  645. $name_offset = $this->seek_table("name");
  646. $format = $this->read_ushort();
  647. if ($format != 0 && $format != 1) // mPDF 5.3.73
  648. die("Unknown name table format ".$format);
  649. $numRecords = $this->read_ushort();
  650. $string_data_offset = $name_offset + $this->read_ushort();
  651. for ($i=0;$i<$numRecords; $i++) {
  652. $x[$i]['platformId'] = $this->read_ushort();
  653. $x[$i]['encodingId'] = $this->read_ushort();
  654. $x[$i]['languageId'] = $this->read_ushort();
  655. $x[$i]['nameId'] = $this->read_ushort();
  656. $x[$i]['length'] = $this->read_ushort();
  657. $x[$i]['offset'] = $this->read_ushort();
  658. $N = '';
  659. if ($x[$i]['platformId'] == 1 && $x[$i]['encodingId'] == 0 && $x[$i]['languageId'] == 0) { // Roman
  660. $opos = $this->_pos;
  661. $N = $this->get_chunk($string_data_offset + $x[$i]['offset'] , $x[$i]['length'] );
  662. $this->_pos = $opos;
  663. $this->seek($opos);
  664. }
  665. else { // Unicode
  666. $opos = $this->_pos;
  667. $this->seek($string_data_offset + $x[$i]['offset'] );
  668. $length = $x[$i]['length'] ;
  669. if ($length % 2 != 0)
  670. $length -= 1;
  671. // die("PostScript name is UTF-16BE string of odd length");
  672. $length /= 2;
  673. $N = '';
  674. while ($length > 0) {
  675. $char = $this->read_ushort();
  676. $N .= (chr($char));
  677. $length -= 1;
  678. }
  679. $this->_pos = $opos;
  680. $this->seek($opos);
  681. }
  682. $x[$i]['names'][$nameId] = $N;
  683. }
  684. print_r($x); exit;
  685. */
  686. $name_offset = $this->seek_table("name");
  687. $format = $this->read_ushort();
  688. if ($format != 0 && $format != 1) // mPDF 5.3.73
  689. die("Unknown name table format ".$format);
  690. $numRecords = $this->read_ushort();
  691. $string_data_offset = $name_offset + $this->read_ushort();
  692. $names = array(1=>'',2=>'',3=>'',4=>'',6=>'');
  693. $K = array_keys($names);
  694. $nameCount = count($names);
  695. for ($i=0;$i<$numRecords; $i++) {
  696. $platformId = $this->read_ushort();
  697. $encodingId = $this->read_ushort();
  698. $languageId = $this->read_ushort();
  699. $nameId = $this->read_ushort();
  700. $length = $this->read_ushort();
  701. $offset = $this->read_ushort();
  702. if (!in_array($nameId,$K)) continue;
  703. $N = '';
  704. if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name
  705. $opos = $this->_pos;
  706. $this->seek($string_data_offset + $offset);
  707. if ($length % 2 != 0)
  708. die("PostScript name is UTF-16BE string of odd length");
  709. $length /= 2;
  710. $N = '';
  711. while ($length > 0) {
  712. $char = $this->read_ushort();
  713. $N .= (chr($char));
  714. $length -= 1;
  715. }
  716. $this->_pos = $opos;
  717. $this->seek($opos);
  718. }
  719. else if ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name
  720. $opos = $this->_pos;
  721. $N = $this->get_chunk($string_data_offset + $offset, $length);
  722. $this->_pos = $opos;
  723. $this->seek($opos);
  724. }
  725. if ($N && $names[$nameId]=='') {
  726. $names[$nameId] = $N;
  727. $nameCount -= 1;
  728. if ($nameCount==0) break;
  729. }
  730. }
  731. if ($names[6])
  732. // $psName = preg_replace('/ /','-',$names[6]); // mPDF 5.2.03
  733. $psName = $names[6];
  734. else if ($names[4])
  735. $psName = preg_replace('/ /','-',$names[4]);
  736. else if ($names[1])
  737. $psName = preg_replace('/ /','-',$names[1]);
  738. else
  739. $psName = '';
  740. if (!$psName)
  741. die("Could not find PostScript font name: ".$this->filename);
  742. if ($debug) {
  743. for ($i=0;$i<count($psName);$i++) { // mPDF 5.3.07
  744. $c = $psName[$i];
  745. $oc = ord($c);
  746. if ($oc>126 || strpos(' [](){}<>/%',$c)!==false)
  747. die("psName=".$psName." contains invalid character ".$c." ie U+".ord(c));
  748. }
  749. }
  750. $this->name = $psName;
  751. if ($names[1]) { $this->familyName = $names[1]; } else { $this->familyName = $psName; }
  752. if ($names[2]) { $this->styleName = $names[2]; } else { $this->styleName = 'Regular'; }
  753. if ($names[4]) { $this->fullName = $names[4]; } else { $this->fullName = $psName; }
  754. if ($names[3]) { $this->uniqueFontID = $names[3]; } else { $this->uniqueFontID = $psName; }
  755. if ($names[6]) { $this->fullName = $names[6]; } // mPDF 5.2.03
  756. ///////////////////////////////////
  757. // head - Font header table
  758. ///////////////////////////////////
  759. $this->seek_table("head");
  760. if ($debug) {
  761. $ver_maj = $this->read_ushort();
  762. $ver_min = $this->read_ushort();
  763. if ($ver_maj != 1)
  764. die('Unknown head table version '. $ver_maj .'.'. $ver_min);
  765. $this->fontRevision = $this->read_ushort() . $this->read_ushort();
  766. $this->skip(4);
  767. $magic = $this->read_ulong();
  768. if ($magic != 0x5F0F3CF5)
  769. die('Invalid head table magic ' .$magic);
  770. $this->skip(2);
  771. }
  772. else {
  773. $this->skip(18);
  774. }
  775. $this->unitsPerEm = $unitsPerEm = $this->read_ushort();
  776. $scale = 1000 / $unitsPerEm;
  777. $this->skip(16);
  778. $xMin = $this->read_short();
  779. $yMin = $this->read_short();
  780. $xMax = $this->read_short();
  781. $yMax = $this->read_short();
  782. $this->bbox = array(($xMin*$scale), ($yMin*$scale), ($xMax*$scale), ($yMax*$scale));
  783. $this->skip(3*2);
  784. $indexToLocFormat = $this->read_ushort();
  785. $glyphDataFormat = $this->read_ushort();
  786. if ($glyphDataFormat != 0)
  787. die('Unknown glyph data format '.$glyphDataFormat);
  788. ///////////////////////////////////
  789. // hhea metrics table
  790. ///////////////////////////////////
  791. // ttf2t1 seems to use this value rather than the one in OS/2 - so put in for compatibility
  792. if (isset($this->tables["hhea"])) {
  793. $this->seek_table("hhea");
  794. $this->skip(4);
  795. $hheaAscender = $this->read_short();
  796. $hheaDescender = $this->read_short();
  797. $this->ascent = ($hheaAscender *$scale);
  798. $this->descent = ($hheaDescender *$scale);
  799. }
  800. ///////////////////////////////////
  801. // OS/2 - OS/2 and Windows metrics table
  802. ///////////////////////////////////
  803. if (isset($this->tables["OS/2"])) {
  804. $this->seek_table("OS/2");
  805. $version = $this->read_ushort();
  806. $this->skip(2);
  807. $usWeightClass = $this->read_ushort();
  808. $this->skip(2);
  809. $fsType = $this->read_ushort();
  810. if ($fsType == 0x0002 || ($fsType & 0x0300) != 0) {
  811. global $overrideTTFFontRestriction;
  812. if (!$overrideTTFFontRestriction) die('ERROR - Font file '.$this->filename.' cannot be embedded due to copyright restrictions.');
  813. $this->restrictedUse = true;
  814. }
  815. $this->skip(20);
  816. $sF = $this->read_short();
  817. $this->sFamilyClass = ($sF >> 8);
  818. $this->sFamilySubClass = ($sF & 0xFF);
  819. $this->_pos += 10; //PANOSE = 10 byte length
  820. $panose = fread($this->fh,10);
  821. $this->panose = array();
  822. for ($p=0;$p<strlen($panose);$p++) { $this->panose[] = ord($panose[$p]); }
  823. $this->skip(26);
  824. $sTypoAscender = $this->read_short();
  825. $sTypoDescender = $this->read_short();
  826. if (!$this->ascent) $this->ascent = ($sTypoAscender*$scale);
  827. if (!$this->descent) $this->descent = ($sTypoDescender*$scale);
  828. if ($version > 1) {
  829. $this->skip(16);
  830. $sCapHeight = $this->read_short();
  831. $this->capHeight = ($sCapHeight*$scale);
  832. }
  833. else {
  834. $this->capHeight = $this->ascent;
  835. }
  836. }
  837. else {
  838. $usWeightClass = 500;
  839. if (!$this->ascent) $this->ascent = ($yMax*$scale);
  840. if (!$this->descent) $this->descent = ($yMin*$scale);
  841. $this->capHeight = $this->ascent;
  842. }
  843. $this->stemV = 50 + intval(pow(($usWeightClass / 65.0),2));
  844. ///////////////////////////////////
  845. // post - PostScript table
  846. ///////////////////////////////////
  847. $this->seek_table("post");
  848. if ($debug) {
  849. $ver_maj = $this->read_ushort();
  850. $ver_min = $this->read_ushort();
  851. if ($ver_maj <1 || $ver_maj >4)
  852. die('Unknown post table version '.$ver_maj);
  853. }
  854. else {
  855. $this->skip(4);
  856. }
  857. $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0;
  858. $this->underlinePosition = $this->read_short() * $scale;
  859. $this->underlineThickness = $this->read_short() * $scale;
  860. $isFixedPitch = $this->read_ulong();
  861. $this->flags = 4;
  862. if ($this->italicAngle!= 0)
  863. $this->flags = $this->flags | 64;
  864. if ($usWeightClass >= 600)
  865. $this->flags = $this->flags | 262144;
  866. if ($isFixedPitch)
  867. $this->flags = $this->flags | 1;
  868. ///////////////////////////////////
  869. // hhea - Horizontal header table
  870. ///////////////////////////////////
  871. $this->seek_table("hhea");
  872. if ($debug) {
  873. $ver_maj = $this->read_ushort();
  874. $ver_min = $this->read_ushort();
  875. if ($ver_maj != 1)
  876. die('Unknown hhea table version '.$ver_maj);
  877. $this->skip(28);
  878. }
  879. else {
  880. $this->skip(32);
  881. }
  882. $metricDataFormat = $this->read_ushort();
  883. if ($metricDataFormat != 0)
  884. die('Unknown horizontal metric data format '.$metricDataFormat);
  885. $numberOfHMetrics = $this->read_ushort();
  886. if ($numberOfHMetrics == 0)
  887. die('Number of horizontal metrics is 0');
  888. ///////////////////////////////////
  889. // maxp - Maximum profile table
  890. ///////////////////////////////////
  891. $this->seek_table("maxp");
  892. if ($debug) {
  893. $ver_maj = $this->read_ushort();
  894. $ver_min = $this->read_ushort();
  895. if ($ver_maj != 1)
  896. die('Unknown maxp table version '.$ver_maj);
  897. }
  898. else {
  899. $this->skip(4);
  900. }
  901. $numGlyphs = $this->read_ushort();
  902. ///////////////////////////////////
  903. // cmap - Character to glyph index mapping table
  904. ///////////////////////////////////
  905. /* Test purposes - displays list of cmaps
  906. $cmap_offset = $this->seek_table("cmap");
  907. $this->skip(2);
  908. $cmapTableCount = $this->read_ushort();
  909. $unicode_cmap_offset = 0;
  910. for ($i=0;$i<$cmapTableCount;$i++) {
  911. $x[$i]['platformId'] = $this->read_ushort();
  912. $x[$i]['encodingId'] = $this->read_ushort();
  913. $x[$i]['offset'] = $this->read_ulong();
  914. $save_pos = $this->_pos;
  915. $x[$i]['format'] = $this->get_ushort($cmap_offset + $x[$i]['offset'] );
  916. $this->seek($save_pos );
  917. }
  918. print_r($x); exit;
  919. */
  920. $cmap_offset = $this->seek_table("cmap");
  921. $this->skip(2);
  922. $cmapTableCount = $this->read_ushort();
  923. $unicode_cmap_offset = 0;
  924. for ($i=0;$i<$cmapTableCount;$i++) {
  925. $platformID = $this->read_ushort();
  926. $encodingID = $this->read_ushort();
  927. $offset = $this->read_ulong();
  928. $save_pos = $this->_pos;
  929. if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
  930. $format = $this->get_ushort($cmap_offset + $offset);
  931. if ($format == 4) {
  932. if (!$unicode_cmap_offset) $unicode_cmap_offset = $cmap_offset + $offset;
  933. if ($BMPonly) break; // mPDF 5.0
  934. }
  935. }
  936. // Microsoft, Unicode Format 12 table HKCS
  937. else if ((($platformID == 3 && $encodingID == 10) || $platformID == 0) && !$BMPonly) {
  938. $format = $this->get_ushort($cmap_offset + $offset);
  939. if ($format == 12) {
  940. $unicode_cmap_offset = $cmap_offset + $offset;
  941. break;
  942. }
  943. }
  944. $this->seek($save_pos );
  945. }
  946. if (!$unicode_cmap_offset)
  947. die('Font ('.$this->filename .') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
  948. $sipset = false;
  949. $smpset = false;
  950. // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
  951. if ($format == 12 && !$BMPonly) {
  952. $this->maxUniChar = 0; // mPDF 5.0
  953. $this->seek($unicode_cmap_offset + 4);
  954. $length = $this->read_ulong();
  955. $limit = $unicode_cmap_offset + $length;
  956. $this->skip(4);
  957. $nGroups = $this->read_ulong();
  958. $glyphToChar = array();
  959. $charToGlyph = array();
  960. for($i=0; $i<$nGroups ; $i++) {
  961. $startCharCode = $this->read_ulong();
  962. $endCharCode = $this->read_ulong();
  963. $startGlyphCode = $this->read_ulong();
  964. if (($endCharCode > 0x20000 && $endCharCode < 0x2A6DF) || ($endCharCode > 0x2F800 && $endCharCode < 0x2FA1F)) {
  965. $sipset = true;
  966. }
  967. else if ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) {
  968. $smpset = true;
  969. }
  970. $offset = 0;
  971. for ($unichar=$startCharCode;$unichar<=$endCharCode;$unichar++) {
  972. $glyph = $startGlyphCode + $offset ;
  973. $offset++;
  974. $charToGlyph[$unichar] = $glyph;
  975. if ($unichar < 196608) { $this->maxUniChar = max($unichar,$this->maxUniChar); }
  976. $glyphToChar[$glyph][] = $unichar;
  977. }
  978. }
  979. }
  980. else {
  981. $glyphToChar = array();
  982. $charToGlyph = array();
  983. $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph );
  984. }
  985. $this->sipset = $sipset ;
  986. $this->smpset = $smpset ;
  987. ///////////////////////////////////
  988. // hmtx - Horizontal metrics table
  989. ///////////////////////////////////
  990. $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
  991. ///////////////////////////////////
  992. // kern - Kerning pair table mPDF 5.1
  993. ///////////////////////////////////
  994. if ($kerninfo) {
  995. // Recognises old form of Kerning table - as required by Windows - Format 0 only
  996. $kern_offset = $this->seek_table("kern");
  997. $version = $this->read_ushort();
  998. $nTables = $this->read_ushort();
  999. // subtable header
  1000. $sversion = $this->read_ushort();
  1001. $slength = $this->read_ushort();
  1002. $scoverage = $this->read_ushort();
  1003. $format = $scoverage >> 8;
  1004. if ($kern_offset && $version==0 && $format==0) {
  1005. // Format 0
  1006. $nPairs = $this->read_ushort();
  1007. $this->skip(6);
  1008. for ($i=0; $i<$nPairs; $i++) {
  1009. $left = $this->read_ushort();
  1010. $right = $this->read_ushort();
  1011. $val = $this->read_short();
  1012. if (count($glyphToChar[$left])==1 && count($glyphToChar[$right])==1) {
  1013. if ($left != 32 && $right != 32) {
  1014. $this->kerninfo[$glyphToChar[$left][0]][$glyphToChar[$right][0]] = intval($val*$scale);
  1015. }
  1016. }
  1017. }
  1018. }
  1019. }
  1020. }
  1021. /////////////////////////////////////////////////////////////////////////////////////////
  1022. function makeSubset($file, &$subset, $TTCfontID=0, $debug=false) {
  1023. $this->filename = $file;
  1024. $this->fh = fopen($file ,'rb') or die('Can\'t open file ' . $file);
  1025. $this->_pos = 0;
  1026. $this->charWidths = '';
  1027. $this->glyphPos = array();
  1028. $this->charToGlyph = array();
  1029. $this->tables = array();
  1030. $this->otables = array();
  1031. $this->ascent = 0;
  1032. $this->descent = 0;
  1033. $this->numTTCFonts = 0;
  1034. $this->TTCFonts = array();
  1035. $this->skip(4);
  1036. $this->maxUni = 0;
  1037. if ($TTCfontID > 0) {
  1038. $this->version = $version = $this->read_ulong(); // TTC Header version now
  1039. if (!in_array($version, array(0x00010000,0x00020000)))
  1040. die("ERROR - Error parsing TrueType Collection: version=".$version." - " . $file);
  1041. $this->numTTCFonts = $this->read_ulong();
  1042. for ($i=1; $i<=$this->numTTCFonts; $i++) {
  1043. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  1044. }
  1045. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  1046. $this->version = $version = $this->read_ulong(); // TTFont version again now
  1047. }
  1048. $this->readTableDirectory($debug);
  1049. ///////////////////////////////////
  1050. // head - Font header table
  1051. ///////////////////////////////////
  1052. $this->seek_table("head");
  1053. $this->skip(50);
  1054. $indexToLocFormat = $this->read_ushort();
  1055. $glyphDataFormat = $this->read_ushort();
  1056. ///////////////////////////////////
  1057. // hhea - Horizontal header table
  1058. ///////////////////////////////////
  1059. $this->seek_table("hhea");
  1060. $this->skip(32);
  1061. $metricDataFormat = $this->read_ushort();
  1062. $orignHmetrics = $numberOfHMetrics = $this->read_ushort();
  1063. ///////////////////////////////////
  1064. // maxp - Maximum profile table
  1065. ///////////////////////////////////
  1066. $this->seek_table("maxp");
  1067. $this->skip(4);
  1068. $numGlyphs = $this->read_ushort();
  1069. ///////////////////////////////////
  1070. // cmap - Character to glyph index mapping table
  1071. ///////////////////////////////////
  1072. $cmap_offset = $this->seek_table("cmap");
  1073. $this->skip(2);
  1074. $cmapTableCount = $this->read_ushort();
  1075. $unicode_cmap_offset = 0;
  1076. for ($i=0;$i<$cmapTableCount;$i++) {
  1077. $platformID = $this->read_ushort();
  1078. $encodingID = $this->read_ushort();
  1079. $offset = $this->read_ulong();
  1080. $save_pos = $this->_pos;
  1081. if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
  1082. $format = $this->get_ushort($cmap_offset + $offset);
  1083. if ($format == 4) {
  1084. $unicode_cmap_offset = $cmap_offset + $offset;
  1085. break;
  1086. }
  1087. }
  1088. $this->seek($save_pos );
  1089. }
  1090. if (!$unicode_cmap_offset)
  1091. die('Font ('.$this->filename .') does not have Unicode cmap (platform 3, encoding 1, format 4, or platform 0 [any encoding] format 4)');
  1092. $glyphToChar = array();
  1093. $charToGlyph = array();
  1094. $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph );
  1095. $this->charToGlyph = $charToGlyph;
  1096. ///////////////////////////////////
  1097. // hmtx - Horizontal metrics table
  1098. ///////////////////////////////////
  1099. $scale = 1; // not used
  1100. $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
  1101. ///////////////////////////////////
  1102. // loca - Index to location
  1103. ///////////////////////////////////
  1104. $this->getLOCA($indexToLocFormat, $numGlyphs);
  1105. $subsetglyphs = array(0=>0, 1=>1, 2=>2); // mPDF 5.3.31
  1106. $subsetCharToGlyph = array();
  1107. foreach($subset AS $code) {
  1108. if (isset($this->charToGlyph[$code])) {
  1109. $subsetglyphs[$this->charToGlyph[$code]] = $code; // Old Glyph ID => Unicode
  1110. $subsetCharToGlyph[$code] = $this->charToGlyph[$code]; // Unicode to old GlyphID
  1111. }
  1112. $this->maxUni = max($this->maxUni, $code);
  1113. }
  1114. list($start,$dummy) = $this->get_table_pos('glyf');
  1115. $glyphSet = array();
  1116. ksort($subsetglyphs);
  1117. $n = 0;
  1118. // mPDF 5.0
  1119. $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.
  1120. foreach($subsetglyphs AS $originalGlyphIdx => $uni) {
  1121. $fsLastCharIndex = max($fsLastCharIndex , $uni); // mPDF 5.0
  1122. $glyphSet[$originalGlyphIdx] = $n; // old glyphID to new glyphID
  1123. $n++;
  1124. }
  1125. ksort($subsetCharToGlyph);
  1126. foreach($subsetCharToGlyph AS $uni => $originalGlyphIdx) {
  1127. $codeToGlyph[$uni] = $glyphSet[$originalGlyphIdx] ;
  1128. }
  1129. $this->codeToGlyph = $codeToGlyph;
  1130. ksort($subsetglyphs);
  1131. foreach($subsetglyphs AS $originalGlyphIdx => $uni) {
  1132. $this->getGlyphs($originalGlyphIdx, $start, $glyphSet, $subsetglyphs);
  1133. }
  1134. $numGlyphs = $numberOfHMetrics = count($subsetglyphs );
  1135. ///////////////////////////////////
  1136. // name - table copied from the original
  1137. ///////////////////////////////////
  1138. $this->add('name', $this->get_table('name'));
  1139. ///////////////////////////////////
  1140. //tables copied from the original
  1141. ///////////////////////////////////
  1142. // mPDF 5.0.067 // 5.1.020
  1143. $tags = array ('cvt ', 'fpgm', 'prep', 'gasp');
  1144. foreach($tags AS $tag) {
  1145. if (isset($this->tables[$tag])) { $this->add($tag, $this->get_table($tag)); }
  1146. }
  1147. ///////////////////////////////////
  1148. // post - PostScript
  1149. ///////////////////////////////////
  1150. if (isset($this->tables['post'])) {
  1151. $opost = $this->get_table('post');
  1152. $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";
  1153. $this->add('post', $post);
  1154. }
  1155. ///////////////////////////////////
  1156. // Sort CID2GID map into segments of contiguous codes
  1157. ///////////////////////////////////
  1158. ksort($codeToGlyph);
  1159. unset($codeToGlyph[0]);
  1160. //unset($codeToGlyph[65535]);
  1161. $rangeid = 0;
  1162. $range = array();
  1163. $prevcid = -2;
  1164. $prevglidx = -1;
  1165. // for each character
  1166. foreach ($codeToGlyph as $cid => $glidx) {
  1167. if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) {
  1168. $range[$rangeid][] = $glidx;
  1169. } else {
  1170. // new range
  1171. $rangeid = $cid;
  1172. $range[$rangeid] = array();
  1173. $range[$rangeid][] = $glidx;
  1174. }
  1175. $prevcid = $cid;
  1176. $prevglidx = $glidx;
  1177. }
  1178. ///////////////////////////////////
  1179. // CMap table
  1180. ///////////////////////////////////
  1181. // cmap - Character to glyph mapping
  1182. $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF
  1183. $searchRange = 1;
  1184. $entrySelector = 0;
  1185. while ($searchRange * 2 <= $segCount ) {
  1186. $searchRange = $searchRange * 2;
  1187. $entrySelector = $entrySelector + 1;
  1188. }
  1189. $searchRange = $searchRange * 2;
  1190. $rangeShift = $segCount * 2 - $searchRange;
  1191. $length = 16 + (8*$segCount ) + ($numGlyphs+1);
  1192. // mPDF 5.3.30
  1193. $cmap = array(0, 3, // Index : version, number of encoding subtables
  1194. 0, 0, // Encoding Subtable : platform (UNI=0), encoding 0
  1195. 0, 28, // Encoding Subtable : offset (hi,lo)
  1196. 0, 3, // Encoding Subtable : platform (UNI=0), encoding 3
  1197. 0, 28, // Encoding Subtable : offset (hi,lo)
  1198. 3, 1, // Encoding Subtable : platform (MS=3), encoding 1
  1199. 0, 28, // Encoding Subtable : offset (hi,lo)
  1200. 4, $length, 0, // Format 4 Mapping subtable: format, length, language
  1201. $segCount*2,
  1202. $searchRange,
  1203. $entrySelector,
  1204. $rangeShift);
  1205. // endCode(s)
  1206. foreach($range AS $start=>$subrange) {
  1207. $endCode = $start + (count($subrange)-1);
  1208. $cmap[] = $endCode; // endCode(s)
  1209. }
  1210. $cmap[] = 0xFFFF; // endCode of last Segment
  1211. $cmap[] = 0; // reservedPad
  1212. // startCode(s)
  1213. foreach($range AS $start=>$subrange) {
  1214. $cmap[] = $start; // startCode(s)
  1215. }
  1216. $cmap[] = 0xFFFF; // startCode of last Segment
  1217. // idDelta(s)
  1218. foreach($range AS $start=>$subrange) {
  1219. $idDelta = -($start-$subrange[0]);
  1220. $n += count($subrange);
  1221. $cmap[] = $idDelta; // idDelta(s)
  1222. }
  1223. $cmap[] = 1; // idDelta of last Segment
  1224. // idRangeOffset(s)
  1225. foreach($range AS $subrange) {
  1226. $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
  1227. }
  1228. $cmap[] = 0; // idRangeOffset of last Segment
  1229. foreach($range AS $subrange) {
  1230. foreach($subrange AS $glidx) {
  1231. $cmap[] = $glidx;
  1232. }
  1233. }
  1234. $cmap[] = 0; // Mapping for last character
  1235. $cmapstr = '';
  1236. foreach($cmap AS $cm) { $cmapstr .= pack("n",$cm); }
  1237. $this->add('cmap', $cmapstr);
  1238. ///////////////////////////////////
  1239. // glyf - Glyph data
  1240. ///////////////////////////////////
  1241. list($glyfOffset,$glyfLength) = $this->get_table_pos('glyf');
  1242. if ($glyfLength < $this->maxStrLenRead) {
  1243. $glyphData = $this->get_table('glyf');
  1244. }
  1245. $offsets = array();
  1246. $glyf = '';
  1247. $pos = 0;
  1248. $hmtxstr = '';
  1249. $xMinT = 0;
  1250. $yMinT = 0;
  1251. $xMaxT = 0;
  1252. $yMaxT = 0;
  1253. $advanceWidthMax = 0;
  1254. $minLeftSideBearing = 0;
  1255. $minRightSideBearing = 0;
  1256. $xMaxExtent = 0;
  1257. $maxPoints = 0; // points in non-compound glyph
  1258. $maxContours = 0; // contours in non-compound glyph
  1259. $maxComponentPoints = 0; // points in compound glyph
  1260. $maxComponentContours = 0; // contours in compound glyph
  1261. $maxComponentElements = 0; // number of glyphs referenced at top level
  1262. $maxComponentDepth = 0; // levels of recursion, set to 0 if font has only simple glyphs
  1263. $this->glyphdata = array();
  1264. foreach($subsetglyphs AS $originalGlyphIdx => $uni) {
  1265. // hmtx - Horizontal Metrics
  1266. $hm = $this->getHMetric($orignHmetrics, $originalGlyphIdx);
  1267. $hmtxstr .= $hm;
  1268. $offsets[] = $pos;
  1269. $glyphPos = $this->glyphPos[$originalGlyphIdx];
  1270. $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
  1271. if ($glyfLength < $this->maxStrLenRead) {
  1272. $data = substr($glyphData,$glyphPos,$glyphLen);
  1273. }
  1274. else {
  1275. if ($glyphLen > 0) $data = $this->get_chunk($glyfOffset+$glyphPos,$glyphLen);
  1276. else $data = '';
  1277. }
  1278. // mPDF 5.0
  1279. if ($glyphLen > 0) {
  1280. if (_RECALC_PROFILE) {
  1281. $xMin = $this->unpack_short(substr($data,2,2));
  1282. $yMin = $this->unpack_short(substr($data,4,2));
  1283. $xMax = $this->unpack_short(substr($data,6,2));
  1284. $yMax = $this->unpack_short(substr($data,8,2));
  1285. $xMinT = min($xMinT,$xMin);
  1286. $yMinT = min($yMinT,$yMin);
  1287. $xMaxT = max($xMaxT,$xMax);
  1288. $yMaxT = max($yMaxT,$yMax);
  1289. $aw = $this->unpack_short(substr($hm,0,2));
  1290. $lsb = $this->unpack_short(substr($hm,2,2));
  1291. $advanceWidthMax = max($advanceWidthMax,$aw);
  1292. $minLeftSideBearing = min($minLeftSideBearing,$lsb);
  1293. $minRightSideBearing = min($minRightSideBearing,($aw - $lsb - ($xMax - $xMin)));
  1294. $xMaxExtent = max($xMaxExtent,($lsb + ($xMax - $xMin)));
  1295. }
  1296. $up = unpack("n", substr($data,0,2));
  1297. }
  1298. if ($glyphLen > 2 && ($up[1] & (1 << 15)) ) { // If number of contours <= -1 i.e. composiste glyph
  1299. $pos_in_glyph = 10;
  1300. $flags = GF_MORE;
  1301. $nComponentElements = 0;
  1302. while ($flags & GF_MORE) {
  1303. $nComponentElements += 1; // number of glyphs referenced at top level
  1304. $up = unpack("n", substr($data,$pos_in_glyph,2));
  1305. $flags = $up[1];
  1306. $up = unpack("n", substr($data,$pos_in_glyph+2,2));
  1307. $glyphIdx = $up[1];
  1308. $this->glyphdata[$originalGlyphIdx]['compGlyphs'][] = $glyphIdx;
  1309. $data = $this->_set_ushort($data, $pos_in_glyph + 2, $glyphSet[$glyphIdx]);
  1310. $pos_in_glyph += 4;
  1311. if ($flags & GF_WORDS) { $pos_in_glyph += 4; }
  1312. else { $pos_in_glyph += 2; }
  1313. if ($flags & GF_SCALE) { $pos_in_glyph += 2; }
  1314. else if ($flags & GF_XYSCALE) { $pos_in_glyph += 4; }
  1315. else if ($flags & GF_TWOBYTWO) { $pos_in_glyph += 8; }
  1316. }
  1317. $maxComponentElements = max($maxComponentElements, $nComponentElements);
  1318. }
  1319. // mPDF 5.0 - Simple Glyph
  1320. else if (_RECALC_PROFILE && $glyphLen > 2 && $up[1] < (1 << 15) && $up[1] > 0) { // Number of contours > 0 simple glyph
  1321. $nContours = $up[1];
  1322. $this->glyphdata[$originalGlyphIdx]['nContours'] = $nContours;
  1323. $maxContours = max($maxContours, $nContours);
  1324. // Count number of points in simple glyph
  1325. $pos_in_glyph = 10 + ($nContours * 2) - 2; // Last endContourPoint
  1326. $up = unpack("n", substr($data,$pos_in_glyph,2));
  1327. $points = $up[1]+1;
  1328. $this->glyphdata[$originalGlyphIdx]['nPoints'] = $points;
  1329. $maxPoints = max($maxPoints, $points);
  1330. }
  1331. $glyf .= $data;
  1332. $pos += $glyphLen;
  1333. if ($pos % 4 != 0) {
  1334. $padding = 4 - ($pos % 4);
  1335. $glyf .= str_repeat("\0",$padding);
  1336. $pos += $padding;
  1337. }
  1338. }
  1339. if (_RECALC_PROFILE) {
  1340. foreach($this->glyphdata AS $originalGlyphIdx => $val) {
  1341. $maxdepth = $depth = -1;
  1342. $points = 0;
  1343. $contours = 0;
  1344. $this->getGlyphData($originalGlyphIdx, $maxdepth, $depth, $points, $contours) ;
  1345. $maxComponentDepth = max($maxComponentDepth , $maxdepth);
  1346. $maxComponentPoints = max($maxComponentPoints , $points);
  1347. $maxComponentContours = max($maxComponentContours , $contours);
  1348. }
  1349. }
  1350. $offsets[] = $pos;
  1351. $this->add('glyf', $glyf);
  1352. ///////////////////////////////////
  1353. // hmtx - Horizontal Metrics
  1354. ///////////////////////////////////
  1355. $this->add('hmtx', $hmtxstr);
  1356. ///////////////////////////////////
  1357. // loca - Index to location
  1358. ///////////////////////////////////
  1359. $locastr = '';
  1360. if ((($pos + 1) >> 1) > 0xFFFF) {
  1361. $indexToLocFormat = 1; // long format
  1362. foreach($offsets AS $offset) { $locastr .= pack("N",$offset); }
  1363. }
  1364. else {
  1365. $indexToLocFormat = 0; // short format
  1366. foreach($offsets AS $offset) { $locastr .= pack("n",($offset/2)); }
  1367. }
  1368. $this->add('loca', $locastr);
  1369. ///////////////////////////////////
  1370. // head - Font header
  1371. ///////////////////////////////////
  1372. $head = $this->get_table('head');
  1373. $head = $this->_set_ushort($head, 50, $indexToLocFormat);
  1374. if (_RECALC_PROFILE) {
  1375. $head = $this->_set_short($head, 36, $xMinT); // for all glyph bounding boxes
  1376. $head = $this->_set_short($head, 38, $yMinT); // for all glyph bounding boxes
  1377. $head = $this->_set_short($head, 40, $xMaxT); // for all glyph bounding boxes
  1378. $head = $this->_set_short($head, 42, $yMaxT); // for all glyph bounding boxes
  1379. // mPDF 5.3.33
  1380. $head[17] = chr($head[17] & ~(1 << 4)); // Unset Bit 4 (as hdmx/LTSH tables not included)
  1381. }
  1382. $this->add('head', $head);
  1383. ///////////////////////////////////
  1384. // hhea - Horizontal Header
  1385. ///////////////////////////////////
  1386. $hhea = $this->get_table('hhea');
  1387. $hhea = $this->_set_ushort($hhea, 34, $numberOfHMetrics);
  1388. if (_RECALC_PROFILE) {
  1389. $hhea = $this->_set_ushort($hhea, 10, $advanceWidthMax);
  1390. $hhea = $this->_set_short($hhea, 12, $minLeftSideBearing);
  1391. $hhea = $this->_set_short($hhea, 14, $minRightSideBearing);
  1392. $hhea = $this->_set_short($hhea, 16, $xMaxExtent);
  1393. }
  1394. $this->add('hhea', $hhea);
  1395. ///////////////////////////////////
  1396. // maxp - Maximum Profile
  1397. ///////////////////////////////////
  1398. $maxp = $this->get_table('maxp');
  1399. $maxp = $this->_set_ushort($maxp, 4, $numGlyphs);
  1400. if (_RECALC_PROFILE) {
  1401. $maxp = $this->_set_ushort($maxp, 6, $maxPoints); // points in non-compound glyph
  1402. $maxp = $this->_set_ushort($maxp, 8, $maxContours); // contours in non-compound glyph
  1403. $maxp = $this->_set_ushort($maxp, 10, $maxComponentPoints); // points in compound glyph
  1404. $maxp = $this->_set_ushort($maxp, 12, $maxComponentContours); // contours in compound glyph
  1405. $maxp = $this->_set_ushort($maxp, 28, $maxComponentElements); // number of glyphs referenced at top level
  1406. $maxp = $this->_set_ushort($maxp, 30, $maxComponentDepth); // levels of recursion, set to 0 if font has only simple glyphs
  1407. }
  1408. $this->add('maxp', $maxp);
  1409. ///////////////////////////////////
  1410. // OS/2 - OS/2
  1411. ///////////////////////////////////
  1412. if (isset($this->tables['OS/2'])) {
  1413. // mPDF 5.2.03
  1414. $os2_offset = $this->seek_table("OS/2");
  1415. // mPDF 5.0
  1416. if (_RECALC_PROFILE) {
  1417. $fsSelection = $this->get_ushort($os2_offset+62);
  1418. $fsSelection = ($fsSelection & ~(1 << 6)); // 2-byte bit field containing information concerning the nature of the font patterns
  1419. // bit#0 = Italic; bit#5=Bold
  1420. // Match name table's font subfamily string
  1421. // Clear bit#6 used for 'Regular' and optional
  1422. }
  1423. // NB Currently this method never subsets characters above BMP
  1424. // Could set nonBMP bit according to $this->maxUni
  1425. $nonBMP = $this->get_ushort($os2_offset+46);
  1426. $nonBMP = ($nonBMP & ~(1 << 9)); // Unset Bit 57 (indicates non-BMP) - for interactive forms
  1427. $os2 = $this->get_table('OS/2');
  1428. if (_RECALC_PROFILE) {
  1429. $os2 = $this->_set_ushort($os2, 62, $fsSelection);
  1430. // mPDF 5.2.03
  1431. $os2 = $this->_set_ushort($os2, 66, $fsLastCharIndex);
  1432. $os2 = $this->_set_ushort($os2, 42, 0x0000); // ulCharRange (ulUnicodeRange) bits 24-31 | 16-23
  1433. $os2 = $this->_set_ushort($os2, 44, 0x0000); // ulCharRange (Unicode ranges) bits 8-15 | 0-7
  1434. $os2 = $this->_set_ushort($os2, 46, $nonBMP); // ulCharRange (Unicode ranges) bits 56-63 | 48-55
  1435. $os2 = $this->_set_ushort($os2, 48, 0x0000); // ulCharRange (Unicode ranges) bits 40-47 | 32-39
  1436. $os2 = $this->_set_ushort($os2, 50, 0x0000); // ulCharRange (Unicode ranges) bits 88-95 | 80-87
  1437. $os2 = $this->_set_ushort($os2, 52, 0x0000); // ulCharRange (Unicode ranges) bits 72-79 | 64-71
  1438. $os2 = $this->_set_ushort($os2, 54, 0x0000); // ulCharRange (Unicode ranges) bits 120-127 | 112-119
  1439. $os2 = $this->_set_ushort($os2, 56, 0x0000); // ulCharRange (Unicode ranges) bits 104-111 | 96-103
  1440. }
  1441. $os2 = $this->_set_ushort($os2, 46, $nonBMP); // Unset Bit 57 (indicates non-BMP) - for interactive forms
  1442. $this->add('OS/2', $os2 );
  1443. }
  1444. fclose($this->fh);
  1445. // Put the TTF file together
  1446. $stm = '';
  1447. $this->endTTFile($stm);
  1448. //file_put_contents('testfont.ttf', $stm); exit;
  1449. return $stm ;
  1450. }
  1451. //================================================================================
  1452. // Also does SMP
  1453. function makeSubsetSIP($file, &$subset, $TTCfontID=0, $debug=false) {
  1454. $this->fh = fopen($file ,'rb') or die('Can\'t open file ' . $file);
  1455. $this->filename = $file;
  1456. $this->_pos = 0;
  1457. $this->charWidths = '';
  1458. $this->glyphPos = array();
  1459. $this->charToGlyph = array();
  1460. $this->tables = array();
  1461. $this->otables = array();
  1462. $this->ascent = 0;
  1463. $this->descent = 0;
  1464. $this->numTTCFonts = 0;
  1465. $this->TTCFonts = array();
  1466. $this->skip(4);
  1467. if ($TTCfontID > 0) {
  1468. $this->version = $version = $this->read_ulong(); // TTC Header version now
  1469. if (!in_array($version, array(0x00010000,0x00020000)))
  1470. die("ERROR - Error parsing TrueType Collection: version=".$version." - " . $file);
  1471. $this->numTTCFonts = $this->read_ulong();
  1472. for ($i=1; $i<=$this->numTTCFonts; $i++) {
  1473. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  1474. }
  1475. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  1476. $this->version = $version = $this->read_ulong(); // TTFont version again now
  1477. }
  1478. $this->readTableDirectory($debug);
  1479. ///////////////////////////////////
  1480. // head - Font header table
  1481. ///////////////////////////////////
  1482. $this->seek_table("head");
  1483. $this->skip(50);
  1484. $indexToLocFormat = $this->read_ushort();
  1485. $glyphDataFormat = $this->read_ushort();
  1486. ///////////////////////////////////
  1487. // hhea - Horizontal header table
  1488. ///////////////////////////////////
  1489. $this->seek_table("hhea");
  1490. $this->skip(32);
  1491. $metricDataFormat = $this->read_ushort();
  1492. $orignHmetrics = $numberOfHMetrics = $this->read_ushort();
  1493. ///////////////////////////////////
  1494. // maxp - Maximum profile table
  1495. ///////////////////////////////////
  1496. $this->seek_table("maxp");
  1497. $this->skip(4);
  1498. $numGlyphs = $this->read_ushort();
  1499. ///////////////////////////////////
  1500. // cmap - Character to glyph index mapping table
  1501. ///////////////////////////////////
  1502. $cmap_offset = $this->seek_table("cmap");
  1503. $this->skip(2);
  1504. $cmapTableCount = $this->read_ushort();
  1505. $unicode_cmap_offset = 0;
  1506. for ($i=0;$i<$cmapTableCount;$i++) {
  1507. $platformID = $this->read_ushort();
  1508. $encodingID = $this->read_ushort();
  1509. $offset = $this->read_ulong();
  1510. $save_pos = $this->_pos;
  1511. // mPDF 5.3.30
  1512. if (($platformID == 3 && $encodingID == 10) || $platformID == 0) { // Microsoft, Unicode Format 12 table HKCS
  1513. $format = $this->get_ushort($cmap_offset + $offset);
  1514. if ($format == 12) {
  1515. $unicode_cmap_offset = $cmap_offset + $offset;
  1516. break;
  1517. }
  1518. }
  1519. $this->seek($save_pos );
  1520. }
  1521. if (!$unicode_cmap_offset)
  1522. die('Font does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
  1523. // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
  1524. if ($format == 12) {
  1525. $this->maxUniChar = 0;
  1526. $this->seek($unicode_cmap_offset + 4);
  1527. $length = $this->read_ulong();
  1528. $limit = $unicode_cmap_offset + $length;
  1529. $this->skip(4);
  1530. $nGroups = $this->read_ulong();
  1531. $glyphToChar = array();
  1532. $charToGlyph = array();
  1533. for($i=0; $i<$nGroups ; $i++) {
  1534. $startCharCode = $this->read_ulong();
  1535. $endCharCode = $this->read_ulong();
  1536. $startGlyphCode = $this->read_ulong();
  1537. $offset = 0;
  1538. for ($unichar=$startCharCode;$unichar<=$endCharCode;$unichar++) {
  1539. $glyph = $startGlyphCode + $offset ;
  1540. $offset++;
  1541. $charToGlyph[$unichar] = $glyph;
  1542. if ($unichar < 196608) { $this->maxUniChar = max($unichar,$this->maxUniChar); }
  1543. $glyphToChar[$glyph][] = $unichar;
  1544. }
  1545. }
  1546. }
  1547. else
  1548. die('Font does not have cmap for Unicode (format 12)');
  1549. ///////////////////////////////////
  1550. // hmtx - Horizontal metrics table
  1551. ///////////////////////////////////
  1552. $scale = 1; // not used here
  1553. $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
  1554. ///////////////////////////////////
  1555. // loca - Index to location
  1556. ///////////////////////////////////
  1557. $this->getLOCA($indexToLocFormat, $numGlyphs);
  1558. ///////////////////////////////////////////////////////////////////
  1559. $glyphMap = array(0=>0);
  1560. $glyphSet = array(0=>0);
  1561. $codeToGlyph = array();
  1562. // mPDF 5.0.067 Set a substitute if ASCII characters do not have glyphs
  1563. if (isset($charToGlyph[0x3F])) { $subs = $charToGlyph[0x3F]; } // Question mark
  1564. else { $subs = $charToGlyph[32]; }
  1565. foreach($subset AS $code) {
  1566. if (isset($charToGlyph[$code]))
  1567. $originalGlyphIdx = $charToGlyph[$code];
  1568. else if ($code<128) { // mPDF 5.0.067
  1569. $originalGlyphIdx = $subs;
  1570. }
  1571. else { $originalGlyphIdx = 0; }
  1572. if (!isset($glyphSet[$originalGlyphIdx])) {
  1573. $glyphSet[$originalGlyphIdx] = count($glyphMap);
  1574. $glyphMap[] = $originalGlyphIdx;
  1575. }
  1576. $codeToGlyph[$code] = $glyphSet[$originalGlyphIdx];
  1577. }
  1578. list($start,$dummy) = $this->get_table_pos('glyf');
  1579. $n = 0;
  1580. while ($n < count($glyphMap)) {
  1581. $originalGlyphIdx = $glyphMap[$n];
  1582. $glyphPos = $this->glyphPos[$originalGlyphIdx];
  1583. $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
  1584. $n += 1;
  1585. if (!$glyphLen) continue;
  1586. $this->seek($start + $glyphPos);
  1587. $numberOfContours = $this->read_short();
  1588. if ($numberOfContours < 0) {
  1589. $this->skip(8);
  1590. $flags = GF_MORE;
  1591. while ($flags & GF_MORE) {
  1592. $flags = $this->read_ushort();
  1593. $glyphIdx = $this->read_ushort();
  1594. if (!isset($glyphSet[$glyphIdx])) {
  1595. $glyphSet[$glyphIdx] = count($glyphMap);
  1596. $glyphMap[] = $glyphIdx;
  1597. }
  1598. if ($flags & GF_WORDS)
  1599. $this->skip(4);
  1600. else
  1601. $this->skip(2);
  1602. if ($flags & GF_SCALE)
  1603. $this->skip(2);
  1604. else if ($flags & GF_XYSCALE)
  1605. $this->skip(4);
  1606. else if ($flags & GF_TWOBYTWO)
  1607. $this->skip(8);
  1608. }
  1609. }
  1610. }
  1611. $numGlyphs = $n = count($glyphMap);
  1612. $numberOfHMetrics = $n;
  1613. ///////////////////////////////////
  1614. // name
  1615. ///////////////////////////////////
  1616. // Needs to have a name entry in 3,0 (e.g. symbol) - original font will be 3,1 (i.e. Unicode)
  1617. $name = $this->get_table('name');
  1618. $name_offset = $this->seek_table("name");
  1619. $format = $this->read_ushort();
  1620. $numRecords = $this->read_ushort();
  1621. $string_data_offset = $name_offset + $this->read_ushort();
  1622. for ($i=0;$i<$numRecords; $i++) {
  1623. $platformId = $this->read_ushort();
  1624. $encodingId = $this->read_ushort();
  1625. if ($platformId == 3 && $encodingId == 1) {
  1626. $pos = 6 + ($i * 12) + 2;
  1627. $name = $this->_set_ushort($name, $pos, 0x00); // Change encoding to 3,0 rather than 3,1
  1628. }
  1629. $this->skip(8);
  1630. }
  1631. $this->add('name', $name);
  1632. ///////////////////////////////////
  1633. // OS/2
  1634. ///////////////////////////////////
  1635. if (isset($this->tables['OS/2'])) {
  1636. $os2 = $this->get_table('OS/2');
  1637. $os2 = $this->_set_ushort($os2, 42, 0x00); // ulCharRange (Unicode ranges)
  1638. $os2 = $this->_set_ushort($os2, 44, 0x00); // ulCharRange (Unicode ranges)
  1639. $os2 = $this->_set_ushort($os2, 46, 0x00); // ulCharRange (Unicode ranges)
  1640. $os2 = $this->_set_ushort($os2, 48, 0x00); // ulCharRange (Unicode ranges)
  1641. $os2 = $this->_set_ushort($os2, 50, 0x00); // ulCharRange (Unicode ranges)
  1642. $os2 = $this->_set_ushort($os2, 52, 0x00); // ulCharRange (Unicode ranges)
  1643. $os2 = $this->_set_ushort($os2, 54, 0x00); // ulCharRange (Unicode ranges)
  1644. $os2 = $this->_set_ushort($os2, 56, 0x00); // ulCharRange (Unicode ranges)
  1645. // Set Symbol character only in ulCodePageRange
  1646. $os2 = $this->_set_ushort($os2, 78, 0x8000); // ulCodePageRange = Bit #31 Symbol **** 78 = Bit 16-31
  1647. $os2 = $this->_set_ushort($os2, 80, 0x0000); // ulCodePageRange = Bit #31 Symbol **** 80 = Bit 0-15
  1648. $os2 = $this->_set_ushort($os2, 82, 0x0000); // ulCodePageRange = Bit #32- Symbol **** 82 = Bits 48-63
  1649. $os2 = $this->_set_ushort($os2, 84, 0x0000); // ulCodePageRange = Bit #32- Symbol **** 84 = Bits 32-47
  1650. $os2 = $this->_set_ushort($os2, 64, 0x01); // FirstCharIndex
  1651. $os2 = $this->_set_ushort($os2, 66, count($subset)); // LastCharIndex
  1652. // Set PANOSE first bit to 5 for Symbol mPDF 5.0.017
  1653. $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));
  1654. $this->add('OS/2', $os2 );
  1655. }
  1656. ///////////////////////////////////
  1657. //tables copied from the original
  1658. ///////////////////////////////////
  1659. // mPDF 5.1.020 // mPDF 5.1.022
  1660. $tags = array ('cvt ', 'fpgm', 'prep', 'gasp');
  1661. foreach($tags AS $tag) { // 1.02
  1662. if (isset($this->tables[$tag])) { $this->add($tag, $this->get_table($tag)); }
  1663. }
  1664. ///////////////////////////////////
  1665. // post - PostScript
  1666. ///////////////////////////////////
  1667. if (isset($this->tables['post'])) {
  1668. $opost = $this->get_table('post');
  1669. $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";
  1670. }
  1671. $this->add('post', $post);
  1672. ///////////////////////////////////
  1673. // hhea - Horizontal Header
  1674. ///////////////////////////////////
  1675. $hhea = $this->get_table('hhea');
  1676. $hhea = $this->_set_ushort($hhea, 34, $numberOfHMetrics);
  1677. $this->add('hhea', $hhea);
  1678. ///////////////////////////////////
  1679. // maxp - Maximum Profile
  1680. ///////////////////////////////////
  1681. $maxp = $this->get_table('maxp');
  1682. $maxp = $this->_set_ushort($maxp, 4, $numGlyphs);
  1683. $this->add('maxp', $maxp);
  1684. ///////////////////////////////////
  1685. // CMap table Formats [1,0,]6 and [3,0,]4
  1686. ///////////////////////////////////
  1687. ///////////////////////////////////
  1688. // Sort CID2GID map into segments of contiguous codes
  1689. ///////////////////////////////////
  1690. $rangeid = 0;
  1691. $range = array();
  1692. $prevcid = -2;
  1693. $prevglidx = -1;
  1694. // for each character
  1695. foreach ($subset as $cid => $code) {
  1696. $glidx = $codeToGlyph[$code];
  1697. if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) {
  1698. $range[$rangeid][] = $glidx;
  1699. } else {
  1700. // new range
  1701. $rangeid = $cid;
  1702. $range[$rangeid] = array();
  1703. $range[$rangeid][] = $glidx;
  1704. }
  1705. $prevcid = $cid;
  1706. $prevglidx = $glidx;
  1707. }
  1708. // cmap - Character to glyph mapping
  1709. $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF
  1710. $searchRange = 1;
  1711. $entrySelector = 0;
  1712. while ($searchRange * 2 <= $segCount ) {
  1713. $searchRange = $searchRange * 2;
  1714. $entrySelector = $entrySelector + 1;
  1715. }
  1716. $searchRange = $searchRange * 2;
  1717. $rangeShift = $segCount * 2 - $searchRange;
  1718. $length = 16 + (8*$segCount ) + ($numGlyphs+1);
  1719. $cmap = array(
  1720. 4, $length, 0, // Format 4 Mapping subtable: format, length, language
  1721. $segCount*2,
  1722. $searchRange,
  1723. $entrySelector,
  1724. $rangeShift);
  1725. // endCode(s)
  1726. foreach($range AS $start=>$subrange) {
  1727. $endCode = $start + (count($subrange)-1);
  1728. $cmap[] = $endCode; // endCode(s)
  1729. }
  1730. $cmap[] = 0xFFFF; // endCode of last Segment
  1731. $cmap[] = 0; // reservedPad
  1732. // startCode(s)
  1733. foreach($range AS $start=>$subrange) {
  1734. $cmap[] = $start; // startCode(s)
  1735. }
  1736. $cmap[] = 0xFFFF; // startCode of last Segment
  1737. // idDelta(s)
  1738. foreach($range AS $start=>$subrange) {
  1739. $idDelta = -($start-$subrange[0]);
  1740. $n += count($subrange);
  1741. $cmap[] = $idDelta; // idDelta(s)
  1742. }
  1743. $cmap[] = 1; // idDelta of last Segment
  1744. // idRangeOffset(s)
  1745. foreach($range AS $subrange) {
  1746. $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
  1747. }
  1748. $cmap[] = 0; // idRangeOffset of last Segment
  1749. foreach($range AS $subrange) {
  1750. foreach($subrange AS $glidx) {
  1751. $cmap[] = $glidx;
  1752. }
  1753. }
  1754. $cmap[] = 0; // Mapping for last character
  1755. $cmapstr4 = '';
  1756. foreach($cmap AS $cm) { $cmapstr4 .= pack("n",$cm); }
  1757. ///////////////////////////////////
  1758. // cmap - Character to glyph mapping
  1759. ///////////////////////////////////
  1760. $entryCount = count($subset);
  1761. $length = 10 + $entryCount * 2;
  1762. $off = 20 + $length;
  1763. $hoff = $off >> 16;
  1764. $loff = $off & 0xFFFF;
  1765. $cmap = array(0, 2, // Index : version, number of subtables
  1766. 1, 0, // Subtable : platform, encoding
  1767. 0, 20, // offset (hi,lo)
  1768. 3, 0, // Subtable : platform, encoding
  1769. $hoff, $loff, // offset (hi,lo)
  1770. 6, $length, // Format 6 Mapping table: format, length
  1771. 0, 1, // language, First char code
  1772. $entryCount
  1773. );
  1774. $cmapstr = '';
  1775. foreach($subset AS $code) { $cmap[] = $codeToGlyph[$code]; }
  1776. foreach($cmap AS $cm) { $cmapstr .= pack("n",$cm); }
  1777. $cmapstr .= $cmapstr4;
  1778. $this->add('cmap', $cmapstr);
  1779. ///////////////////////////////////
  1780. // hmtx - Horizontal Metrics
  1781. ///////////////////////////////////
  1782. $hmtxstr = '';
  1783. for($n=0;$n<$numGlyphs;$n++) {
  1784. $originalGlyphIdx = $glyphMap[$n];
  1785. $hm = $this->getHMetric($orignHmetrics, $originalGlyphIdx);
  1786. $hmtxstr .= $hm;
  1787. }
  1788. $this->add('hmtx', $hmtxstr);
  1789. ///////////////////////////////////
  1790. // glyf - Glyph data
  1791. ///////////////////////////////////
  1792. list($glyfOffset,$glyfLength) = $this->get_table_pos('glyf');
  1793. if ($glyfLength < $this->maxStrLenRead) {
  1794. $glyphData = $this->get_table('glyf');
  1795. }
  1796. $offsets = array();
  1797. $glyf = '';
  1798. $pos = 0;
  1799. for ($n=0;$n<$numGlyphs;$n++) {
  1800. $offsets[] = $pos;
  1801. $originalGlyphIdx = $glyphMap[$n];
  1802. $glyphPos = $this->glyphPos[$originalGlyphIdx];
  1803. $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
  1804. if ($glyfLength < $this->maxStrLenRead) {
  1805. $data = substr($glyphData,$glyphPos,$glyphLen);
  1806. }
  1807. else {
  1808. if ($glyphLen > 0) $data = $this->get_chunk($glyfOffset+$glyphPos,$glyphLen);
  1809. else $data = '';
  1810. }
  1811. if ($glyphLen > 0) $up = unpack("n", substr($data,0,2));
  1812. if ($glyphLen > 2 && ($up[1] & (1 << 15)) ) {
  1813. $pos_in_glyph = 10;
  1814. $flags = GF_MORE;
  1815. while ($flags & GF_MORE) {
  1816. $up = unpack("n", substr($data,$pos_in_glyph,2));
  1817. $flags = $up[1];
  1818. $up = unpack("n", substr($data,$pos_in_glyph+2,2));
  1819. $glyphIdx = $up[1];
  1820. $data = $this->_set_ushort($data, $pos_in_glyph + 2, $glyphSet[$glyphIdx]);
  1821. $pos_in_glyph += 4;
  1822. if ($flags & GF_WORDS) { $pos_in_glyph += 4; }
  1823. else { $pos_in_glyph += 2; }
  1824. if ($flags & GF_SCALE) { $pos_in_glyph += 2; }
  1825. else if ($flags & GF_XYSCALE) { $pos_in_glyph += 4; }
  1826. else if ($flags & GF_TWOBYTWO) { $pos_in_glyph += 8; }
  1827. }
  1828. }
  1829. $glyf .= $data;
  1830. $pos += $glyphLen;
  1831. if ($pos % 4 != 0) {
  1832. $padding = 4 - ($pos % 4);
  1833. $glyf .= str_repeat("\0",$padding);
  1834. $pos += $padding;
  1835. }
  1836. }
  1837. $offsets[] = $pos;
  1838. $this->add('glyf', $glyf);
  1839. ///////////////////////////////////
  1840. // loca - Index to location
  1841. ///////////////////////////////////
  1842. $locastr = '';
  1843. if ((($pos + 1) >> 1) > 0xFFFF) {
  1844. $indexToLocFormat = 1; // long format
  1845. foreach($offsets AS $offset) { $locastr .= pack("N",$offset); }
  1846. }
  1847. else {
  1848. $indexToLocFormat = 0; // short format
  1849. foreach($offsets AS $offset) { $locastr .= pack("n",($offset/2)); }
  1850. }
  1851. $this->add('loca', $locastr);
  1852. ///////////////////////////////////
  1853. // head - Font header
  1854. ///////////////////////////////////
  1855. $head = $this->get_table('head');
  1856. $head = $this->_set_ushort($head, 50, $indexToLocFormat);
  1857. $this->add('head', $head);
  1858. fclose($this->fh);
  1859. // Put the TTF file together
  1860. $stm = '';
  1861. $this->endTTFile($stm);
  1862. //file_put_contents('testfont.ttf', $stm); exit;
  1863. return $stm ;
  1864. }
  1865. //////////////////////////////////////////////////////////////////////////////////
  1866. // Recursively get composite glyph data
  1867. function getGlyphData($originalGlyphIdx, &$maxdepth, &$depth, &$points, &$contours) {
  1868. $depth++;
  1869. $maxdepth = max($maxdepth, $depth);
  1870. if (count($this->glyphdata[$originalGlyphIdx]['compGlyphs'])) {
  1871. foreach($this->glyphdata[$originalGlyphIdx]['compGlyphs'] AS $glyphIdx) {
  1872. $this->getGlyphData($glyphIdx, $maxdepth, $depth, $points, $contours);
  1873. }
  1874. }
  1875. else if (($this->glyphdata[$originalGlyphIdx]['nContours'] > 0) && $depth > 0) { // simple
  1876. $contours += $this->glyphdata[$originalGlyphIdx]['nContours'];
  1877. $points += $this->glyphdata[$originalGlyphIdx]['nPoints'];
  1878. }
  1879. $depth--;
  1880. }
  1881. //////////////////////////////////////////////////////////////////////////////////
  1882. // Recursively get composite glyphs
  1883. function getGlyphs($originalGlyphIdx, &$start, &$glyphSet, &$subsetglyphs) {
  1884. $glyphPos = $this->glyphPos[$originalGlyphIdx];
  1885. $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
  1886. if (!$glyphLen) {
  1887. return;
  1888. }
  1889. $this->seek($start + $glyphPos);
  1890. $numberOfContours = $this->read_short();
  1891. if ($numberOfContours < 0) {
  1892. $this->skip(8);
  1893. $flags = GF_MORE;
  1894. while ($flags & GF_MORE) {
  1895. $flags = $this->read_ushort();
  1896. $glyphIdx = $this->read_ushort();
  1897. if (!isset($glyphSet[$glyphIdx])) {
  1898. $glyphSet[$glyphIdx] = count($subsetglyphs); // old glyphID to new glyphID
  1899. $subsetglyphs[$glyphIdx] = true;
  1900. }
  1901. $savepos = ftell($this->fh);
  1902. $this->getGlyphs($glyphIdx, $start, $glyphSet, $subsetglyphs);
  1903. $this->seek($savepos);
  1904. if ($flags & GF_WORDS)
  1905. $this->skip(4);
  1906. else
  1907. $this->skip(2);
  1908. if ($flags & GF_SCALE)
  1909. $this->skip(2);
  1910. else if ($flags & GF_XYSCALE)
  1911. $this->skip(4);
  1912. else if ($flags & GF_TWOBYTWO)
  1913. $this->skip(8);
  1914. }
  1915. }
  1916. }
  1917. //////////////////////////////////////////////////////////////////////////////////
  1918. function getHMTX($numberOfHMetrics, $numGlyphs, &$glyphToChar, $scale) {
  1919. $start = $this->seek_table("hmtx");
  1920. $aw = 0;
  1921. $this->charWidths = str_pad('', 256*256*2, "\x00");
  1922. if ($this->maxUniChar > 65536) { $this->charWidths .= str_pad('', 256*256*2, "\x00"); } // Plane 1 SMP
  1923. if ($this->maxUniChar > 131072) { $this->charWidths .= str_pad('', 256*256*2, "\x00"); } // Plane 2 SMP
  1924. $nCharWidths = 0;
  1925. if (($numberOfHMetrics*4) < $this->maxStrLenRead) {
  1926. $data = $this->get_chunk($start,($numberOfHMetrics*4));
  1927. $arr = unpack("n*", $data);
  1928. }
  1929. else { $this->seek($start); }
  1930. for( $glyph=0; $glyph<$numberOfHMetrics; $glyph++) {
  1931. if (($numberOfHMetrics*4) < $this->maxStrLenRead) {
  1932. $aw = $arr[($glyph*2)+1];
  1933. }
  1934. else {
  1935. $aw = $this->read_ushort();
  1936. $lsb = $this->read_ushort();
  1937. }
  1938. if (isset($glyphToChar[$glyph]) || $glyph == 0) {
  1939. if ($aw >= (1 << 15) ) { $aw = 0; } // 1.03 Some (arabic) fonts have -ve values for width
  1940. // although should be unsigned value - comes out as e.g. 65108 (intended -50)
  1941. if ($glyph == 0) {
  1942. $this->defaultWidth = $scale*$aw;
  1943. continue;
  1944. }
  1945. foreach($glyphToChar[$glyph] AS $char) {
  1946. //$this->charWidths[$char] = intval(round($scale*$aw));
  1947. if ($char != 0 && $char != 65535) {
  1948. $w = intval(round($scale*$aw));
  1949. if ($w == 0) { $w = 65535; }
  1950. if ($char < 196608) {
  1951. $this->charWidths[$char*2] = chr($w >> 8);
  1952. $this->charWidths[$char*2 + 1] = chr($w & 0xFF);
  1953. $nCharWidths++;
  1954. }
  1955. }
  1956. }
  1957. }
  1958. }
  1959. $data = $this->get_chunk(($start+$numberOfHMetrics*4),($numGlyphs*2));
  1960. $arr = unpack("n*", $data);
  1961. $diff = $numGlyphs-$numberOfHMetrics;
  1962. $w = intval(round($scale*$aw)); // mPDF 5.3.32
  1963. if ($w == 0) { $w = 65535; } // mPDF 5.3.32
  1964. for( $pos=0; $pos<$diff; $pos++) {
  1965. $glyph = $pos + $numberOfHMetrics;
  1966. if (isset($glyphToChar[$glyph])) {
  1967. foreach($glyphToChar[$glyph] AS $char) {
  1968. if ($char != 0 && $char != 65535) {
  1969. if ($char < 196608) {
  1970. $this->charWidths[$char*2] = chr($w >> 8);
  1971. $this->charWidths[$char*2 + 1] = chr($w & 0xFF);
  1972. $nCharWidths++;
  1973. }
  1974. }
  1975. }
  1976. }
  1977. }
  1978. // NB 65535 is a set width of 0
  1979. // First bytes define number of chars in font
  1980. $this->charWidths[0] = chr($nCharWidths >> 8);
  1981. $this->charWidths[1] = chr($nCharWidths & 0xFF);
  1982. }
  1983. function getHMetric($numberOfHMetrics, $gid) {
  1984. $start = $this->seek_table("hmtx");
  1985. if ($gid < $numberOfHMetrics) {
  1986. $this->seek($start+($gid*4));
  1987. $hm = fread($this->fh,4);
  1988. }
  1989. else {
  1990. $this->seek($start+(($numberOfHMetrics-1)*4));
  1991. $hm = fread($this->fh,2);
  1992. $this->seek($start+($numberOfHMetrics*2)+($gid*2));
  1993. $hm .= fread($this->fh,2);
  1994. }
  1995. return $hm;
  1996. }
  1997. function getLOCA($indexToLocFormat, $numGlyphs) {
  1998. $start = $this->seek_table('loca');
  1999. $this->glyphPos = array();
  2000. if ($indexToLocFormat == 0) {
  2001. $data = $this->get_chunk($start,($numGlyphs*2)+2);
  2002. $arr = unpack("n*", $data);
  2003. for ($n=0; $n<=$numGlyphs; $n++) {
  2004. $this->glyphPos[] = ($arr[$n+1] * 2);
  2005. }
  2006. }
  2007. else if ($indexToLocFormat == 1) {
  2008. $data = $this->get_chunk($start,($numGlyphs*4)+4);
  2009. $arr = unpack("N*", $data);
  2010. for ($n=0; $n<=$numGlyphs; $n++) {
  2011. $this->glyphPos[] = ($arr[$n+1]);
  2012. }
  2013. }
  2014. else
  2015. die('Unknown location table format '.$indexToLocFormat);
  2016. }
  2017. // CMAP Format 4
  2018. function getCMAP4($unicode_cmap_offset, &$glyphToChar, &$charToGlyph ) {
  2019. $this->maxUniChar = 0;
  2020. $this->seek($unicode_cmap_offset + 2);
  2021. $length = $this->read_ushort();
  2022. $limit = $unicode_cmap_offset + $length;
  2023. $this->skip(2);
  2024. $segCount = $this->read_ushort() / 2;
  2025. $this->skip(6);
  2026. $endCount = array();
  2027. for($i=0; $i<$segCount; $i++) { $endCount[] = $this->read_ushort(); }
  2028. $this->skip(2);
  2029. $startCount = array();
  2030. for($i=0; $i<$segCount; $i++) { $startCount[] = $this->read_ushort(); }
  2031. $idDelta = array();
  2032. for($i=0; $i<$segCount; $i++) { $idDelta[] = $this->read_short(); } // ???? was unsigned short
  2033. $idRangeOffset_start = $this->_pos;
  2034. $idRangeOffset = array();
  2035. for($i=0; $i<$segCount; $i++) { $idRangeOffset[] = $this->read_ushort(); }
  2036. for ($n=0;$n<$segCount;$n++) {
  2037. $endpoint = ($endCount[$n] + 1);
  2038. for ($unichar=$startCount[$n];$unichar<$endpoint;$unichar++) {
  2039. if ($idRangeOffset[$n] == 0)
  2040. $glyph = ($unichar + $idDelta[$n]) & 0xFFFF;
  2041. else {
  2042. $offset = ($unichar - $startCount[$n]) * 2 + $idRangeOffset[$n];
  2043. $offset = $idRangeOffset_start + 2 * $n + $offset;
  2044. if ($offset >= $limit)
  2045. $glyph = 0;
  2046. else {
  2047. $glyph = $this->get_ushort($offset);
  2048. if ($glyph != 0)
  2049. $glyph = ($glyph + $idDelta[$n]) & 0xFFFF;
  2050. }
  2051. }
  2052. $charToGlyph[$unichar] = $glyph;
  2053. if ($unichar < 196608) { $this->maxUniChar = max($unichar,$this->maxUniChar); }
  2054. $glyphToChar[$glyph][] = $unichar;
  2055. }
  2056. }
  2057. }
  2058. // Put the TTF file together
  2059. function endTTFile(&$stm) {
  2060. $stm = '';
  2061. $numTables = count($this->otables);
  2062. $searchRange = 1;
  2063. $entrySelector = 0;
  2064. while ($searchRange * 2 <= $numTables) {
  2065. $searchRange = $searchRange * 2;
  2066. $entrySelector = $entrySelector + 1;
  2067. }
  2068. $searchRange = $searchRange * 16;
  2069. $rangeShift = $numTables * 16 - $searchRange;
  2070. // Header
  2071. if (_TTF_MAC_HEADER) {
  2072. $stm .= (pack("Nnnnn", 0x74727565, $numTables, $searchRange, $entrySelector, $rangeShift)); // Mac
  2073. }
  2074. else {
  2075. $stm .= (pack("Nnnnn", 0x00010000 , $numTables, $searchRange, $entrySelector, $rangeShift)); // Windows
  2076. }
  2077. // Table directory
  2078. $tables = $this->otables;
  2079. ksort ($tables);
  2080. $offset = 12 + $numTables * 16;
  2081. foreach ($tables AS $tag=>$data) {
  2082. if ($tag == 'head') { $head_start = $offset; }
  2083. $stm .= $tag;
  2084. $checksum = $this->calcChecksum($data);
  2085. $stm .= pack("nn", $checksum[0],$checksum[1]);
  2086. $stm .= pack("NN", $offset, strlen($data));
  2087. $paddedLength = (strlen($data)+3)&~3;
  2088. $offset = $offset + $paddedLength;
  2089. }
  2090. // Table data
  2091. foreach ($tables AS $tag=>$data) {
  2092. $data .= "\0\0\0";
  2093. $stm .= substr($data,0,(strlen($data)&~3));
  2094. }
  2095. $checksum = $this->calcChecksum($stm);
  2096. $checksum = $this->sub32(array(0xB1B0,0xAFBA), $checksum);
  2097. $chk = pack("nn", $checksum[0],$checksum[1]);
  2098. $stm = $this->splice($stm,($head_start + 8),$chk);
  2099. return $stm ;
  2100. }
  2101. function repackageTTF($file, $TTCfontID=0, $debug=false) {
  2102. $this->filename = $file;
  2103. $this->fh = fopen($file ,'rb') or die('Can\'t open file ' . $file);
  2104. $this->_pos = 0;
  2105. $this->charWidths = '';
  2106. $this->glyphPos = array();
  2107. $this->charToGlyph = array();
  2108. $this->tables = array();
  2109. $this->otables = array();
  2110. $this->ascent = 0;
  2111. $this->descent = 0;
  2112. $this->numTTCFonts = 0;
  2113. $this->TTCFonts = array();
  2114. $this->skip(4);
  2115. $this->maxUni = 0;
  2116. if ($TTCfontID > 0) {
  2117. $this->version = $version = $this->read_ulong(); // TTC Header version now
  2118. if (!in_array($version, array(0x00010000,0x00020000)))
  2119. die("ERROR - Error parsing TrueType Collection: version=".$version." - " . $file);
  2120. $this->numTTCFonts = $this->read_ulong();
  2121. for ($i=1; $i<=$this->numTTCFonts; $i++) {
  2122. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  2123. }
  2124. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  2125. $this->version = $version = $this->read_ulong(); // TTFont version again now
  2126. }
  2127. $this->readTableDirectory($debug);
  2128. $tags = array ('OS/2', 'cmap', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'name', 'post', 'cvt ', 'fpgm', 'gasp', 'prep');
  2129. /*
  2130. Tables which require glyphIndex
  2131. hdmx
  2132. kern
  2133. LTSH
  2134. Tables which do NOT require glyphIndex
  2135. VDMX
  2136. GDEF
  2137. GPOS
  2138. GSUB
  2139. JSTF
  2140. DSIG
  2141. PCLT - not recommended
  2142. */
  2143. foreach($tags AS $tag) {
  2144. if (isset($this->tables[$tag])) { $this->add($tag, $this->get_table($tag)); }
  2145. }
  2146. fclose($this->fh);
  2147. $stm = '';
  2148. $this->endTTFile($stm);
  2149. return $stm ;
  2150. }
  2151. }
  2152. ?>