tcpdf_fonts.php 93 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582
  1. <?php
  2. //============================================================+
  3. // File name : tcpdf_fonts.php
  4. // Version : 1.0.010
  5. // Begin : 2008-01-01
  6. // Last Update : 2013-11-10
  7. // Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
  8. // License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
  9. // -------------------------------------------------------------------
  10. // Copyright (C) 2008-2013 Nicola Asuni - Tecnick.com LTD
  11. //
  12. // This file is part of TCPDF software library.
  13. //
  14. // TCPDF is free software: you can redistribute it and/or modify it
  15. // under the terms of the GNU Lesser General Public License as
  16. // published by the Free Software Foundation, either version 3 of the
  17. // License, or (at your option) any later version.
  18. //
  19. // TCPDF is distributed in the hope that it will be useful, but
  20. // WITHOUT ANY WARRANTY; without even the implied warranty of
  21. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  22. // See the GNU Lesser General Public License for more details.
  23. //
  24. // You should have received a copy of the GNU Lesser General Public License
  25. // along with TCPDF. If not, see <http://www.gnu.org/licenses/>.
  26. //
  27. // See LICENSE.TXT file for more information.
  28. // -------------------------------------------------------------------
  29. //
  30. // Description :Font methods for TCPDF library.
  31. //
  32. //============================================================+
  33. /**
  34. * @file
  35. * Unicode data and font methods for TCPDF library.
  36. * @author Nicola Asuni
  37. * @package com.tecnick.tcpdf
  38. */
  39. /**
  40. * @class TCPDF_FONTS
  41. * Font methods for TCPDF library.
  42. * @package com.tecnick.tcpdf
  43. * @version 1.0.010
  44. * @author Nicola Asuni - info@tecnick.com
  45. */
  46. class TCPDF_FONTS {
  47. /**
  48. * Static cache used for speed up uniord performances
  49. * @protected
  50. */
  51. protected static $cache_uniord = array();
  52. /**
  53. * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
  54. * @param $fontfile (string) Font file (full path).
  55. * @param $fonttype (string) Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional.
  56. * @param $enc (string) Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats.
  57. * @param $flags (int) Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font.
  58. * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
  59. * @param $platid (int) Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1).
  60. * @param $encid (int) Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4.
  61. * @param $addcbbox (boolean) If true includes the character bounding box information on the php font file.
  62. * @param $link (boolean) If true link to system font instead of copying the font data (not transportable) - Note: do not work with Type1 fonts.
  63. * @return (string) TCPDF font name or boolean false in case of error.
  64. * @author Nicola Asuni
  65. * @since 5.9.123 (2010-09-30)
  66. * @public static
  67. */
  68. public static function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false, $link=false) {
  69. if (!file_exists($fontfile)) {
  70. // Could not find file
  71. return false;
  72. }
  73. // font metrics
  74. $fmetric = array();
  75. // build new font name for TCPDF compatibility
  76. $font_path_parts = pathinfo($fontfile);
  77. if (!isset($font_path_parts['filename'])) {
  78. $font_path_parts['filename'] = substr($font_path_parts['basename'], 0, -(strlen($font_path_parts['extension']) + 1));
  79. }
  80. $font_name = strtolower($font_path_parts['filename']);
  81. $font_name = preg_replace('/[^a-z0-9_]/', '', $font_name);
  82. $search = array('bold', 'oblique', 'italic', 'regular');
  83. $replace = array('b', 'i', 'i', '');
  84. $font_name = str_replace($search, $replace, $font_name);
  85. if (empty($font_name)) {
  86. // set generic name
  87. $font_name = 'tcpdffont';
  88. }
  89. // set output path
  90. if (empty($outpath)) {
  91. $outpath = self::_getfontpath();
  92. }
  93. // check if this font already exist
  94. if (@file_exists($outpath.$font_name.'.php')) {
  95. // this font already exist (delete it from fonts folder to rebuild it)
  96. return $font_name;
  97. }
  98. $fmetric['file'] = $font_name;
  99. $fmetric['ctg'] = $font_name.'.ctg.z';
  100. // get font data
  101. $font = file_get_contents($fontfile);
  102. $fmetric['originalsize'] = strlen($font);
  103. // autodetect font type
  104. if (empty($fonttype)) {
  105. if (TCPDF_STATIC::_getULONG($font, 0) == 0x10000) {
  106. // True Type (Unicode or not)
  107. $fonttype = 'TrueTypeUnicode';
  108. } elseif (substr($font, 0, 4) == 'OTTO') {
  109. // Open Type (Unicode or not)
  110. //Unsupported font format: OpenType with CFF data
  111. return false;
  112. } else {
  113. // Type 1
  114. $fonttype = 'Type1';
  115. }
  116. }
  117. // set font type
  118. switch ($fonttype) {
  119. case 'CID0CT':
  120. case 'CID0CS':
  121. case 'CID0KR':
  122. case 'CID0JP': {
  123. $fmetric['type'] = 'cidfont0';
  124. break;
  125. }
  126. case 'Type1': {
  127. $fmetric['type'] = 'Type1';
  128. if (empty($enc) AND (($flags & 4) == 0)) {
  129. $enc = 'cp1252';
  130. }
  131. break;
  132. }
  133. case 'TrueType': {
  134. $fmetric['type'] = 'TrueType';
  135. break;
  136. }
  137. case 'TrueTypeUnicode':
  138. default: {
  139. $fmetric['type'] = 'TrueTypeUnicode';
  140. break;
  141. }
  142. }
  143. // set encoding maps (if any)
  144. $fmetric['enc'] = preg_replace('/[^A-Za-z0-9_\-]/', '', $enc);
  145. $fmetric['diff'] = '';
  146. if (($fmetric['type'] == 'TrueType') OR ($fmetric['type'] == 'Type1')) {
  147. if (!empty($enc) AND ($enc != 'cp1252') AND isset(TCPDF_FONT_DATA::$encmap[$enc])) {
  148. // build differences from reference encoding
  149. $enc_ref = TCPDF_FONT_DATA::$encmap['cp1252'];
  150. $enc_target = TCPDF_FONT_DATA::$encmap[$enc];
  151. $last = 0;
  152. for ($i = 32; $i <= 255; ++$i) {
  153. if ($enc_target != $enc_ref[$i]) {
  154. if ($i != ($last + 1)) {
  155. $fmetric['diff'] .= $i.' ';
  156. }
  157. $last = $i;
  158. $fmetric['diff'] .= '/'.$enc_target[$i].' ';
  159. }
  160. }
  161. }
  162. }
  163. // parse the font by type
  164. if ($fmetric['type'] == 'Type1') {
  165. // ---------- TYPE 1 ----------
  166. // read first segment
  167. $a = unpack('Cmarker/Ctype/Vsize', substr($font, 0, 6));
  168. if ($a['marker'] != 128) {
  169. // Font file is not a valid binary Type1
  170. return false;
  171. }
  172. $fmetric['size1'] = $a['size'];
  173. $data = substr($font, 6, $fmetric['size1']);
  174. // read second segment
  175. $a = unpack('Cmarker/Ctype/Vsize', substr($font, (6 + $fmetric['size1']), 6));
  176. if ($a['marker'] != 128) {
  177. // Font file is not a valid binary Type1
  178. return false;
  179. }
  180. $fmetric['size2'] = $a['size'];
  181. $encrypted = substr($font, (12 + $fmetric['size1']), $fmetric['size2']);
  182. $data .= $encrypted;
  183. // store compressed font
  184. $fmetric['file'] .= '.z';
  185. $fp = fopen($outpath.$fmetric['file'], 'wb');
  186. fwrite($fp, gzcompress($data));
  187. fclose($fp);
  188. // get font info
  189. $fmetric['Flags'] = $flags;
  190. preg_match ('#/FullName[\s]*\(([^\)]*)#', $font, $matches);
  191. $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $matches[1]);
  192. preg_match('#/FontBBox[\s]*{([^}]*)#', $font, $matches);
  193. $fmetric['bbox'] = trim($matches[1]);
  194. $bv = explode(' ', $fmetric['bbox']);
  195. $fmetric['Ascent'] = intval($bv[3]);
  196. $fmetric['Descent'] = intval($bv[1]);
  197. preg_match('#/ItalicAngle[\s]*([0-9\+\-]*)#', $font, $matches);
  198. $fmetric['italicAngle'] = intval($matches[1]);
  199. if ($fmetric['italicAngle'] != 0) {
  200. $fmetric['Flags'] |= 64;
  201. }
  202. preg_match('#/UnderlinePosition[\s]*([0-9\+\-]*)#', $font, $matches);
  203. $fmetric['underlinePosition'] = intval($matches[1]);
  204. preg_match('#/UnderlineThickness[\s]*([0-9\+\-]*)#', $font, $matches);
  205. $fmetric['underlineThickness'] = intval($matches[1]);
  206. preg_match('#/isFixedPitch[\s]*([^\s]*)#', $font, $matches);
  207. if ($matches[1] == 'true') {
  208. $fmetric['Flags'] |= 1;
  209. }
  210. // get internal map
  211. $imap = array();
  212. if (preg_match_all('#dup[\s]([0-9]+)[\s]*/([^\s]*)[\s]put#sU', $font, $fmap, PREG_SET_ORDER) > 0) {
  213. foreach ($fmap as $v) {
  214. $imap[$v[2]] = $v[1];
  215. }
  216. }
  217. // decrypt eexec encrypted part
  218. $r = 55665; // eexec encryption constant
  219. $c1 = 52845;
  220. $c2 = 22719;
  221. $elen = strlen($encrypted);
  222. $eplain = '';
  223. for ($i = 0; $i < $elen; ++$i) {
  224. $chr = ord($encrypted[$i]);
  225. $eplain .= chr($chr ^ ($r >> 8));
  226. $r = ((($chr + $r) * $c1 + $c2) % 65536);
  227. }
  228. if (preg_match('#/ForceBold[\s]*([^\s]*)#', $eplain, $matches) > 0) {
  229. if ($matches[1] == 'true') {
  230. $fmetric['Flags'] |= 0x40000;
  231. }
  232. }
  233. if (preg_match('#/StdVW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
  234. $fmetric['StemV'] = intval($matches[1]);
  235. } else {
  236. $fmetric['StemV'] = 70;
  237. }
  238. if (preg_match('#/StdHW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
  239. $fmetric['StemH'] = intval($matches[1]);
  240. } else {
  241. $fmetric['StemH'] = 30;
  242. }
  243. if (preg_match('#/BlueValues[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
  244. $bv = explode(' ', $matches[1]);
  245. if (count($bv) >= 6) {
  246. $v1 = intval($bv[2]);
  247. $v2 = intval($bv[4]);
  248. if ($v1 <= $v2) {
  249. $fmetric['XHeight'] = $v1;
  250. $fmetric['CapHeight'] = $v2;
  251. } else {
  252. $fmetric['XHeight'] = $v2;
  253. $fmetric['CapHeight'] = $v1;
  254. }
  255. } else {
  256. $fmetric['XHeight'] = 450;
  257. $fmetric['CapHeight'] = 700;
  258. }
  259. } else {
  260. $fmetric['XHeight'] = 450;
  261. $fmetric['CapHeight'] = 700;
  262. }
  263. // get the number of random bytes at the beginning of charstrings
  264. if (preg_match('#/lenIV[\s]*([0-9]*)#', $eplain, $matches) > 0) {
  265. $lenIV = intval($matches[1]);
  266. } else {
  267. $lenIV = 4;
  268. }
  269. $fmetric['Leading'] = 0;
  270. // get charstring data
  271. $eplain = substr($eplain, (strpos($eplain, '/CharStrings') + 1));
  272. preg_match_all('#/([A-Za-z0-9\.]*)[\s][0-9]+[\s]RD[\s](.*)[\s]ND#sU', $eplain, $matches, PREG_SET_ORDER);
  273. if (!empty($enc) AND isset(TCPDF_FONT_DATA::$encmap[$enc])) {
  274. $enc_map = TCPDF_FONT_DATA::$encmap[$enc];
  275. } else {
  276. $enc_map = false;
  277. }
  278. $fmetric['cw'] = '';
  279. $fmetric['MaxWidth'] = 0;
  280. $cwidths = array();
  281. foreach ($matches as $k => $v) {
  282. $cid = 0;
  283. if (isset($imap[$v[1]])) {
  284. $cid = $imap[$v[1]];
  285. } elseif ($enc_map !== false) {
  286. $cid = array_search($v[1], $enc_map);
  287. if ($cid === false) {
  288. $cid = 0;
  289. } elseif ($cid > 1000) {
  290. $cid -= 1000;
  291. }
  292. }
  293. // decrypt charstring encrypted part
  294. $r = 4330; // charstring encryption constant
  295. $c1 = 52845;
  296. $c2 = 22719;
  297. $cd = $v[2];
  298. $clen = strlen($cd);
  299. $ccom = array();
  300. for ($i = 0; $i < $clen; ++$i) {
  301. $chr = ord($cd[$i]);
  302. $ccom[] = ($chr ^ ($r >> 8));
  303. $r = ((($chr + $r) * $c1 + $c2) % 65536);
  304. }
  305. // decode numbers
  306. $cdec = array();
  307. $ck = 0;
  308. $i = $lenIV;
  309. while ($i < $clen) {
  310. if ($ccom[$i] < 32) {
  311. $cdec[$ck] = $ccom[$i];
  312. if (($ck > 0) AND ($cdec[$ck] == 13)) {
  313. // hsbw command: update width
  314. $cwidths[$cid] = $cdec[($ck - 1)];
  315. }
  316. ++$i;
  317. } elseif (($ccom[$i] >= 32) AND ($ccom[$i] <= 246)) {
  318. $cdec[$ck] = ($ccom[$i] - 139);
  319. ++$i;
  320. } elseif (($ccom[$i] >= 247) AND ($ccom[$i] <= 250)) {
  321. $cdec[$ck] = ((($ccom[$i] - 247) * 256) + $ccom[($i + 1)] + 108);
  322. $i += 2;
  323. } elseif (($ccom[$i] >= 251) AND ($ccom[$i] <= 254)) {
  324. $cdec[$ck] = ((-($ccom[$i] - 251) * 256) - $ccom[($i + 1)] - 108);
  325. $i += 2;
  326. } elseif ($ccom[$i] == 255) {
  327. $sval = chr($ccom[($i + 1)]).chr($ccom[($i + 2)]).chr($ccom[($i + 3)]).chr($ccom[($i + 4)]);
  328. $vsval = unpack('li', $sval);
  329. $cdec[$ck] = $vsval['i'];
  330. $i += 5;
  331. }
  332. ++$ck;
  333. }
  334. } // end for each matches
  335. $fmetric['MissingWidth'] = $cwidths[0];
  336. $fmetric['MaxWidth'] = $fmetric['MissingWidth'];
  337. $fmetric['AvgWidth'] = 0;
  338. // set chars widths
  339. for ($cid = 0; $cid <= 255; ++$cid) {
  340. if (isset($cwidths[$cid])) {
  341. if ($cwidths[$cid] > $fmetric['MaxWidth']) {
  342. $fmetric['MaxWidth'] = $cwidths[$cid];
  343. }
  344. $fmetric['AvgWidth'] += $cwidths[$cid];
  345. $fmetric['cw'] .= ','.$cid.'=>'.$cwidths[$cid];
  346. } else {
  347. $fmetric['cw'] .= ','.$cid.'=>'.$fmetric['MissingWidth'];
  348. }
  349. }
  350. $fmetric['AvgWidth'] = round($fmetric['AvgWidth'] / count($cwidths));
  351. } else {
  352. // ---------- TRUE TYPE ----------
  353. if ($fmetric['type'] != 'cidfont0') {
  354. if ($link) {
  355. // creates a symbolic link to the existing font
  356. symlink($fontfile, $outpath.$fmetric['file']);
  357. } else {
  358. // store compressed font
  359. $fmetric['file'] .= '.z';
  360. $fp = fopen($outpath.$fmetric['file'], 'wb');
  361. fwrite($fp, gzcompress($font));
  362. fclose($fp);
  363. }
  364. }
  365. $offset = 0; // offset position of the font data
  366. if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
  367. // sfnt version must be 0x00010000 for TrueType version 1.0.
  368. return false;
  369. }
  370. $offset += 4;
  371. // get number of tables
  372. $numTables = TCPDF_STATIC::_getUSHORT($font, $offset);
  373. $offset += 2;
  374. // skip searchRange, entrySelector and rangeShift
  375. $offset += 6;
  376. // tables array
  377. $table = array();
  378. // ---------- get tables ----------
  379. for ($i = 0; $i < $numTables; ++$i) {
  380. // get table info
  381. $tag = substr($font, $offset, 4);
  382. $offset += 4;
  383. $table[$tag] = array();
  384. $table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset);
  385. $offset += 4;
  386. $table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
  387. $offset += 4;
  388. $table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset);
  389. $offset += 4;
  390. }
  391. // check magicNumber
  392. $offset = $table['head']['offset'] + 12;
  393. if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) {
  394. // magicNumber must be 0x5F0F3CF5
  395. return false;
  396. }
  397. $offset += 4;
  398. $offset += 2; // skip flags
  399. // get FUnits
  400. $fmetric['unitsPerEm'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  401. $offset += 2;
  402. // units ratio constant
  403. $urk = (1000 / $fmetric['unitsPerEm']);
  404. $offset += 16; // skip created, modified
  405. $xMin = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
  406. $offset += 2;
  407. $yMin = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
  408. $offset += 2;
  409. $xMax = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
  410. $offset += 2;
  411. $yMax = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
  412. $offset += 2;
  413. $fmetric['bbox'] = ''.$xMin.' '.$yMin.' '.$xMax.' '.$yMax.'';
  414. $macStyle = TCPDF_STATIC::_getUSHORT($font, $offset);
  415. $offset += 2;
  416. // PDF font flags
  417. $fmetric['Flags'] = $flags;
  418. if (($macStyle & 2) == 2) {
  419. // italic flag
  420. $fmetric['Flags'] |= 64;
  421. }
  422. // get offset mode (indexToLocFormat : 0 = short, 1 = long)
  423. $offset = $table['head']['offset'] + 50;
  424. $short_offset = (TCPDF_STATIC::_getSHORT($font, $offset) == 0);
  425. $offset += 2;
  426. // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
  427. $indexToLoc = array();
  428. $offset = $table['loca']['offset'];
  429. if ($short_offset) {
  430. // short version
  431. $tot_num_glyphs = floor($table['loca']['length'] / 2); // numGlyphs + 1
  432. for ($i = 0; $i < $tot_num_glyphs; ++$i) {
  433. $indexToLoc[$i] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
  434. $offset += 2;
  435. }
  436. } else {
  437. // long version
  438. $tot_num_glyphs = floor($table['loca']['length'] / 4); // numGlyphs + 1
  439. for ($i = 0; $i < $tot_num_glyphs; ++$i) {
  440. $indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset);
  441. $offset += 4;
  442. }
  443. }
  444. // get glyphs indexes of chars from cmap table
  445. $offset = $table['cmap']['offset'] + 2;
  446. $numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
  447. $offset += 2;
  448. $encodingTables = array();
  449. for ($i = 0; $i < $numEncodingTables; ++$i) {
  450. $encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  451. $offset += 2;
  452. $encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  453. $offset += 2;
  454. $encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
  455. $offset += 4;
  456. }
  457. // ---------- get os/2 metrics ----------
  458. $offset = $table['OS/2']['offset'];
  459. $offset += 2; // skip version
  460. // xAvgCharWidth
  461. $fmetric['AvgWidth'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
  462. $offset += 2;
  463. // usWeightClass
  464. $usWeightClass = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
  465. // estimate StemV and StemH (400 = usWeightClass for Normal - Regular font)
  466. $fmetric['StemV'] = round((70 * $usWeightClass) / 400);
  467. $fmetric['StemH'] = round((30 * $usWeightClass) / 400);
  468. $offset += 2;
  469. $offset += 2; // usWidthClass
  470. $fsType = TCPDF_STATIC::_getSHORT($font, $offset);
  471. $offset += 2;
  472. if ($fsType == 2) {
  473. // This Font cannot be modified, embedded or exchanged in any manner without first obtaining permission of the legal owner.
  474. return false;
  475. }
  476. // ---------- get font name ----------
  477. $fmetric['name'] = '';
  478. $offset = $table['name']['offset'];
  479. $offset += 2; // skip Format selector (=0).
  480. // Number of NameRecords that follow n.
  481. $numNameRecords = TCPDF_STATIC::_getUSHORT($font, $offset);
  482. $offset += 2;
  483. // Offset to start of string storage (from start of table).
  484. $stringStorageOffset = TCPDF_STATIC::_getUSHORT($font, $offset);
  485. $offset += 2;
  486. for ($i = 0; $i < $numNameRecords; ++$i) {
  487. $offset += 6; // skip Platform ID, Platform-specific encoding ID, Language ID.
  488. // Name ID.
  489. $nameID = TCPDF_STATIC::_getUSHORT($font, $offset);
  490. $offset += 2;
  491. if ($nameID == 6) {
  492. // String length (in bytes).
  493. $stringLength = TCPDF_STATIC::_getUSHORT($font, $offset);
  494. $offset += 2;
  495. // String offset from start of storage area (in bytes).
  496. $stringOffset = TCPDF_STATIC::_getUSHORT($font, $offset);
  497. $offset += 2;
  498. $offset = ($table['name']['offset'] + $stringStorageOffset + $stringOffset);
  499. $fmetric['name'] = substr($font, $offset, $stringLength);
  500. $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $fmetric['name']);
  501. break;
  502. } else {
  503. $offset += 4; // skip String length, String offset
  504. }
  505. }
  506. if (empty($fmetric['name'])) {
  507. $fmetric['name'] = $font_name;
  508. }
  509. // ---------- get post data ----------
  510. $offset = $table['post']['offset'];
  511. $offset += 4; // skip Format Type
  512. $fmetric['italicAngle'] = TCPDF_STATIC::_getFIXED($font, $offset);
  513. $offset += 4;
  514. $fmetric['underlinePosition'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
  515. $offset += 2;
  516. $fmetric['underlineThickness'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
  517. $offset += 2;
  518. $isFixedPitch = (TCPDF_STATIC::_getULONG($font, $offset) == 0) ? false : true;
  519. $offset += 2;
  520. if ($isFixedPitch) {
  521. $fmetric['Flags'] |= 1;
  522. }
  523. // ---------- get hhea data ----------
  524. $offset = $table['hhea']['offset'];
  525. $offset += 4; // skip Table version number
  526. // Ascender
  527. $fmetric['Ascent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
  528. $offset += 2;
  529. // Descender
  530. $fmetric['Descent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
  531. $offset += 2;
  532. // LineGap
  533. $fmetric['Leading'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
  534. $offset += 2;
  535. // advanceWidthMax
  536. $fmetric['MaxWidth'] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
  537. $offset += 2;
  538. $offset += 22; // skip some values
  539. // get the number of hMetric entries in hmtx table
  540. $numberOfHMetrics = TCPDF_STATIC::_getUSHORT($font, $offset);
  541. // ---------- get maxp data ----------
  542. $offset = $table['maxp']['offset'];
  543. $offset += 4; // skip Table version number
  544. // get the the number of glyphs in the font.
  545. $numGlyphs = TCPDF_STATIC::_getUSHORT($font, $offset);
  546. // ---------- get CIDToGIDMap ----------
  547. $ctg = array();
  548. foreach ($encodingTables as $enctable) {
  549. // get only specified Platform ID and Encoding ID
  550. if (($enctable['platformID'] == $platid) AND ($enctable['encodingID'] == $encid)) {
  551. $offset = $table['cmap']['offset'] + $enctable['offset'];
  552. $format = TCPDF_STATIC::_getUSHORT($font, $offset);
  553. $offset += 2;
  554. switch ($format) {
  555. case 0: { // Format 0: Byte encoding table
  556. $offset += 4; // skip length and version/language
  557. for ($c = 0; $c < 256; ++$c) {
  558. $g = TCPDF_STATIC::_getBYTE($font, $offset);
  559. $ctg[$c] = $g;
  560. ++$offset;
  561. }
  562. break;
  563. }
  564. case 2: { // Format 2: High-byte mapping through table
  565. $offset += 4; // skip length and version/language
  566. $numSubHeaders = 0;
  567. for ($i = 0; $i < 256; ++$i) {
  568. // Array that maps high bytes to subHeaders: value is subHeader index * 8.
  569. $subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8);
  570. $offset += 2;
  571. if ($numSubHeaders < $subHeaderKeys[$i]) {
  572. $numSubHeaders = $subHeaderKeys[$i];
  573. }
  574. }
  575. // the number of subHeaders is equal to the max of subHeaderKeys + 1
  576. ++$numSubHeaders;
  577. // read subHeader structures
  578. $subHeaders = array();
  579. $numGlyphIndexArray = 0;
  580. for ($k = 0; $k < $numSubHeaders; ++$k) {
  581. $subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  582. $offset += 2;
  583. $subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  584. $offset += 2;
  585. $subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  586. $offset += 2;
  587. $subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  588. $offset += 2;
  589. $subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
  590. $subHeaders[$k]['idRangeOffset'] /= 2;
  591. $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
  592. }
  593. for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
  594. $glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
  595. $offset += 2;
  596. }
  597. for ($i = 0; $i < 256; ++$i) {
  598. $k = $subHeaderKeys[$i];
  599. if ($k == 0) {
  600. // one byte code
  601. $c = $i;
  602. $g = $glyphIndexArray[0];
  603. $ctg[$c] = $g;
  604. } else {
  605. // two bytes code
  606. $start_byte = $subHeaders[$k]['firstCode'];
  607. $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
  608. for ($j = $start_byte; $j < $end_byte; ++$j) {
  609. // combine high and low bytes
  610. $c = (($i << 8) + $j);
  611. $idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
  612. $g = ($glyphIndexArray[$idRangeOffset] + $subHeaders[$k]['idDelta']) % 65536;
  613. if ($g < 0) {
  614. $g = 0;
  615. }
  616. $ctg[$c] = $g;
  617. }
  618. }
  619. }
  620. break;
  621. }
  622. case 4: { // Format 4: Segment mapping to delta values
  623. $length = TCPDF_STATIC::_getUSHORT($font, $offset);
  624. $offset += 2;
  625. $offset += 2; // skip version/language
  626. $segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
  627. $offset += 2;
  628. $offset += 6; // skip searchRange, entrySelector, rangeShift
  629. $endCount = array(); // array of end character codes for each segment
  630. for ($k = 0; $k < $segCount; ++$k) {
  631. $endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
  632. $offset += 2;
  633. }
  634. $offset += 2; // skip reservedPad
  635. $startCount = array(); // array of start character codes for each segment
  636. for ($k = 0; $k < $segCount; ++$k) {
  637. $startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
  638. $offset += 2;
  639. }
  640. $idDelta = array(); // delta for all character codes in segment
  641. for ($k = 0; $k < $segCount; ++$k) {
  642. $idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
  643. $offset += 2;
  644. }
  645. $idRangeOffset = array(); // Offsets into glyphIdArray or 0
  646. for ($k = 0; $k < $segCount; ++$k) {
  647. $idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
  648. $offset += 2;
  649. }
  650. $gidlen = (floor($length / 2) - 8 - (4 * $segCount));
  651. $glyphIdArray = array(); // glyph index array
  652. for ($k = 0; $k < $gidlen; ++$k) {
  653. $glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
  654. $offset += 2;
  655. }
  656. for ($k = 0; $k < $segCount; ++$k) {
  657. for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
  658. if ($idRangeOffset[$k] == 0) {
  659. $g = ($idDelta[$k] + $c) % 65536;
  660. } else {
  661. $gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
  662. $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
  663. }
  664. if ($g < 0) {
  665. $g = 0;
  666. }
  667. $ctg[$c] = $g;
  668. }
  669. }
  670. break;
  671. }
  672. case 6: { // Format 6: Trimmed table mapping
  673. $offset += 4; // skip length and version/language
  674. $firstCode = TCPDF_STATIC::_getUSHORT($font, $offset);
  675. $offset += 2;
  676. $entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
  677. $offset += 2;
  678. for ($k = 0; $k < $entryCount; ++$k) {
  679. $c = ($k + $firstCode);
  680. $g = TCPDF_STATIC::_getUSHORT($font, $offset);
  681. $offset += 2;
  682. $ctg[$c] = $g;
  683. }
  684. break;
  685. }
  686. case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
  687. $offset += 10; // skip reserved, length and version/language
  688. for ($k = 0; $k < 8192; ++$k) {
  689. $is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset);
  690. ++$offset;
  691. }
  692. $nGroups = TCPDF_STATIC::_getULONG($font, $offset);
  693. $offset += 4;
  694. for ($i = 0; $i < $nGroups; ++$i) {
  695. $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
  696. $offset += 4;
  697. $endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
  698. $offset += 4;
  699. $startGlyphID = TCPDF_STATIC::_getULONG($font, $offset);
  700. $offset += 4;
  701. for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
  702. $is32idx = floor($c / 8);
  703. if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
  704. $c = $k;
  705. } else {
  706. // 32 bit format
  707. // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
  708. //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
  709. //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
  710. $c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
  711. }
  712. $ctg[$c] = 0;
  713. ++$startGlyphID;
  714. }
  715. }
  716. break;
  717. }
  718. case 10: { // Format 10: Trimmed array
  719. $offset += 10; // skip reserved, length and version/language
  720. $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
  721. $offset += 4;
  722. $numChars = TCPDF_STATIC::_getULONG($font, $offset);
  723. $offset += 4;
  724. for ($k = 0; $k < $numChars; ++$k) {
  725. $c = ($k + $startCharCode);
  726. $g = TCPDF_STATIC::_getUSHORT($font, $offset);
  727. $ctg[$c] = $g;
  728. $offset += 2;
  729. }
  730. break;
  731. }
  732. case 12: { // Format 12: Segmented coverage
  733. $offset += 10; // skip length and version/language
  734. $nGroups = TCPDF_STATIC::_getULONG($font, $offset);
  735. $offset += 4;
  736. for ($k = 0; $k < $nGroups; ++$k) {
  737. $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
  738. $offset += 4;
  739. $endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
  740. $offset += 4;
  741. $startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset);
  742. $offset += 4;
  743. for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
  744. $ctg[$c] = $startGlyphCode;
  745. ++$startGlyphCode;
  746. }
  747. }
  748. break;
  749. }
  750. case 13: { // Format 13: Many-to-one range mappings
  751. // to be implemented ...
  752. break;
  753. }
  754. case 14: { // Format 14: Unicode Variation Sequences
  755. // to be implemented ...
  756. break;
  757. }
  758. }
  759. }
  760. }
  761. if (!isset($ctg[0])) {
  762. $ctg[0] = 0;
  763. }
  764. // get xHeight (height of x)
  765. $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[120]] + 4);
  766. $yMin = TCPDF_STATIC::_getFWORD($font, $offset);
  767. $offset += 4;
  768. $yMax = TCPDF_STATIC::_getFWORD($font, $offset);
  769. $offset += 2;
  770. $fmetric['XHeight'] = round(($yMax - $yMin) * $urk);
  771. // get CapHeight (height of H)
  772. $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[72]] + 4);
  773. $yMin = TCPDF_STATIC::_getFWORD($font, $offset);
  774. $offset += 4;
  775. $yMax = TCPDF_STATIC::_getFWORD($font, $offset);
  776. $offset += 2;
  777. $fmetric['CapHeight'] = round(($yMax - $yMin) * $urk);
  778. // ceate widths array
  779. $cw = array();
  780. $offset = $table['hmtx']['offset'];
  781. for ($i = 0 ; $i < $numberOfHMetrics; ++$i) {
  782. $cw[$i] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
  783. $offset += 4; // skip lsb
  784. }
  785. if ($numberOfHMetrics < $numGlyphs) {
  786. // fill missing widths with the last value
  787. $cw = array_pad($cw, $numGlyphs, $cw[($numberOfHMetrics - 1)]);
  788. }
  789. $fmetric['MissingWidth'] = $cw[0];
  790. $fmetric['cw'] = '';
  791. for ($cid = 0; $cid <= 65535; ++$cid) {
  792. if (isset($ctg[$cid])) {
  793. if (isset($cw[$ctg[$cid]])) {
  794. $fmetric['cw'] .= ','.$cid.'=>'.$cw[$ctg[$cid]];
  795. }
  796. if ($addcbbox AND isset($indexToLoc[$ctg[$cid]])) {
  797. $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[$cid]]);
  798. $xMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 2) * $urk);
  799. $yMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 4) * $urk);
  800. $xMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 6) * $urk);
  801. $yMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 8) * $urk);
  802. $fmetric['cbbox'] .= ','.$cid.'=>array('.$xMin.','.$yMin.','.$xMax.','.$yMax.')';
  803. }
  804. }
  805. }
  806. } // end of true type
  807. if (($fmetric['type'] == 'TrueTypeUnicode') AND (count($ctg) == 256)) {
  808. $fmetric['type'] == 'TrueType';
  809. }
  810. // ---------- create php font file ----------
  811. $pfile = '<'.'?'.'php'."\n";
  812. $pfile .= '// TCPDF FONT FILE DESCRIPTION'."\n";
  813. $pfile .= '$type=\''.$fmetric['type'].'\';'."\n";
  814. $pfile .= '$name=\''.$fmetric['name'].'\';'."\n";
  815. $pfile .= '$up='.$fmetric['underlinePosition'].';'."\n";
  816. $pfile .= '$ut='.$fmetric['underlineThickness'].';'."\n";
  817. if ($fmetric['MissingWidth'] > 0) {
  818. $pfile .= '$dw='.$fmetric['MissingWidth'].';'."\n";
  819. } else {
  820. $pfile .= '$dw='.$fmetric['AvgWidth'].';'."\n";
  821. }
  822. $pfile .= '$diff=\''.$fmetric['diff'].'\';'."\n";
  823. if ($fmetric['type'] == 'Type1') {
  824. // Type 1
  825. $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
  826. $pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
  827. $pfile .= '$size1='.$fmetric['size1'].';'."\n";
  828. $pfile .= '$size2='.$fmetric['size2'].';'."\n";
  829. } else {
  830. $pfile .= '$originalsize='.$fmetric['originalsize'].';'."\n";
  831. if ($fmetric['type'] == 'cidfont0') {
  832. // CID-0
  833. switch ($fonttype) {
  834. case 'CID0JP': {
  835. $pfile .= '// Japanese'."\n";
  836. $pfile .= '$enc=\'UniJIS-UTF16-H\';'."\n";
  837. $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Japan1\',\'Supplement\'=>5);'."\n";
  838. $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
  839. break;
  840. }
  841. case 'CID0KR': {
  842. $pfile .= '// Korean'."\n";
  843. $pfile .= '$enc=\'UniKS-UTF16-H\';'."\n";
  844. $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Korea1\',\'Supplement\'=>0);'."\n";
  845. $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ak12.php\');'."\n";
  846. break;
  847. }
  848. case 'CID0CS': {
  849. $pfile .= '// Chinese Simplified'."\n";
  850. $pfile .= '$enc=\'UniGB-UTF16-H\';'."\n";
  851. $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'GB1\',\'Supplement\'=>2);'."\n";
  852. $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ag15.php\');'."\n";
  853. break;
  854. }
  855. case 'CID0CT':
  856. default: {
  857. $pfile .= '// Chinese Traditional'."\n";
  858. $pfile .= '$enc=\'UniCNS-UTF16-H\';'."\n";
  859. $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'CNS1\',\'Supplement\'=>0);'."\n";
  860. $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
  861. break;
  862. }
  863. }
  864. } else {
  865. // TrueType
  866. $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
  867. $pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
  868. $pfile .= '$ctg=\''.$fmetric['ctg'].'\';'."\n";
  869. // create CIDToGIDMap
  870. $cidtogidmap = str_pad('', 131072, "\x00"); // (256 * 256 * 2) = 131072
  871. foreach ($ctg as $cid => $gid) {
  872. $cidtogidmap = self::updateCIDtoGIDmap($cidtogidmap, $cid, $ctg[$cid]);
  873. }
  874. // store compressed CIDToGIDMap
  875. $fp = fopen($outpath.$fmetric['ctg'], 'wb');
  876. fwrite($fp, gzcompress($cidtogidmap));
  877. fclose($fp);
  878. }
  879. }
  880. $pfile .= '$desc=array(';
  881. $pfile .= '\'Flags\'=>'.$fmetric['Flags'].',';
  882. $pfile .= '\'FontBBox\'=>\'['.$fmetric['bbox'].']\',';
  883. $pfile .= '\'ItalicAngle\'=>'.$fmetric['italicAngle'].',';
  884. $pfile .= '\'Ascent\'=>'.$fmetric['Ascent'].',';
  885. $pfile .= '\'Descent\'=>'.$fmetric['Descent'].',';
  886. $pfile .= '\'Leading\'=>'.$fmetric['Leading'].',';
  887. $pfile .= '\'CapHeight\'=>'.$fmetric['CapHeight'].',';
  888. $pfile .= '\'XHeight\'=>'.$fmetric['XHeight'].',';
  889. $pfile .= '\'StemV\'=>'.$fmetric['StemV'].',';
  890. $pfile .= '\'StemH\'=>'.$fmetric['StemH'].',';
  891. $pfile .= '\'AvgWidth\'=>'.$fmetric['AvgWidth'].',';
  892. $pfile .= '\'MaxWidth\'=>'.$fmetric['MaxWidth'].',';
  893. $pfile .= '\'MissingWidth\'=>'.$fmetric['MissingWidth'].'';
  894. $pfile .= ');'."\n";
  895. if (isset($fmetric['cbbox'])) {
  896. $pfile .= '$cbbox=array('.substr($fmetric['cbbox'], 1).');'."\n";
  897. }
  898. $pfile .= '$cw=array('.substr($fmetric['cw'], 1).');'."\n";
  899. $pfile .= '// --- EOF ---'."\n";
  900. // store file
  901. $fp = fopen($outpath.$font_name.'.php', 'w');
  902. fwrite($fp, $pfile);
  903. fclose($fp);
  904. // return TCPDF font name
  905. return $font_name;
  906. }
  907. /**
  908. * Returs the checksum of a TTF table.
  909. * @param $table (string) table to check
  910. * @param $length (int) length of table in bytes
  911. * @return int checksum
  912. * @author Nicola Asuni
  913. * @since 5.2.000 (2010-06-02)
  914. * @public static
  915. */
  916. public static function _getTTFtableChecksum($table, $length) {
  917. $sum = 0;
  918. $tlen = ($length / 4);
  919. $offset = 0;
  920. for ($i = 0; $i < $tlen; ++$i) {
  921. $v = unpack('Ni', substr($table, $offset, 4));
  922. $sum += $v['i'];
  923. $offset += 4;
  924. }
  925. $sum = unpack('Ni', pack('N', $sum));
  926. return $sum['i'];
  927. }
  928. /**
  929. * Returns a subset of the TrueType font data without the unused glyphs.
  930. * @param $font (string) TrueType font data.
  931. * @param $subsetchars (array) Array of used characters (the glyphs to keep).
  932. * @return (string) A subset of TrueType font data without the unused glyphs.
  933. * @author Nicola Asuni
  934. * @since 5.2.000 (2010-06-02)
  935. * @public static
  936. */
  937. public static function _getTrueTypeFontSubset($font, $subsetchars) {
  938. ksort($subsetchars);
  939. $offset = 0; // offset position of the font data
  940. if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
  941. // sfnt version must be 0x00010000 for TrueType version 1.0.
  942. return $font;
  943. }
  944. $offset += 4;
  945. // get number of tables
  946. $numTables = TCPDF_STATIC::_getUSHORT($font, $offset);
  947. $offset += 2;
  948. // skip searchRange, entrySelector and rangeShift
  949. $offset += 6;
  950. // tables array
  951. $table = array();
  952. // for each table
  953. for ($i = 0; $i < $numTables; ++$i) {
  954. // get table info
  955. $tag = substr($font, $offset, 4);
  956. $offset += 4;
  957. $table[$tag] = array();
  958. $table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset);
  959. $offset += 4;
  960. $table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
  961. $offset += 4;
  962. $table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset);
  963. $offset += 4;
  964. }
  965. // check magicNumber
  966. $offset = $table['head']['offset'] + 12;
  967. if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) {
  968. // magicNumber must be 0x5F0F3CF5
  969. return $font;
  970. }
  971. $offset += 4;
  972. // get offset mode (indexToLocFormat : 0 = short, 1 = long)
  973. $offset = $table['head']['offset'] + 50;
  974. $short_offset = (TCPDF_STATIC::_getSHORT($font, $offset) == 0);
  975. $offset += 2;
  976. // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
  977. $indexToLoc = array();
  978. $offset = $table['loca']['offset'];
  979. if ($short_offset) {
  980. // short version
  981. $tot_num_glyphs = floor($table['loca']['length'] / 2); // numGlyphs + 1
  982. for ($i = 0; $i < $tot_num_glyphs; ++$i) {
  983. $indexToLoc[$i] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
  984. $offset += 2;
  985. }
  986. } else {
  987. // long version
  988. $tot_num_glyphs = ($table['loca']['length'] / 4); // numGlyphs + 1
  989. for ($i = 0; $i < $tot_num_glyphs; ++$i) {
  990. $indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset);
  991. $offset += 4;
  992. }
  993. }
  994. // get glyphs indexes of chars from cmap table
  995. $subsetglyphs = array(); // glyph IDs on key
  996. $subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
  997. $offset = $table['cmap']['offset'] + 2;
  998. $numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
  999. $offset += 2;
  1000. $encodingTables = array();
  1001. for ($i = 0; $i < $numEncodingTables; ++$i) {
  1002. $encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  1003. $offset += 2;
  1004. $encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  1005. $offset += 2;
  1006. $encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
  1007. $offset += 4;
  1008. }
  1009. foreach ($encodingTables as $enctable) {
  1010. // get all platforms and encodings
  1011. $offset = $table['cmap']['offset'] + $enctable['offset'];
  1012. $format = TCPDF_STATIC::_getUSHORT($font, $offset);
  1013. $offset += 2;
  1014. switch ($format) {
  1015. case 0: { // Format 0: Byte encoding table
  1016. $offset += 4; // skip length and version/language
  1017. for ($c = 0; $c < 256; ++$c) {
  1018. if (isset($subsetchars[$c])) {
  1019. $g = TCPDF_STATIC::_getBYTE($font, $offset);
  1020. $subsetglyphs[$g] = true;
  1021. }
  1022. ++$offset;
  1023. }
  1024. break;
  1025. }
  1026. case 2: { // Format 2: High-byte mapping through table
  1027. $offset += 4; // skip length and version/language
  1028. $numSubHeaders = 0;
  1029. for ($i = 0; $i < 256; ++$i) {
  1030. // Array that maps high bytes to subHeaders: value is subHeader index * 8.
  1031. $subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8);
  1032. $offset += 2;
  1033. if ($numSubHeaders < $subHeaderKeys[$i]) {
  1034. $numSubHeaders = $subHeaderKeys[$i];
  1035. }
  1036. }
  1037. // the number of subHeaders is equal to the max of subHeaderKeys + 1
  1038. ++$numSubHeaders;
  1039. // read subHeader structures
  1040. $subHeaders = array();
  1041. $numGlyphIndexArray = 0;
  1042. for ($k = 0; $k < $numSubHeaders; ++$k) {
  1043. $subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  1044. $offset += 2;
  1045. $subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  1046. $offset += 2;
  1047. $subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  1048. $offset += 2;
  1049. $subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
  1050. $offset += 2;
  1051. $subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
  1052. $subHeaders[$k]['idRangeOffset'] /= 2;
  1053. $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
  1054. }
  1055. for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
  1056. $glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
  1057. $offset += 2;
  1058. }
  1059. for ($i = 0; $i < 256; ++$i) {
  1060. $k = $subHeaderKeys[$i];
  1061. if ($k == 0) {
  1062. // one byte code
  1063. $c = $i;
  1064. if (isset($subsetchars[$c])) {
  1065. $g = $glyphIndexArray[0];
  1066. $subsetglyphs[$g] = true;
  1067. }
  1068. } else {
  1069. // two bytes code
  1070. $start_byte = $subHeaders[$k]['firstCode'];
  1071. $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
  1072. for ($j = $start_byte; $j < $end_byte; ++$j) {
  1073. // combine high and low bytes
  1074. $c = (($i << 8) + $j);
  1075. if (isset($subsetchars[$c])) {
  1076. $idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
  1077. $g = ($glyphIndexArray[$idRangeOffset] + $subHeaders[$k]['idDelta']) % 65536;
  1078. if ($g < 0) {
  1079. $g = 0;
  1080. }
  1081. $subsetglyphs[$g] = true;
  1082. }
  1083. }
  1084. }
  1085. }
  1086. break;
  1087. }
  1088. case 4: { // Format 4: Segment mapping to delta values
  1089. $length = TCPDF_STATIC::_getUSHORT($font, $offset);
  1090. $offset += 2;
  1091. $offset += 2; // skip version/language
  1092. $segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
  1093. $offset += 2;
  1094. $offset += 6; // skip searchRange, entrySelector, rangeShift
  1095. $endCount = array(); // array of end character codes for each segment
  1096. for ($k = 0; $k < $segCount; ++$k) {
  1097. $endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
  1098. $offset += 2;
  1099. }
  1100. $offset += 2; // skip reservedPad
  1101. $startCount = array(); // array of start character codes for each segment
  1102. for ($k = 0; $k < $segCount; ++$k) {
  1103. $startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
  1104. $offset += 2;
  1105. }
  1106. $idDelta = array(); // delta for all character codes in segment
  1107. for ($k = 0; $k < $segCount; ++$k) {
  1108. $idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
  1109. $offset += 2;
  1110. }
  1111. $idRangeOffset = array(); // Offsets into glyphIdArray or 0
  1112. for ($k = 0; $k < $segCount; ++$k) {
  1113. $idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
  1114. $offset += 2;
  1115. }
  1116. $gidlen = (floor($length / 2) - 8 - (4 * $segCount));
  1117. $glyphIdArray = array(); // glyph index array
  1118. for ($k = 0; $k < $gidlen; ++$k) {
  1119. $glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
  1120. $offset += 2;
  1121. }
  1122. for ($k = 0; $k < $segCount; ++$k) {
  1123. for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
  1124. if (isset($subsetchars[$c])) {
  1125. if ($idRangeOffset[$k] == 0) {
  1126. $g = ($idDelta[$k] + $c) % 65536;
  1127. } else {
  1128. $gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
  1129. $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
  1130. }
  1131. if ($g < 0) {
  1132. $g = 0;
  1133. }
  1134. $subsetglyphs[$g] = true;
  1135. }
  1136. }
  1137. }
  1138. break;
  1139. }
  1140. case 6: { // Format 6: Trimmed table mapping
  1141. $offset += 4; // skip length and version/language
  1142. $firstCode = TCPDF_STATIC::_getUSHORT($font, $offset);
  1143. $offset += 2;
  1144. $entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
  1145. $offset += 2;
  1146. for ($k = 0; $k < $entryCount; ++$k) {
  1147. $c = ($k + $firstCode);
  1148. if (isset($subsetchars[$c])) {
  1149. $g = TCPDF_STATIC::_getUSHORT($font, $offset);
  1150. $subsetglyphs[$g] = true;
  1151. }
  1152. $offset += 2;
  1153. }
  1154. break;
  1155. }
  1156. case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
  1157. $offset += 10; // skip reserved, length and version/language
  1158. for ($k = 0; $k < 8192; ++$k) {
  1159. $is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset);
  1160. ++$offset;
  1161. }
  1162. $nGroups = TCPDF_STATIC::_getULONG($font, $offset);
  1163. $offset += 4;
  1164. for ($i = 0; $i < $nGroups; ++$i) {
  1165. $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
  1166. $offset += 4;
  1167. $endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
  1168. $offset += 4;
  1169. $startGlyphID = TCPDF_STATIC::_getULONG($font, $offset);
  1170. $offset += 4;
  1171. for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
  1172. $is32idx = floor($c / 8);
  1173. if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
  1174. $c = $k;
  1175. } else {
  1176. // 32 bit format
  1177. // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
  1178. //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
  1179. //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
  1180. $c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
  1181. }
  1182. if (isset($subsetchars[$c])) {
  1183. $subsetglyphs[$startGlyphID] = true;
  1184. }
  1185. ++$startGlyphID;
  1186. }
  1187. }
  1188. break;
  1189. }
  1190. case 10: { // Format 10: Trimmed array
  1191. $offset += 10; // skip reserved, length and version/language
  1192. $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
  1193. $offset += 4;
  1194. $numChars = TCPDF_STATIC::_getULONG($font, $offset);
  1195. $offset += 4;
  1196. for ($k = 0; $k < $numChars; ++$k) {
  1197. $c = ($k + $startCharCode);
  1198. if (isset($subsetchars[$c])) {
  1199. $g = TCPDF_STATIC::_getUSHORT($font, $offset);
  1200. $subsetglyphs[$g] = true;
  1201. }
  1202. $offset += 2;
  1203. }
  1204. break;
  1205. }
  1206. case 12: { // Format 12: Segmented coverage
  1207. $offset += 10; // skip length and version/language
  1208. $nGroups = TCPDF_STATIC::_getULONG($font, $offset);
  1209. $offset += 4;
  1210. for ($k = 0; $k < $nGroups; ++$k) {
  1211. $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
  1212. $offset += 4;
  1213. $endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
  1214. $offset += 4;
  1215. $startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset);
  1216. $offset += 4;
  1217. for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
  1218. if (isset($subsetchars[$c])) {
  1219. $subsetglyphs[$startGlyphCode] = true;
  1220. }
  1221. ++$startGlyphCode;
  1222. }
  1223. }
  1224. break;
  1225. }
  1226. case 13: { // Format 13: Many-to-one range mappings
  1227. // to be implemented ...
  1228. break;
  1229. }
  1230. case 14: { // Format 14: Unicode Variation Sequences
  1231. // to be implemented ...
  1232. break;
  1233. }
  1234. }
  1235. }
  1236. // include all parts of composite glyphs
  1237. $new_sga = $subsetglyphs;
  1238. while (!empty($new_sga)) {
  1239. $sga = $new_sga;
  1240. $new_sga = array();
  1241. foreach ($sga as $key => $val) {
  1242. if (isset($indexToLoc[$key])) {
  1243. $offset = ($table['glyf']['offset'] + $indexToLoc[$key]);
  1244. $numberOfContours = TCPDF_STATIC::_getSHORT($font, $offset);
  1245. $offset += 2;
  1246. if ($numberOfContours < 0) { // composite glyph
  1247. $offset += 8; // skip xMin, yMin, xMax, yMax
  1248. do {
  1249. $flags = TCPDF_STATIC::_getUSHORT($font, $offset);
  1250. $offset += 2;
  1251. $glyphIndex = TCPDF_STATIC::_getUSHORT($font, $offset);
  1252. $offset += 2;
  1253. if (!isset($subsetglyphs[$glyphIndex])) {
  1254. // add missing glyphs
  1255. $new_sga[$glyphIndex] = true;
  1256. }
  1257. // skip some bytes by case
  1258. if ($flags & 1) {
  1259. $offset += 4;
  1260. } else {
  1261. $offset += 2;
  1262. }
  1263. if ($flags & 8) {
  1264. $offset += 2;
  1265. } elseif ($flags & 64) {
  1266. $offset += 4;
  1267. } elseif ($flags & 128) {
  1268. $offset += 8;
  1269. }
  1270. } while ($flags & 32);
  1271. }
  1272. }
  1273. }
  1274. $subsetglyphs += $new_sga;
  1275. }
  1276. // sort glyphs by key (and remove duplicates)
  1277. ksort($subsetglyphs);
  1278. // build new glyf and loca tables
  1279. $glyf = '';
  1280. $loca = '';
  1281. $offset = 0;
  1282. $glyf_offset = $table['glyf']['offset'];
  1283. for ($i = 0; $i < $tot_num_glyphs; ++$i) {
  1284. if (isset($subsetglyphs[$i])) {
  1285. $length = ($indexToLoc[($i + 1)] - $indexToLoc[$i]);
  1286. $glyf .= substr($font, ($glyf_offset + $indexToLoc[$i]), $length);
  1287. } else {
  1288. $length = 0;
  1289. }
  1290. if ($short_offset) {
  1291. $loca .= pack('n', floor($offset / 2));
  1292. } else {
  1293. $loca .= pack('N', $offset);
  1294. }
  1295. $offset += $length;
  1296. }
  1297. // array of table names to preserve (loca and glyf tables will be added later)
  1298. // the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
  1299. $table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names
  1300. // get the tables to preserve
  1301. $offset = 12;
  1302. foreach ($table as $tag => $val) {
  1303. if (in_array($tag, $table_names)) {
  1304. $table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']);
  1305. if ($tag == 'head') {
  1306. // set the checkSumAdjustment to 0
  1307. $table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
  1308. }
  1309. $pad = 4 - ($table[$tag]['length'] % 4);
  1310. if ($pad != 4) {
  1311. // the length of a table must be a multiple of four bytes
  1312. $table[$tag]['length'] += $pad;
  1313. $table[$tag]['data'] .= str_repeat("\x0", $pad);
  1314. }
  1315. $table[$tag]['offset'] = $offset;
  1316. $offset += $table[$tag]['length'];
  1317. // check sum is not changed (so keep the following line commented)
  1318. //$table[$tag]['checkSum'] = self::_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']);
  1319. } else {
  1320. unset($table[$tag]);
  1321. }
  1322. }
  1323. // add loca
  1324. $table['loca']['data'] = $loca;
  1325. $table['loca']['length'] = strlen($loca);
  1326. $pad = 4 - ($table['loca']['length'] % 4);
  1327. if ($pad != 4) {
  1328. // the length of a table must be a multiple of four bytes
  1329. $table['loca']['length'] += $pad;
  1330. $table['loca']['data'] .= str_repeat("\x0", $pad);
  1331. }
  1332. $table['loca']['offset'] = $offset;
  1333. $table['loca']['checkSum'] = self::_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
  1334. $offset += $table['loca']['length'];
  1335. // add glyf
  1336. $table['glyf']['data'] = $glyf;
  1337. $table['glyf']['length'] = strlen($glyf);
  1338. $pad = 4 - ($table['glyf']['length'] % 4);
  1339. if ($pad != 4) {
  1340. // the length of a table must be a multiple of four bytes
  1341. $table['glyf']['length'] += $pad;
  1342. $table['glyf']['data'] .= str_repeat("\x0", $pad);
  1343. }
  1344. $table['glyf']['offset'] = $offset;
  1345. $table['glyf']['checkSum'] = self::_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
  1346. // rebuild font
  1347. $font = '';
  1348. $font .= pack('N', 0x10000); // sfnt version
  1349. $numTables = count($table);
  1350. $font .= pack('n', $numTables); // numTables
  1351. $entrySelector = floor(log($numTables, 2));
  1352. $searchRange = pow(2, $entrySelector) * 16;
  1353. $rangeShift = ($numTables * 16) - $searchRange;
  1354. $font .= pack('n', $searchRange); // searchRange
  1355. $font .= pack('n', $entrySelector); // entrySelector
  1356. $font .= pack('n', $rangeShift); // rangeShift
  1357. $offset = ($numTables * 16);
  1358. foreach ($table as $tag => $data) {
  1359. $font .= $tag; // tag
  1360. $font .= pack('N', $data['checkSum']); // checkSum
  1361. $font .= pack('N', ($data['offset'] + $offset)); // offset
  1362. $font .= pack('N', $data['length']); // length
  1363. }
  1364. foreach ($table as $data) {
  1365. $font .= $data['data'];
  1366. }
  1367. // set checkSumAdjustment on head table
  1368. $checkSumAdjustment = 0xB1B0AFBA - self::_getTTFtableChecksum($font, strlen($font));
  1369. $font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12);
  1370. return $font;
  1371. }
  1372. /**
  1373. * Outputs font widths
  1374. * @param $font (array) font data
  1375. * @param $cidoffset (int) offset for CID values
  1376. * @return PDF command string for font widths
  1377. * @author Nicola Asuni
  1378. * @since 4.4.000 (2008-12-07)
  1379. * @public static
  1380. */
  1381. public static function _putfontwidths($font, $cidoffset=0) {
  1382. ksort($font['cw']);
  1383. $rangeid = 0;
  1384. $range = array();
  1385. $prevcid = -2;
  1386. $prevwidth = -1;
  1387. $interval = false;
  1388. // for each character
  1389. foreach ($font['cw'] as $cid => $width) {
  1390. $cid -= $cidoffset;
  1391. if ($font['subset'] AND (!isset($font['subsetchars'][$cid]))) {
  1392. // ignore the unused characters (font subsetting)
  1393. continue;
  1394. }
  1395. if ($width != $font['dw']) {
  1396. if ($cid == ($prevcid + 1)) {
  1397. // consecutive CID
  1398. if ($width == $prevwidth) {
  1399. if ($width == $range[$rangeid][0]) {
  1400. $range[$rangeid][] = $width;
  1401. } else {
  1402. array_pop($range[$rangeid]);
  1403. // new range
  1404. $rangeid = $prevcid;
  1405. $range[$rangeid] = array();
  1406. $range[$rangeid][] = $prevwidth;
  1407. $range[$rangeid][] = $width;
  1408. }
  1409. $interval = true;
  1410. $range[$rangeid]['interval'] = true;
  1411. } else {
  1412. if ($interval) {
  1413. // new range
  1414. $rangeid = $cid;
  1415. $range[$rangeid] = array();
  1416. $range[$rangeid][] = $width;
  1417. } else {
  1418. $range[$rangeid][] = $width;
  1419. }
  1420. $interval = false;
  1421. }
  1422. } else {
  1423. // new range
  1424. $rangeid = $cid;
  1425. $range[$rangeid] = array();
  1426. $range[$rangeid][] = $width;
  1427. $interval = false;
  1428. }
  1429. $prevcid = $cid;
  1430. $prevwidth = $width;
  1431. }
  1432. }
  1433. // optimize ranges
  1434. $prevk = -1;
  1435. $nextk = -1;
  1436. $prevint = false;
  1437. foreach ($range as $k => $ws) {
  1438. $cws = count($ws);
  1439. if (($k == $nextk) AND (!$prevint) AND ((!isset($ws['interval'])) OR ($cws < 4))) {
  1440. if (isset($range[$k]['interval'])) {
  1441. unset($range[$k]['interval']);
  1442. }
  1443. $range[$prevk] = array_merge($range[$prevk], $range[$k]);
  1444. unset($range[$k]);
  1445. } else {
  1446. $prevk = $k;
  1447. }
  1448. $nextk = $k + $cws;
  1449. if (isset($ws['interval'])) {
  1450. if ($cws > 3) {
  1451. $prevint = true;
  1452. } else {
  1453. $prevint = false;
  1454. }
  1455. if (isset($range[$k]['interval'])) {
  1456. unset($range[$k]['interval']);
  1457. }
  1458. --$nextk;
  1459. } else {
  1460. $prevint = false;
  1461. }
  1462. }
  1463. // output data
  1464. $w = '';
  1465. foreach ($range as $k => $ws) {
  1466. if (count(array_count_values($ws)) == 1) {
  1467. // interval mode is more compact
  1468. $w .= ' '.$k.' '.($k + count($ws) - 1).' '.$ws[0];
  1469. } else {
  1470. // range mode
  1471. $w .= ' '.$k.' [ '.implode(' ', $ws).' ]';
  1472. }
  1473. }
  1474. return '/W ['.$w.' ]';
  1475. }
  1476. /**
  1477. * Returns the unicode caracter specified by the value
  1478. * @param $c (int) UTF-8 value
  1479. * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
  1480. * @return Returns the specified character.
  1481. * @since 2.3.000 (2008-03-05)
  1482. * @public static
  1483. */
  1484. public static function unichr($c, $unicode=true) {
  1485. if (!$unicode) {
  1486. return chr($c);
  1487. } elseif ($c <= 0x7F) {
  1488. // one byte
  1489. return chr($c);
  1490. } elseif ($c <= 0x7FF) {
  1491. // two bytes
  1492. return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
  1493. } elseif ($c <= 0xFFFF) {
  1494. // three bytes
  1495. return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
  1496. } elseif ($c <= 0x10FFFF) {
  1497. // four bytes
  1498. return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
  1499. } else {
  1500. return '';
  1501. }
  1502. }
  1503. /**
  1504. * Returns the unicode caracter specified by UTF-8 value
  1505. * @param $c (int) UTF-8 value
  1506. * @return Returns the specified character.
  1507. * @public static
  1508. */
  1509. public static function unichrUnicode($c) {
  1510. return self::unichr($c, true);
  1511. }
  1512. /**
  1513. * Returns the unicode caracter specified by ASCII value
  1514. * @param $c (int) UTF-8 value
  1515. * @return Returns the specified character.
  1516. * @public static
  1517. */
  1518. public static function unichrASCII($c) {
  1519. return self::unichr($c, false);
  1520. }
  1521. /**
  1522. * Converts array of UTF-8 characters to UTF16-BE string.<br>
  1523. * Based on: http://www.faqs.org/rfcs/rfc2781.html
  1524. * <pre>
  1525. * Encoding UTF-16:
  1526. *
  1527. * Encoding of a single character from an ISO 10646 character value to
  1528. * UTF-16 proceeds as follows. Let U be the character number, no greater
  1529. * than 0x10FFFF.
  1530. *
  1531. * 1) If U < 0x10000, encode U as a 16-bit unsigned integer and
  1532. * terminate.
  1533. *
  1534. * 2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
  1535. * U' must be less than or equal to 0xFFFFF. That is, U' can be
  1536. * represented in 20 bits.
  1537. *
  1538. * 3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
  1539. * 0xDC00, respectively. These integers each have 10 bits free to
  1540. * encode the character value, for a total of 20 bits.
  1541. *
  1542. * 4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
  1543. * bits of W1 and the 10 low-order bits of U' to the 10 low-order
  1544. * bits of W2. Terminate.
  1545. *
  1546. * Graphically, steps 2 through 4 look like:
  1547. * U' = yyyyyyyyyyxxxxxxxxxx
  1548. * W1 = 110110yyyyyyyyyy
  1549. * W2 = 110111xxxxxxxxxx
  1550. * </pre>
  1551. * @param $unicode (array) array containing UTF-8 unicode values
  1552. * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
  1553. * @return string
  1554. * @protected
  1555. * @author Nicola Asuni
  1556. * @since 2.1.000 (2008-01-08)
  1557. * @public static
  1558. */
  1559. public static function arrUTF8ToUTF16BE($unicode, $setbom=false) {
  1560. $outstr = ''; // string to be returned
  1561. if ($setbom) {
  1562. $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
  1563. }
  1564. foreach ($unicode as $char) {
  1565. if ($char == 0x200b) {
  1566. // skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
  1567. } elseif ($char == 0xFFFD) {
  1568. $outstr .= "\xFF\xFD"; // replacement character
  1569. } elseif ($char < 0x10000) {
  1570. $outstr .= chr($char >> 0x08);
  1571. $outstr .= chr($char & 0xFF);
  1572. } else {
  1573. $char -= 0x10000;
  1574. $w1 = 0xD800 | ($char >> 0x0a);
  1575. $w2 = 0xDC00 | ($char & 0x3FF);
  1576. $outstr .= chr($w1 >> 0x08);
  1577. $outstr .= chr($w1 & 0xFF);
  1578. $outstr .= chr($w2 >> 0x08);
  1579. $outstr .= chr($w2 & 0xFF);
  1580. }
  1581. }
  1582. return $outstr;
  1583. }
  1584. /**
  1585. * Convert an array of UTF8 values to array of unicode characters
  1586. * @param $ta (array) The input array of UTF8 values.
  1587. * @param $isunicode (boolean) True for Unicode mode, false otherwise.
  1588. * @return Return array of unicode characters
  1589. * @since 4.5.037 (2009-04-07)
  1590. * @public static
  1591. */
  1592. public static function UTF8ArrayToUniArray($ta, $isunicode=true) {
  1593. if ($isunicode) {
  1594. return array_map(array('self', 'unichrUnicode'), $ta);
  1595. }
  1596. return array_map(array('self', 'unichrASCII'), $ta);
  1597. }
  1598. /**
  1599. * Extract a slice of the $strarr array and return it as string.
  1600. * @param $strarr (string) The input array of characters.
  1601. * @param $start (int) the starting element of $strarr.
  1602. * @param $end (int) first element that will not be returned.
  1603. * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
  1604. * @return Return part of a string
  1605. * @public static
  1606. */
  1607. public static function UTF8ArrSubString($strarr, $start='', $end='', $unicode=true) {
  1608. if (strlen($start) == 0) {
  1609. $start = 0;
  1610. }
  1611. if (strlen($end) == 0) {
  1612. $end = count($strarr);
  1613. }
  1614. $string = '';
  1615. for ($i = $start; $i < $end; ++$i) {
  1616. $string .= self::unichr($strarr[$i], $unicode);
  1617. }
  1618. return $string;
  1619. }
  1620. /**
  1621. * Extract a slice of the $uniarr array and return it as string.
  1622. * @param $uniarr (string) The input array of characters.
  1623. * @param $start (int) the starting element of $strarr.
  1624. * @param $end (int) first element that will not be returned.
  1625. * @return Return part of a string
  1626. * @since 4.5.037 (2009-04-07)
  1627. * @public static
  1628. */
  1629. public static function UniArrSubString($uniarr, $start='', $end='') {
  1630. if (strlen($start) == 0) {
  1631. $start = 0;
  1632. }
  1633. if (strlen($end) == 0) {
  1634. $end = count($uniarr);
  1635. }
  1636. $string = '';
  1637. for ($i=$start; $i < $end; ++$i) {
  1638. $string .= $uniarr[$i];
  1639. }
  1640. return $string;
  1641. }
  1642. /**
  1643. * Update the CIDToGIDMap string with a new value.
  1644. * @param $map (string) CIDToGIDMap.
  1645. * @param $cid (int) CID value.
  1646. * @param $gid (int) GID value.
  1647. * @return (string) CIDToGIDMap.
  1648. * @author Nicola Asuni
  1649. * @since 5.9.123 (2011-09-29)
  1650. * @public static
  1651. */
  1652. public static function updateCIDtoGIDmap($map, $cid, $gid) {
  1653. if (($cid >= 0) AND ($cid <= 0xFFFF) AND ($gid >= 0)) {
  1654. if ($gid > 0xFFFF) {
  1655. $gid -= 0x10000;
  1656. }
  1657. $map[($cid * 2)] = chr($gid >> 8);
  1658. $map[(($cid * 2) + 1)] = chr($gid & 0xFF);
  1659. }
  1660. return $map;
  1661. }
  1662. /**
  1663. * Return fonts path
  1664. * @return string
  1665. * @public static
  1666. */
  1667. public static function _getfontpath() {
  1668. if (!defined('K_PATH_FONTS') AND is_dir($fdir = realpath(dirname(__FILE__).'/../fonts'))) {
  1669. if (substr($fdir, -1) != '/') {
  1670. $fdir .= '/';
  1671. }
  1672. define('K_PATH_FONTS', $fdir);
  1673. }
  1674. return defined('K_PATH_FONTS') ? K_PATH_FONTS : '';
  1675. }
  1676. /**
  1677. * Return font full path
  1678. * @param $file (string) Font file name.
  1679. * @param $fontdir (string) Font directory (set to false fto search on default directories)
  1680. * @return string Font full path or empty string
  1681. * @author Nicola Asuni
  1682. * @since 6.0.025
  1683. * @public static
  1684. */
  1685. public static function getFontFullPath($file, $fontdir=false) {
  1686. $fontfile = '';
  1687. // search files on various directories
  1688. if (($fontdir !== false) AND @file_exists($fontdir.$file)) {
  1689. $fontfile = $fontdir.$file;
  1690. } elseif (@file_exists(self::_getfontpath().$file)) {
  1691. $fontfile = self::_getfontpath().$file;
  1692. } elseif (@file_exists($file)) {
  1693. $fontfile = $file;
  1694. }
  1695. return $fontfile;
  1696. }
  1697. /**
  1698. * Converts UTF-8 characters array to array of Latin1 characters array<br>
  1699. * @param $unicode (array) array containing UTF-8 unicode values
  1700. * @return array
  1701. * @author Nicola Asuni
  1702. * @since 4.8.023 (2010-01-15)
  1703. * @public static
  1704. */
  1705. public static function UTF8ArrToLatin1Arr($unicode) {
  1706. $outarr = array(); // array to be returned
  1707. foreach ($unicode as $char) {
  1708. if ($char < 256) {
  1709. $outarr[] = $char;
  1710. } elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) {
  1711. // map from UTF-8
  1712. $outarr[] = TCPDF_FONT_DATA::$uni_utf8tolatin[$char];
  1713. } elseif ($char == 0xFFFD) {
  1714. // skip
  1715. } else {
  1716. $outarr[] = 63; // '?' character
  1717. }
  1718. }
  1719. return $outarr;
  1720. }
  1721. /**
  1722. * Converts UTF-8 characters array to array of Latin1 string<br>
  1723. * @param $unicode (array) array containing UTF-8 unicode values
  1724. * @return array
  1725. * @author Nicola Asuni
  1726. * @since 4.8.023 (2010-01-15)
  1727. * @public static
  1728. */
  1729. public static function UTF8ArrToLatin1($unicode) {
  1730. $outstr = ''; // string to be returned
  1731. foreach ($unicode as $char) {
  1732. if ($char < 256) {
  1733. $outstr .= chr($char);
  1734. } elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) {
  1735. // map from UTF-8
  1736. $outstr .= chr(TCPDF_FONT_DATA::$uni_utf8tolatin[$char]);
  1737. } elseif ($char == 0xFFFD) {
  1738. // skip
  1739. } else {
  1740. $outstr .= '?';
  1741. }
  1742. }
  1743. return $outstr;
  1744. }
  1745. /**
  1746. * Converts UTF-8 character to integer value.<br>
  1747. * Uses the getUniord() method if the value is not cached.
  1748. * @param $uch (string) character string to process.
  1749. * @return integer Unicode value
  1750. * @public static
  1751. */
  1752. public static function uniord($uch) {
  1753. if (!isset(self::$cache_uniord[$uch])) {
  1754. self::$cache_uniord[$uch] = self::getUniord($uch);
  1755. }
  1756. return self::$cache_uniord[$uch];
  1757. }
  1758. /**
  1759. * Converts UTF-8 character to integer value.<br>
  1760. * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
  1761. * Based on: http://www.faqs.org/rfcs/rfc3629.html
  1762. * <pre>
  1763. * Char. number range | UTF-8 octet sequence
  1764. * (hexadecimal) | (binary)
  1765. * --------------------+-----------------------------------------------
  1766. * 0000 0000-0000 007F | 0xxxxxxx
  1767. * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
  1768. * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
  1769. * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  1770. * ---------------------------------------------------------------------
  1771. *
  1772. * ABFN notation:
  1773. * ---------------------------------------------------------------------
  1774. * UTF8-octets = *( UTF8-char )
  1775. * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
  1776. * UTF8-1 = %x00-7F
  1777. * UTF8-2 = %xC2-DF UTF8-tail
  1778. *
  1779. * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
  1780. * %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
  1781. * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
  1782. * %xF4 %x80-8F 2( UTF8-tail )
  1783. * UTF8-tail = %x80-BF
  1784. * ---------------------------------------------------------------------
  1785. * </pre>
  1786. * @param $uch (string) character string to process.
  1787. * @return integer Unicode value
  1788. * @author Nicola Asuni
  1789. * @public static
  1790. */
  1791. public static function getUniord($uch) {
  1792. if (function_exists('mb_convert_encoding')) {
  1793. list(, $char) = @unpack('N', mb_convert_encoding($uch, 'UCS-4BE', 'UTF-8'));
  1794. if ($char >= 0) {
  1795. return $char;
  1796. }
  1797. }
  1798. $bytes = array(); // array containing single character byte sequences
  1799. $countbytes = 0;
  1800. $numbytes = 1; // number of octetc needed to represent the UTF-8 character
  1801. $length = strlen($uch);
  1802. for ($i = 0; $i < $length; ++$i) {
  1803. $char = ord($uch[$i]); // get one string character at time
  1804. if ($countbytes == 0) { // get starting octect
  1805. if ($char <= 0x7F) {
  1806. return $char; // use the character "as is" because is ASCII
  1807. } elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN)
  1808. $bytes[] = ($char - 0xC0) << 0x06;
  1809. ++$countbytes;
  1810. $numbytes = 2;
  1811. } elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
  1812. $bytes[] = ($char - 0xE0) << 0x0C;
  1813. ++$countbytes;
  1814. $numbytes = 3;
  1815. } elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
  1816. $bytes[] = ($char - 0xF0) << 0x12;
  1817. ++$countbytes;
  1818. $numbytes = 4;
  1819. } else {
  1820. // use replacement character for other invalid sequences
  1821. return 0xFFFD;
  1822. }
  1823. } elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
  1824. $bytes[] = $char - 0x80;
  1825. ++$countbytes;
  1826. if ($countbytes == $numbytes) {
  1827. // compose UTF-8 bytes to a single unicode value
  1828. $char = $bytes[0];
  1829. for ($j = 1; $j < $numbytes; ++$j) {
  1830. $char += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
  1831. }
  1832. if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 0x10FFFF)) {
  1833. // The definition of UTF-8 prohibits encoding character numbers between
  1834. // U+D800 and U+DFFF, which are reserved for use with the UTF-16
  1835. // encoding form (as surrogate pairs) and do not directly represent
  1836. // characters.
  1837. return 0xFFFD; // use replacement character
  1838. } else {
  1839. return $char;
  1840. }
  1841. }
  1842. } else {
  1843. // use replacement character for other invalid sequences
  1844. return 0xFFFD;
  1845. }
  1846. }
  1847. return 0xFFFD;
  1848. }
  1849. /**
  1850. * Converts UTF-8 strings to codepoints array.<br>
  1851. * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
  1852. * @param $str (string) string to process.
  1853. * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
  1854. * @param $currentfont (array) Reference to current font array.
  1855. * @return array containing codepoints (UTF-8 characters values)
  1856. * @author Nicola Asuni
  1857. * @public static
  1858. */
  1859. public static function UTF8StringToArray($str, $isunicode=true, &$currentfont) {
  1860. if ($isunicode) {
  1861. // requires PCRE unicode support turned on
  1862. $chars = TCPDF_STATIC::pregSplit('//','u', $str, -1, PREG_SPLIT_NO_EMPTY);
  1863. $carr = array_map(array('self', 'uniord'), $chars);
  1864. } else {
  1865. $chars = str_split($str);
  1866. $carr = array_map('ord', $chars);
  1867. }
  1868. $currentfont['subsetchars'] += array_fill_keys($carr, true);
  1869. return $carr;
  1870. }
  1871. /**
  1872. * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br>
  1873. * @param $str (string) string to process.
  1874. * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
  1875. * @param $currentfont (array) Reference to current font array.
  1876. * @return string
  1877. * @since 3.2.000 (2008-06-23)
  1878. * @public static
  1879. */
  1880. public static function UTF8ToLatin1($str, $isunicode=true, &$currentfont) {
  1881. $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
  1882. return self::UTF8ArrToLatin1($unicode);
  1883. }
  1884. /**
  1885. * Converts UTF-8 strings to UTF16-BE.<br>
  1886. * @param $str (string) string to process.
  1887. * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
  1888. * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
  1889. * @param $currentfont (array) Reference to current font array.
  1890. * @return string
  1891. * @author Nicola Asuni
  1892. * @since 1.53.0.TC005 (2005-01-05)
  1893. * @public static
  1894. */
  1895. public static function UTF8ToUTF16BE($str, $setbom=false, $isunicode=true, &$currentfont) {
  1896. if (!$isunicode) {
  1897. return $str; // string is not in unicode
  1898. }
  1899. $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
  1900. return self::arrUTF8ToUTF16BE($unicode, $setbom);
  1901. }
  1902. /**
  1903. * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
  1904. * @param $str (string) string to manipulate.
  1905. * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
  1906. * @param $forcertl (bool) if true forces RTL text direction
  1907. * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
  1908. * @param $currentfont (array) Reference to current font array.
  1909. * @return string
  1910. * @author Nicola Asuni
  1911. * @since 2.1.000 (2008-01-08)
  1912. * @public static
  1913. */
  1914. public static function utf8StrRev($str, $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) {
  1915. return self::utf8StrArrRev(self::UTF8StringToArray($str, $isunicode, $currentfont), $str, $setbom, $forcertl, $isunicode, $currentfont);
  1916. }
  1917. /**
  1918. * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
  1919. * @param $arr (array) array of unicode values.
  1920. * @param $str (string) string to manipulate (or empty value).
  1921. * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
  1922. * @param $forcertl (bool) if true forces RTL text direction
  1923. * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
  1924. * @param $currentfont (array) Reference to current font array.
  1925. * @return string
  1926. * @author Nicola Asuni
  1927. * @since 4.9.000 (2010-03-27)
  1928. * @public static
  1929. */
  1930. public static function utf8StrArrRev($arr, $str='', $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) {
  1931. return self::arrUTF8ToUTF16BE(self::utf8Bidi($arr, $str, $forcertl, $isunicode, $currentfont), $setbom);
  1932. }
  1933. /**
  1934. * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
  1935. * @param $ta (array) array of characters composing the string.
  1936. * @param $str (string) string to process
  1937. * @param $forcertl (bool) if 'R' forces RTL, if 'L' forces LTR
  1938. * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
  1939. * @param $currentfont (array) Reference to current font array.
  1940. * @return array of unicode chars
  1941. * @author Nicola Asuni
  1942. * @since 2.4.000 (2008-03-06)
  1943. * @public static
  1944. */
  1945. public static function utf8Bidi($ta, $str='', $forcertl=false, $isunicode=true, &$currentfont) {
  1946. // paragraph embedding level
  1947. $pel = 0;
  1948. // max level
  1949. $maxlevel = 0;
  1950. if (TCPDF_STATIC::empty_string($str)) {
  1951. // create string from array
  1952. $str = self::UTF8ArrSubString($ta, '', '', $isunicode);
  1953. }
  1954. // check if string contains arabic text
  1955. if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $str)) {
  1956. $arabic = true;
  1957. } else {
  1958. $arabic = false;
  1959. }
  1960. // check if string contains RTL text
  1961. if (!($forcertl OR $arabic OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $str))) {
  1962. return $ta;
  1963. }
  1964. // get number of chars
  1965. $numchars = count($ta);
  1966. if ($forcertl == 'R') {
  1967. $pel = 1;
  1968. } elseif ($forcertl == 'L') {
  1969. $pel = 0;
  1970. } else {
  1971. // P2. In each paragraph, find the first character of type L, AL, or R.
  1972. // P3. If a character is found in P2 and it is of type AL or R, then set the paragraph embedding level to one; otherwise, set it to zero.
  1973. for ($i=0; $i < $numchars; ++$i) {
  1974. $type = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
  1975. if ($type == 'L') {
  1976. $pel = 0;
  1977. break;
  1978. } elseif (($type == 'AL') OR ($type == 'R')) {
  1979. $pel = 1;
  1980. break;
  1981. }
  1982. }
  1983. }
  1984. // Current Embedding Level
  1985. $cel = $pel;
  1986. // directional override status
  1987. $dos = 'N';
  1988. $remember = array();
  1989. // start-of-level-run
  1990. $sor = $pel % 2 ? 'R' : 'L';
  1991. $eor = $sor;
  1992. // Array of characters data
  1993. $chardata = Array();
  1994. // X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral. Process each character iteratively, applying rules X2 through X9. Only embedding levels from 0 to 61 are valid in this phase.
  1995. // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
  1996. for ($i=0; $i < $numchars; ++$i) {
  1997. if ($ta[$i] == TCPDF_FONT_DATA::$uni_RLE) {
  1998. // X2. With each RLE, compute the least greater odd embedding level.
  1999. // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
  2000. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  2001. $next_level = $cel + ($cel % 2) + 1;
  2002. if ($next_level < 62) {
  2003. $remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLE, 'cel' => $cel, 'dos' => $dos);
  2004. $cel = $next_level;
  2005. $dos = 'N';
  2006. $sor = $eor;
  2007. $eor = $cel % 2 ? 'R' : 'L';
  2008. }
  2009. } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRE) {
  2010. // X3. With each LRE, compute the least greater even embedding level.
  2011. // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
  2012. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  2013. $next_level = $cel + 2 - ($cel % 2);
  2014. if ( $next_level < 62 ) {
  2015. $remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRE, 'cel' => $cel, 'dos' => $dos);
  2016. $cel = $next_level;
  2017. $dos = 'N';
  2018. $sor = $eor;
  2019. $eor = $cel % 2 ? 'R' : 'L';
  2020. }
  2021. } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_RLO) {
  2022. // X4. With each RLO, compute the least greater odd embedding level.
  2023. // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to right-to-left.
  2024. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  2025. $next_level = $cel + ($cel % 2) + 1;
  2026. if ($next_level < 62) {
  2027. $remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLO, 'cel' => $cel, 'dos' => $dos);
  2028. $cel = $next_level;
  2029. $dos = 'R';
  2030. $sor = $eor;
  2031. $eor = $cel % 2 ? 'R' : 'L';
  2032. }
  2033. } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRO) {
  2034. // X5. With each LRO, compute the least greater even embedding level.
  2035. // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to left-to-right.
  2036. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  2037. $next_level = $cel + 2 - ($cel % 2);
  2038. if ( $next_level < 62 ) {
  2039. $remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRO, 'cel' => $cel, 'dos' => $dos);
  2040. $cel = $next_level;
  2041. $dos = 'L';
  2042. $sor = $eor;
  2043. $eor = $cel % 2 ? 'R' : 'L';
  2044. }
  2045. } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_PDF) {
  2046. // X7. With each PDF, determine the matching embedding or override code. If there was a valid matching code, restore (pop) the last remembered (pushed) embedding level and directional override.
  2047. if (count($remember)) {
  2048. $last = count($remember ) - 1;
  2049. if (($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLE) OR
  2050. ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRE) OR
  2051. ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLO) OR
  2052. ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRO)) {
  2053. $match = array_pop($remember);
  2054. $cel = $match['cel'];
  2055. $dos = $match['dos'];
  2056. $sor = $eor;
  2057. $eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
  2058. }
  2059. }
  2060. } elseif (($ta[$i] != TCPDF_FONT_DATA::$uni_RLE) AND
  2061. ($ta[$i] != TCPDF_FONT_DATA::$uni_LRE) AND
  2062. ($ta[$i] != TCPDF_FONT_DATA::$uni_RLO) AND
  2063. ($ta[$i] != TCPDF_FONT_DATA::$uni_LRO) AND
  2064. ($ta[$i] != TCPDF_FONT_DATA::$uni_PDF)) {
  2065. // X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
  2066. // a. Set the level of the current character to the current embedding level.
  2067. // b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
  2068. if ($dos != 'N') {
  2069. $chardir = $dos;
  2070. } else {
  2071. if (isset(TCPDF_FONT_DATA::$uni_type[$ta[$i]])) {
  2072. $chardir = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
  2073. } else {
  2074. $chardir = 'L';
  2075. }
  2076. }
  2077. // stores string characters and other information
  2078. $chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
  2079. }
  2080. } // end for each char
  2081. // X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
  2082. // X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
  2083. // X10. The remaining rules are applied to each run of characters at the same level. For each run, determine the start-of-level-run (sor) and end-of-level-run (eor) type, either L or R. This depends on the higher of the two levels on either side of the boundary (at the start or end of the paragraph, the level of the 'other' run is the base embedding level). If the higher level is odd, the type is R; otherwise, it is L.
  2084. // 3.3.3 Resolving Weak Types
  2085. // Weak types are now resolved one level run at a time. At level run boundaries where the type of the character on the other side of the boundary is required, the type assigned to sor or eor is used.
  2086. // Nonspacing marks are now resolved based on the previous characters.
  2087. $numchars = count($chardata);
  2088. // W1. Examine each nonspacing mark (NSM) in the level run, and change the type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor.
  2089. $prevlevel = -1; // track level changes
  2090. $levcount = 0; // counts consecutive chars at the same level
  2091. for ($i=0; $i < $numchars; ++$i) {
  2092. if ($chardata[$i]['type'] == 'NSM') {
  2093. if ($levcount) {
  2094. $chardata[$i]['type'] = $chardata[$i]['sor'];
  2095. } elseif ($i > 0) {
  2096. $chardata[$i]['type'] = $chardata[($i-1)]['type'];
  2097. }
  2098. }
  2099. if ($chardata[$i]['level'] != $prevlevel) {
  2100. $levcount = 0;
  2101. } else {
  2102. ++$levcount;
  2103. }
  2104. $prevlevel = $chardata[$i]['level'];
  2105. }
  2106. // W2. Search backward from each instance of a European number until the first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number.
  2107. $prevlevel = -1;
  2108. $levcount = 0;
  2109. for ($i=0; $i < $numchars; ++$i) {
  2110. if ($chardata[$i]['char'] == 'EN') {
  2111. for ($j=$levcount; $j >= 0; $j--) {
  2112. if ($chardata[$j]['type'] == 'AL') {
  2113. $chardata[$i]['type'] = 'AN';
  2114. } elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) {
  2115. break;
  2116. }
  2117. }
  2118. }
  2119. if ($chardata[$i]['level'] != $prevlevel) {
  2120. $levcount = 0;
  2121. } else {
  2122. ++$levcount;
  2123. }
  2124. $prevlevel = $chardata[$i]['level'];
  2125. }
  2126. // W3. Change all ALs to R.
  2127. for ($i=0; $i < $numchars; ++$i) {
  2128. if ($chardata[$i]['type'] == 'AL') {
  2129. $chardata[$i]['type'] = 'R';
  2130. }
  2131. }
  2132. // W4. A single European separator between two European numbers changes to a European number. A single common separator between two numbers of the same type changes to that type.
  2133. $prevlevel = -1;
  2134. $levcount = 0;
  2135. for ($i=0; $i < $numchars; ++$i) {
  2136. if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
  2137. if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
  2138. $chardata[$i]['type'] = 'EN';
  2139. } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
  2140. $chardata[$i]['type'] = 'EN';
  2141. } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) {
  2142. $chardata[$i]['type'] = 'AN';
  2143. }
  2144. }
  2145. if ($chardata[$i]['level'] != $prevlevel) {
  2146. $levcount = 0;
  2147. } else {
  2148. ++$levcount;
  2149. }
  2150. $prevlevel = $chardata[$i]['level'];
  2151. }
  2152. // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
  2153. $prevlevel = -1;
  2154. $levcount = 0;
  2155. for ($i=0; $i < $numchars; ++$i) {
  2156. if ($chardata[$i]['type'] == 'ET') {
  2157. if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
  2158. $chardata[$i]['type'] = 'EN';
  2159. } else {
  2160. $j = $i+1;
  2161. while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
  2162. if ($chardata[$j]['type'] == 'EN') {
  2163. $chardata[$i]['type'] = 'EN';
  2164. break;
  2165. } elseif ($chardata[$j]['type'] != 'ET') {
  2166. break;
  2167. }
  2168. ++$j;
  2169. }
  2170. }
  2171. }
  2172. if ($chardata[$i]['level'] != $prevlevel) {
  2173. $levcount = 0;
  2174. } else {
  2175. ++$levcount;
  2176. }
  2177. $prevlevel = $chardata[$i]['level'];
  2178. }
  2179. // W6. Otherwise, separators and terminators change to Other Neutral.
  2180. $prevlevel = -1;
  2181. $levcount = 0;
  2182. for ($i=0; $i < $numchars; ++$i) {
  2183. if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
  2184. $chardata[$i]['type'] = 'ON';
  2185. }
  2186. if ($chardata[$i]['level'] != $prevlevel) {
  2187. $levcount = 0;
  2188. } else {
  2189. ++$levcount;
  2190. }
  2191. $prevlevel = $chardata[$i]['level'];
  2192. }
  2193. //W7. Search backward from each instance of a European number until the first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L.
  2194. $prevlevel = -1;
  2195. $levcount = 0;
  2196. for ($i=0; $i < $numchars; ++$i) {
  2197. if ($chardata[$i]['char'] == 'EN') {
  2198. for ($j=$levcount; $j >= 0; $j--) {
  2199. if ($chardata[$j]['type'] == 'L') {
  2200. $chardata[$i]['type'] = 'L';
  2201. } elseif ($chardata[$j]['type'] == 'R') {
  2202. break;
  2203. }
  2204. }
  2205. }
  2206. if ($chardata[$i]['level'] != $prevlevel) {
  2207. $levcount = 0;
  2208. } else {
  2209. ++$levcount;
  2210. }
  2211. $prevlevel = $chardata[$i]['level'];
  2212. }
  2213. // N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. European and Arabic numbers act as if they were R in terms of their influence on neutrals. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries.
  2214. $prevlevel = -1;
  2215. $levcount = 0;
  2216. for ($i=0; $i < $numchars; ++$i) {
  2217. if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
  2218. if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
  2219. $chardata[$i]['type'] = 'L';
  2220. } elseif (($chardata[$i]['type'] == 'N') AND
  2221. (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
  2222. (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
  2223. $chardata[$i]['type'] = 'R';
  2224. } elseif ($chardata[$i]['type'] == 'N') {
  2225. // N2. Any remaining neutrals take the embedding direction
  2226. $chardata[$i]['type'] = $chardata[$i]['sor'];
  2227. }
  2228. } elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
  2229. // first char
  2230. if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
  2231. $chardata[$i]['type'] = 'L';
  2232. } elseif (($chardata[$i]['type'] == 'N') AND
  2233. (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
  2234. (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
  2235. $chardata[$i]['type'] = 'R';
  2236. } elseif ($chardata[$i]['type'] == 'N') {
  2237. // N2. Any remaining neutrals take the embedding direction
  2238. $chardata[$i]['type'] = $chardata[$i]['sor'];
  2239. }
  2240. } elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) {
  2241. //last char
  2242. if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
  2243. $chardata[$i]['type'] = 'L';
  2244. } elseif (($chardata[$i]['type'] == 'N') AND
  2245. (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
  2246. (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
  2247. $chardata[$i]['type'] = 'R';
  2248. } elseif ($chardata[$i]['type'] == 'N') {
  2249. // N2. Any remaining neutrals take the embedding direction
  2250. $chardata[$i]['type'] = $chardata[$i]['sor'];
  2251. }
  2252. } elseif ($chardata[$i]['type'] == 'N') {
  2253. // N2. Any remaining neutrals take the embedding direction
  2254. $chardata[$i]['type'] = $chardata[$i]['sor'];
  2255. }
  2256. if ($chardata[$i]['level'] != $prevlevel) {
  2257. $levcount = 0;
  2258. } else {
  2259. ++$levcount;
  2260. }
  2261. $prevlevel = $chardata[$i]['level'];
  2262. }
  2263. // I1. For all characters with an even (left-to-right) embedding direction, those of type R go up one level and those of type AN or EN go up two levels.
  2264. // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
  2265. for ($i=0; $i < $numchars; ++$i) {
  2266. $odd = $chardata[$i]['level'] % 2;
  2267. if ($odd) {
  2268. if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
  2269. $chardata[$i]['level'] += 1;
  2270. }
  2271. } else {
  2272. if ($chardata[$i]['type'] == 'R') {
  2273. $chardata[$i]['level'] += 1;
  2274. } elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
  2275. $chardata[$i]['level'] += 2;
  2276. }
  2277. }
  2278. $maxlevel = max($chardata[$i]['level'],$maxlevel);
  2279. }
  2280. // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
  2281. // 1. Segment separators,
  2282. // 2. Paragraph separators,
  2283. // 3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
  2284. // 4. Any sequence of white space characters at the end of the line.
  2285. for ($i=0; $i < $numchars; ++$i) {
  2286. if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
  2287. $chardata[$i]['level'] = $pel;
  2288. } elseif ($chardata[$i]['type'] == 'WS') {
  2289. $j = $i+1;
  2290. while ($j < $numchars) {
  2291. if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
  2292. (($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
  2293. $chardata[$i]['level'] = $pel;
  2294. break;
  2295. } elseif ($chardata[$j]['type'] != 'WS') {
  2296. break;
  2297. }
  2298. ++$j;
  2299. }
  2300. }
  2301. }
  2302. // Arabic Shaping
  2303. // Cursively connected scripts, such as Arabic or Syriac, require the selection of positional character shapes that depend on adjacent characters. Shaping is logically applied after the Bidirectional Algorithm is used and is limited to characters within the same directional run.
  2304. if ($arabic) {
  2305. $endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688);
  2306. $alfletter = array(1570,1571,1573,1575);
  2307. $chardata2 = $chardata;
  2308. $laaletter = false;
  2309. $charAL = array();
  2310. $x = 0;
  2311. for ($i=0; $i < $numchars; ++$i) {
  2312. if ((TCPDF_FONT_DATA::$uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) {
  2313. $charAL[$x] = $chardata[$i];
  2314. $charAL[$x]['i'] = $i;
  2315. $chardata[$i]['x'] = $x;
  2316. ++$x;
  2317. }
  2318. }
  2319. $numAL = $x;
  2320. for ($i=0; $i < $numchars; ++$i) {
  2321. $thischar = $chardata[$i];
  2322. if ($i > 0) {
  2323. $prevchar = $chardata[($i-1)];
  2324. } else {
  2325. $prevchar = false;
  2326. }
  2327. if (($i+1) < $numchars) {
  2328. $nextchar = $chardata[($i+1)];
  2329. } else {
  2330. $nextchar = false;
  2331. }
  2332. if (TCPDF_FONT_DATA::$uni_type[$thischar['char']] == 'AL') {
  2333. $x = $thischar['x'];
  2334. if ($x > 0) {
  2335. $prevchar = $charAL[($x-1)];
  2336. } else {
  2337. $prevchar = false;
  2338. }
  2339. if (($x+1) < $numAL) {
  2340. $nextchar = $charAL[($x+1)];
  2341. } else {
  2342. $nextchar = false;
  2343. }
  2344. // if laa letter
  2345. if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) {
  2346. $arabicarr = TCPDF_FONT_DATA::$uni_laa_array;
  2347. $laaletter = true;
  2348. if ($x > 1) {
  2349. $prevchar = $charAL[($x-2)];
  2350. } else {
  2351. $prevchar = false;
  2352. }
  2353. } else {
  2354. $arabicarr = TCPDF_FONT_DATA::$uni_arabicsubst;
  2355. $laaletter = false;
  2356. }
  2357. if (($prevchar !== false) AND ($nextchar !== false) AND
  2358. ((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND
  2359. ((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND
  2360. ($prevchar['type'] == $thischar['type']) AND
  2361. ($nextchar['type'] == $thischar['type']) AND
  2362. ($nextchar['char'] != 1567)) {
  2363. if (in_array($prevchar['char'], $endedletter)) {
  2364. if (isset($arabicarr[$thischar['char']][2])) {
  2365. // initial
  2366. $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
  2367. }
  2368. } else {
  2369. if (isset($arabicarr[$thischar['char']][3])) {
  2370. // medial
  2371. $chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
  2372. }
  2373. }
  2374. } elseif (($nextchar !== false) AND
  2375. ((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND
  2376. ($nextchar['type'] == $thischar['type']) AND
  2377. ($nextchar['char'] != 1567)) {
  2378. if (isset($arabicarr[$chardata[$i]['char']][2])) {
  2379. // initial
  2380. $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
  2381. }
  2382. } elseif ((($prevchar !== false) AND
  2383. ((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND
  2384. ($prevchar['type'] == $thischar['type'])) OR
  2385. (($nextchar !== false) AND ($nextchar['char'] == 1567))) {
  2386. // final
  2387. if (($i > 1) AND ($thischar['char'] == 1607) AND
  2388. ($chardata[$i-1]['char'] == 1604) AND
  2389. ($chardata[$i-2]['char'] == 1604)) {
  2390. //Allah Word
  2391. // mark characters to delete with false
  2392. $chardata2[$i-2]['char'] = false;
  2393. $chardata2[$i-1]['char'] = false;
  2394. $chardata2[$i]['char'] = 65010;
  2395. } else {
  2396. if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) {
  2397. if (isset($arabicarr[$thischar['char']][0])) {
  2398. // isolated
  2399. $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
  2400. }
  2401. } else {
  2402. if (isset($arabicarr[$thischar['char']][1])) {
  2403. // final
  2404. $chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
  2405. }
  2406. }
  2407. }
  2408. } elseif (isset($arabicarr[$thischar['char']][0])) {
  2409. // isolated
  2410. $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
  2411. }
  2412. // if laa letter
  2413. if ($laaletter) {
  2414. // mark characters to delete with false
  2415. $chardata2[($charAL[($x-1)]['i'])]['char'] = false;
  2416. }
  2417. } // end if AL (Arabic Letter)
  2418. } // end for each char
  2419. /*
  2420. * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
  2421. * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
  2422. */
  2423. for ($i = 0; $i < ($numchars-1); ++$i) {
  2424. if (($chardata2[$i]['char'] == 1617) AND (isset(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])]))) {
  2425. // check if the subtitution font is defined on current font
  2426. if (isset($currentfont['cw'][(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])])])) {
  2427. $chardata2[$i]['char'] = false;
  2428. $chardata2[$i+1]['char'] = TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])];
  2429. }
  2430. }
  2431. }
  2432. // remove marked characters
  2433. foreach ($chardata2 as $key => $value) {
  2434. if ($value['char'] === false) {
  2435. unset($chardata2[$key]);
  2436. }
  2437. }
  2438. $chardata = array_values($chardata2);
  2439. $numchars = count($chardata);
  2440. unset($chardata2);
  2441. unset($arabicarr);
  2442. unset($laaletter);
  2443. unset($charAL);
  2444. }
  2445. // L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher.
  2446. for ($j=$maxlevel; $j > 0; $j--) {
  2447. $ordarray = Array();
  2448. $revarr = Array();
  2449. $onlevel = false;
  2450. for ($i=0; $i < $numchars; ++$i) {
  2451. if ($chardata[$i]['level'] >= $j) {
  2452. $onlevel = true;
  2453. if (isset(TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']])) {
  2454. // L4. A character is depicted by a mirrored glyph if and only if (a) the resolved directionality of that character is R, and (b) the Bidi_Mirrored property value of that character is true.
  2455. $chardata[$i]['char'] = TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']];
  2456. }
  2457. $revarr[] = $chardata[$i];
  2458. } else {
  2459. if ($onlevel) {
  2460. $revarr = array_reverse($revarr);
  2461. $ordarray = array_merge($ordarray, $revarr);
  2462. $revarr = Array();
  2463. $onlevel = false;
  2464. }
  2465. $ordarray[] = $chardata[$i];
  2466. }
  2467. }
  2468. if ($onlevel) {
  2469. $revarr = array_reverse($revarr);
  2470. $ordarray = array_merge($ordarray, $revarr);
  2471. }
  2472. $chardata = $ordarray;
  2473. }
  2474. $ordarray = array();
  2475. foreach ($chardata as $cd) {
  2476. $ordarray[] = $cd['char'];
  2477. // store char values for subsetting
  2478. $currentfont['subsetchars'][$cd['char']] = true;
  2479. }
  2480. return $ordarray;
  2481. }
  2482. /**
  2483. * Get a reference font size.
  2484. * @param $size (string) String containing font size value.
  2485. * @param $refsize (float) Reference font size in points.
  2486. * @return float value in points
  2487. * @public static
  2488. */
  2489. public static function getFontRefSize($size, $refsize=12) {
  2490. switch ($size) {
  2491. case 'xx-small': {
  2492. $size = ($refsize - 4);
  2493. break;
  2494. }
  2495. case 'x-small': {
  2496. $size = ($refsize - 3);
  2497. break;
  2498. }
  2499. case 'small': {
  2500. $size = ($refsize - 2);
  2501. break;
  2502. }
  2503. case 'medium': {
  2504. $size = $refsize;
  2505. break;
  2506. }
  2507. case 'large': {
  2508. $size = ($refsize + 2);
  2509. break;
  2510. }
  2511. case 'x-large': {
  2512. $size = ($refsize + 4);
  2513. break;
  2514. }
  2515. case 'xx-large': {
  2516. $size = ($refsize + 6);
  2517. break;
  2518. }
  2519. case 'smaller': {
  2520. $size = ($refsize - 3);
  2521. break;
  2522. }
  2523. case 'larger': {
  2524. $size = ($refsize + 3);
  2525. break;
  2526. }
  2527. }
  2528. return $size;
  2529. }
  2530. } // END OF TCPDF_FONTS CLASS
  2531. //============================================================+
  2532. // END OF FILE
  2533. //============================================================+