otl_dump.php 156 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288
  1. <?php
  2. require_once __DIR__ . '/../MpdfException.php';
  3. /* * *****************************************************************************
  4. * otl_dump class *
  5. * ***************************************************************************** */
  6. // Define the value used in the "head" table of a created TTF file
  7. // 0x74727565 "true" for Mac
  8. // 0x00010000 for Windows
  9. // Either seems to work for a font embedded in a PDF file
  10. // when read by Adobe Reader on a Windows PC(!)
  11. if (!defined('_TTF_MAC_HEADER'))
  12. define("_TTF_MAC_HEADER", false);
  13. // Recalculate correct metadata/profiles when making subset fonts (not SIP/SMP)
  14. // e.g. xMin, xMax, maxNContours
  15. if (!defined('_RECALC_PROFILE'))
  16. define("_RECALC_PROFILE", false);
  17. // TrueType Font Glyph operators
  18. define("GF_WORDS", (1 << 0));
  19. define("GF_SCALE", (1 << 3));
  20. define("GF_MORE", (1 << 5));
  21. define("GF_XYSCALE", (1 << 6));
  22. define("GF_TWOBYTWO", (1 << 7));
  23. // mPDF 5.7.1
  24. if (!function_exists('unicode_hex')) {
  25. function unicode_hex($unicode_dec)
  26. {
  27. return (sprintf("%05s", strtoupper(dechex($unicode_dec))));
  28. }
  29. }
  30. class OTLdump
  31. {
  32. var $GPOSFeatures; // mPDF 5.7.1
  33. var $GPOSLookups; // mPDF 5.7.1
  34. var $GPOSScriptLang; // mPDF 5.7.1
  35. var $ignoreStrings; // mPDF 5.7.1
  36. var $MarkAttachmentType; // mPDF 5.7.1
  37. var $MarkGlyphSets; // mPDF 7.5.1
  38. var $GlyphClassMarks; // mPDF 5.7.1
  39. var $GlyphClassLigatures; // mPDF 5.7.1
  40. var $GlyphClassBases; // mPDF 5.7.1
  41. var $GlyphClassComponents; // mPDF 5.7.1
  42. var $GSUBScriptLang; // mPDF 5.7.1
  43. var $rtlPUAstr; // mPDF 5.7.1
  44. var $rtlPUAarr; // mPDF 5.7.1
  45. var $fontkey; // mPDF 5.7.1
  46. var $useOTL; // mPDF 5.7.1
  47. var $panose;
  48. var $maxUni;
  49. var $sFamilyClass;
  50. var $sFamilySubClass;
  51. var $sipset;
  52. var $smpset;
  53. var $_pos;
  54. var $numTables;
  55. var $searchRange;
  56. var $entrySelector;
  57. var $rangeShift;
  58. var $tables;
  59. var $otables;
  60. var $filename;
  61. var $fh;
  62. var $glyphPos;
  63. var $charToGlyph;
  64. var $ascent;
  65. var $descent;
  66. var $name;
  67. var $familyName;
  68. var $styleName;
  69. var $fullName;
  70. var $uniqueFontID;
  71. var $unitsPerEm;
  72. var $bbox;
  73. var $capHeight;
  74. var $stemV;
  75. var $italicAngle;
  76. var $flags;
  77. var $underlinePosition;
  78. var $underlineThickness;
  79. var $charWidths;
  80. var $defaultWidth;
  81. var $maxStrLenRead;
  82. var $numTTCFonts;
  83. var $TTCFonts;
  84. var $maxUniChar;
  85. var $kerninfo;
  86. public function __construct(mPDF $mpdf)
  87. {
  88. $this->mpdf = $mpdf;
  89. $this->maxStrLenRead = 200000; // Maximum size of glyf table to read in as string (otherwise reads each glyph from file)
  90. }
  91. function getMetrics($file, $fontkey, $TTCfontID = 0, $debug = false, $BMPonly = false, $kerninfo = false, $useOTL = 0, $mode)
  92. {
  93. // mPDF 5.7.1
  94. $this->mode = $mode;
  95. $this->useOTL = $useOTL; // mPDF 5.7.1
  96. $this->fontkey = $fontkey; // mPDF 5.7.1
  97. $this->filename = $file;
  98. $this->fh = fopen($file, 'rb');
  99. if (!$this->fh) {
  100. throw new MpdfException('Can\'t open file ' . $file);
  101. }
  102. $this->_pos = 0;
  103. $this->charWidths = '';
  104. $this->glyphPos = array();
  105. $this->charToGlyph = array();
  106. $this->tables = array();
  107. $this->otables = array();
  108. $this->kerninfo = array();
  109. $this->ascent = 0;
  110. $this->descent = 0;
  111. $this->numTTCFonts = 0;
  112. $this->TTCFonts = array();
  113. $this->version = $version = $this->read_ulong();
  114. $this->panose = array();
  115. if ($version == 0x4F54544F) {
  116. throw new MpdfException("Postscript outlines are not supported");
  117. }
  118. if ($version == 0x74746366 && !$TTCfontID) {
  119. throw new MpdfException("ERROR - You must define the TTCfontID for a TrueType Collection in config_fonts.php (" . $file . ")");
  120. }
  121. if (!in_array($version, array(0x00010000, 0x74727565)) && !$TTCfontID) {
  122. throw new MpdfException("Not a TrueType font: version=" . $version);
  123. }
  124. if ($TTCfontID > 0) {
  125. $this->version = $version = $this->read_ulong(); // TTC Header version now
  126. if (!in_array($version, array(0x00010000, 0x00020000))) {
  127. throw new MpdfException("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file);
  128. }
  129. $this->numTTCFonts = $this->read_ulong();
  130. for ($i = 1; $i <= $this->numTTCFonts; $i++) {
  131. $this->TTCFonts[$i]['offset'] = $this->read_ulong();
  132. }
  133. $this->seek($this->TTCFonts[$TTCfontID]['offset']);
  134. $this->version = $version = $this->read_ulong(); // TTFont version again now
  135. }
  136. $this->readTableDirectory($debug);
  137. $this->extractInfo($debug, $BMPonly, $kerninfo, $useOTL);
  138. fclose($this->fh);
  139. }
  140. function readTableDirectory($debug = false)
  141. {
  142. $this->numTables = $this->read_ushort();
  143. $this->searchRange = $this->read_ushort();
  144. $this->entrySelector = $this->read_ushort();
  145. $this->rangeShift = $this->read_ushort();
  146. $this->tables = array();
  147. for ($i = 0; $i < $this->numTables; $i++) {
  148. $record = array();
  149. $record['tag'] = $this->read_tag();
  150. $record['checksum'] = array($this->read_ushort(), $this->read_ushort());
  151. $record['offset'] = $this->read_ulong();
  152. $record['length'] = $this->read_ulong();
  153. $this->tables[$record['tag']] = $record;
  154. }
  155. if ($debug)
  156. $this->checksumTables();
  157. }
  158. function checksumTables()
  159. {
  160. // Check the checksums for all tables
  161. foreach ($this->tables AS $t) {
  162. if ($t['length'] > 0 && $t['length'] < $this->maxStrLenRead) { // 1.02
  163. $table = $this->get_chunk($t['offset'], $t['length']);
  164. $checksum = $this->calcChecksum($table);
  165. if ($t['tag'] == 'head') {
  166. $up = unpack('n*', substr($table, 8, 4));
  167. $adjustment[0] = $up[1];
  168. $adjustment[1] = $up[2];
  169. $checksum = $this->sub32($checksum, $adjustment);
  170. }
  171. $xchecksum = $t['checksum'];
  172. if ($xchecksum != $checksum) {
  173. throw new MpdfException(sprintf('TTF file "%s": invalid checksum %s table: %s (expected %s)', $this->filename, dechex($checksum[0]) . dechex($checksum[1]), $t['tag'], dechex($xchecksum[0]) . dechex($xchecksum[1])));
  174. }
  175. }
  176. }
  177. }
  178. function sub32($x, $y)
  179. {
  180. $xlo = $x[1];
  181. $xhi = $x[0];
  182. $ylo = $y[1];
  183. $yhi = $y[0];
  184. if ($ylo > $xlo) {
  185. $xlo += 1 << 16;
  186. $yhi += 1;
  187. }
  188. $reslo = $xlo - $ylo;
  189. if ($yhi > $xhi) {
  190. $xhi += 1 << 16;
  191. }
  192. $reshi = $xhi - $yhi;
  193. $reshi = $reshi & 0xFFFF;
  194. return array($reshi, $reslo);
  195. }
  196. function calcChecksum($data)
  197. {
  198. if (strlen($data) % 4) {
  199. $data .= str_repeat("\0", (4 - (strlen($data) % 4)));
  200. }
  201. $len = strlen($data);
  202. $hi = 0x0000;
  203. $lo = 0x0000;
  204. for ($i = 0; $i < $len; $i+=4) {
  205. $hi += (ord($data[$i]) << 8) + ord($data[$i + 1]);
  206. $lo += (ord($data[$i + 2]) << 8) + ord($data[$i + 3]);
  207. $hi += ($lo >> 16) & 0xFFFF;
  208. $lo = $lo & 0xFFFF;
  209. }
  210. return array($hi, $lo);
  211. }
  212. function get_table_pos($tag)
  213. {
  214. $offset = $this->tables[$tag]['offset'];
  215. $length = $this->tables[$tag]['length'];
  216. return array($offset, $length);
  217. }
  218. function seek($pos)
  219. {
  220. $this->_pos = $pos;
  221. fseek($this->fh, $this->_pos);
  222. }
  223. function skip($delta)
  224. {
  225. $this->_pos = $this->_pos + $delta;
  226. fseek($this->fh, $delta, SEEK_CUR);
  227. }
  228. function seek_table($tag, $offset_in_table = 0)
  229. {
  230. $tpos = $this->get_table_pos($tag);
  231. $this->_pos = $tpos[0] + $offset_in_table;
  232. fseek($this->fh, $this->_pos);
  233. return $this->_pos;
  234. }
  235. function read_tag()
  236. {
  237. $this->_pos += 4;
  238. return fread($this->fh, 4);
  239. }
  240. function read_short()
  241. {
  242. $this->_pos += 2;
  243. $s = fread($this->fh, 2);
  244. $a = (ord($s[0]) << 8) + ord($s[1]);
  245. if ($a & (1 << 15)) {
  246. $a = ($a - (1 << 16));
  247. }
  248. return $a;
  249. }
  250. function unpack_short($s)
  251. {
  252. $a = (ord($s[0]) << 8) + ord($s[1]);
  253. if ($a & (1 << 15)) {
  254. $a = ($a - (1 << 16));
  255. }
  256. return $a;
  257. }
  258. function read_ushort()
  259. {
  260. $this->_pos += 2;
  261. $s = fread($this->fh, 2);
  262. return (ord($s[0]) << 8) + ord($s[1]);
  263. }
  264. function read_ulong()
  265. {
  266. $this->_pos += 4;
  267. $s = fread($this->fh, 4);
  268. // if large uInt32 as an integer, PHP converts it to -ve
  269. return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 16777216 = 1<<24
  270. }
  271. function get_ushort($pos)
  272. {
  273. fseek($this->fh, $pos);
  274. $s = fread($this->fh, 2);
  275. return (ord($s[0]) << 8) + ord($s[1]);
  276. }
  277. function get_ulong($pos)
  278. {
  279. fseek($this->fh, $pos);
  280. $s = fread($this->fh, 4);
  281. // iF large uInt32 as an integer, PHP converts it to -ve
  282. return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 16777216 = 1<<24
  283. }
  284. function pack_short($val)
  285. {
  286. if ($val < 0) {
  287. $val = abs($val);
  288. $val = ~$val;
  289. $val += 1;
  290. }
  291. return pack("n", $val);
  292. }
  293. function splice($stream, $offset, $value)
  294. {
  295. return substr($stream, 0, $offset) . $value . substr($stream, $offset + strlen($value));
  296. }
  297. function _set_ushort($stream, $offset, $value)
  298. {
  299. $up = pack("n", $value);
  300. return $this->splice($stream, $offset, $up);
  301. }
  302. function _set_short($stream, $offset, $val)
  303. {
  304. if ($val < 0) {
  305. $val = abs($val);
  306. $val = ~$val;
  307. $val += 1;
  308. }
  309. $up = pack("n", $val);
  310. return $this->splice($stream, $offset, $up);
  311. }
  312. function get_chunk($pos, $length)
  313. {
  314. fseek($this->fh, $pos);
  315. if ($length < 1) {
  316. return '';
  317. }
  318. return (fread($this->fh, $length));
  319. }
  320. function get_table($tag)
  321. {
  322. list($pos, $length) = $this->get_table_pos($tag);
  323. if ($length == 0) {
  324. return '';
  325. }
  326. fseek($this->fh, $pos);
  327. return (fread($this->fh, $length));
  328. }
  329. function add($tag, $data)
  330. {
  331. if ($tag == 'head') {
  332. $data = $this->splice($data, 8, "\0\0\0\0");
  333. }
  334. $this->otables[$tag] = $data;
  335. }
  336. /////////////////////////////////////////////////////////////////////////////////////////
  337. /////////////////////////////////////////////////////////////////////////////////////////
  338. function extractInfo($debug = false, $BMPonly = false, $kerninfo = false, $useOTL = 0)
  339. {
  340. $this->panose = array();
  341. $this->sFamilyClass = 0;
  342. $this->sFamilySubClass = 0;
  343. ///////////////////////////////////
  344. // name - Naming table
  345. ///////////////////////////////////
  346. $name_offset = $this->seek_table("name");
  347. $format = $this->read_ushort();
  348. if ($format != 0 && $format != 1) {
  349. throw new MpdfException("Unknown name table format " . $format);
  350. }
  351. $numRecords = $this->read_ushort();
  352. $string_data_offset = $name_offset + $this->read_ushort();
  353. $names = array(1 => '', 2 => '', 3 => '', 4 => '', 6 => '');
  354. $K = array_keys($names);
  355. $nameCount = count($names);
  356. for ($i = 0; $i < $numRecords; $i++) {
  357. $platformId = $this->read_ushort();
  358. $encodingId = $this->read_ushort();
  359. $languageId = $this->read_ushort();
  360. $nameId = $this->read_ushort();
  361. $length = $this->read_ushort();
  362. $offset = $this->read_ushort();
  363. if (!in_array($nameId, $K))
  364. continue;
  365. $N = '';
  366. if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name
  367. $opos = $this->_pos;
  368. $this->seek($string_data_offset + $offset);
  369. if ($length % 2 != 0) {
  370. throw new MpdfException("PostScript name is UTF-16BE string of odd length");
  371. }
  372. $length /= 2;
  373. $N = '';
  374. while ($length > 0) {
  375. $char = $this->read_ushort();
  376. $N .= (chr($char));
  377. $length -= 1;
  378. }
  379. $this->_pos = $opos;
  380. $this->seek($opos);
  381. } else if ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name
  382. $opos = $this->_pos;
  383. $N = $this->get_chunk($string_data_offset + $offset, $length);
  384. $this->_pos = $opos;
  385. $this->seek($opos);
  386. }
  387. if ($N && $names[$nameId] == '') {
  388. $names[$nameId] = $N;
  389. $nameCount -= 1;
  390. if ($nameCount == 0)
  391. break;
  392. }
  393. }
  394. if ($names[6])
  395. $psName = $names[6];
  396. else if ($names[4])
  397. $psName = preg_replace('/ /', '-', $names[4]);
  398. else if ($names[1])
  399. $psName = preg_replace('/ /', '-', $names[1]);
  400. else
  401. $psName = '';
  402. if (!$psName) {
  403. throw new MpdfException("Could not find PostScript font name: " . $this->filename);
  404. }
  405. if ($debug) {
  406. for ($i = 0; $i < count($psName); $i++) {
  407. $c = $psName[$i];
  408. $oc = ord($c);
  409. if ($oc > 126 || strpos(' [](){}<>/%', $c) !== false) {
  410. throw new MpdfException("psName=" . $psName . " contains invalid character " . $c . " ie U+" . ord(c));
  411. }
  412. }
  413. }
  414. $this->name = $psName;
  415. if ($names[1]) {
  416. $this->familyName = $names[1];
  417. } else {
  418. $this->familyName = $psName;
  419. }
  420. if ($names[2]) {
  421. $this->styleName = $names[2];
  422. } else {
  423. $this->styleName = 'Regular';
  424. }
  425. if ($names[4]) {
  426. $this->fullName = $names[4];
  427. } else {
  428. $this->fullName = $psName;
  429. }
  430. if ($names[3]) {
  431. $this->uniqueFontID = $names[3];
  432. } else {
  433. $this->uniqueFontID = $psName;
  434. }
  435. if ($names[6]) {
  436. $this->fullName = $names[6];
  437. }
  438. ///////////////////////////////////
  439. // head - Font header table
  440. ///////////////////////////////////
  441. $this->seek_table("head");
  442. if ($debug) {
  443. $ver_maj = $this->read_ushort();
  444. $ver_min = $this->read_ushort();
  445. if ($ver_maj != 1) {
  446. throw new MpdfException('Unknown head table version ' . $ver_maj . '.' . $ver_min);
  447. }
  448. $this->fontRevision = $this->read_ushort() . $this->read_ushort();
  449. $this->skip(4);
  450. $magic = $this->read_ulong();
  451. if ($magic != 0x5F0F3CF5) {
  452. throw new MpdfException('Invalid head table magic ' . $magic);
  453. }
  454. $this->skip(2);
  455. }
  456. else {
  457. $this->skip(18);
  458. }
  459. $this->unitsPerEm = $unitsPerEm = $this->read_ushort();
  460. $scale = 1000 / $unitsPerEm;
  461. $this->skip(16);
  462. $xMin = $this->read_short();
  463. $yMin = $this->read_short();
  464. $xMax = $this->read_short();
  465. $yMax = $this->read_short();
  466. $this->bbox = array(($xMin * $scale), ($yMin * $scale), ($xMax * $scale), ($yMax * $scale));
  467. $this->skip(3 * 2);
  468. $indexToLocFormat = $this->read_ushort();
  469. $glyphDataFormat = $this->read_ushort();
  470. if ($glyphDataFormat != 0) {
  471. throw new MpdfException('Unknown glyph data format ' . $glyphDataFormat);
  472. }
  473. ///////////////////////////////////
  474. // hhea metrics table
  475. ///////////////////////////////////
  476. // ttf2t1 seems to use this value rather than the one in OS/2 - so put in for compatibility
  477. if (isset($this->tables["hhea"])) {
  478. $this->seek_table("hhea");
  479. $this->skip(4);
  480. $hheaAscender = $this->read_short();
  481. $hheaDescender = $this->read_short();
  482. $this->ascent = ($hheaAscender * $scale);
  483. $this->descent = ($hheaDescender * $scale);
  484. }
  485. ///////////////////////////////////
  486. // OS/2 - OS/2 and Windows metrics table
  487. ///////////////////////////////////
  488. if (isset($this->tables["OS/2"])) {
  489. $this->seek_table("OS/2");
  490. $version = $this->read_ushort();
  491. $this->skip(2);
  492. $usWeightClass = $this->read_ushort();
  493. $this->skip(2);
  494. $fsType = $this->read_ushort();
  495. if ($fsType == 0x0002 || ($fsType & 0x0300) != 0) {
  496. global $overrideTTFFontRestriction;
  497. if (!$overrideTTFFontRestriction) {
  498. throw new MpdfException('ERROR - Font file ' . $this->filename . ' cannot be embedded due to copyright restrictions.');
  499. }
  500. $this->restrictedUse = true;
  501. }
  502. $this->skip(20);
  503. $sF = $this->read_short();
  504. $this->sFamilyClass = ($sF >> 8);
  505. $this->sFamilySubClass = ($sF & 0xFF);
  506. $this->_pos += 10; //PANOSE = 10 byte length
  507. $panose = fread($this->fh, 10);
  508. $this->panose = array();
  509. for ($p = 0; $p < strlen($panose); $p++) {
  510. $this->panose[] = ord($panose[$p]);
  511. }
  512. $this->skip(26);
  513. $sTypoAscender = $this->read_short();
  514. $sTypoDescender = $this->read_short();
  515. if (!$this->ascent)
  516. $this->ascent = ($sTypoAscender * $scale);
  517. if (!$this->descent)
  518. $this->descent = ($sTypoDescender * $scale);
  519. if ($version > 1) {
  520. $this->skip(16);
  521. $sCapHeight = $this->read_short();
  522. $this->capHeight = ($sCapHeight * $scale);
  523. } else {
  524. $this->capHeight = $this->ascent;
  525. }
  526. } else {
  527. $usWeightClass = 500;
  528. if (!$this->ascent)
  529. $this->ascent = ($yMax * $scale);
  530. if (!$this->descent)
  531. $this->descent = ($yMin * $scale);
  532. $this->capHeight = $this->ascent;
  533. }
  534. $this->stemV = 50 + intval(pow(($usWeightClass / 65.0), 2));
  535. ///////////////////////////////////
  536. // post - PostScript table
  537. ///////////////////////////////////
  538. $this->seek_table("post");
  539. if ($debug) {
  540. $ver_maj = $this->read_ushort();
  541. $ver_min = $this->read_ushort();
  542. if ($ver_maj < 1 || $ver_maj > 4) {
  543. throw new MpdfException('Unknown post table version ' . $ver_maj);
  544. }
  545. }
  546. else {
  547. $this->skip(4);
  548. }
  549. $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0;
  550. $this->underlinePosition = $this->read_short() * $scale;
  551. $this->underlineThickness = $this->read_short() * $scale;
  552. $isFixedPitch = $this->read_ulong();
  553. $this->flags = 4;
  554. if ($this->italicAngle != 0)
  555. $this->flags = $this->flags | 64;
  556. if ($usWeightClass >= 600)
  557. $this->flags = $this->flags | 262144;
  558. if ($isFixedPitch)
  559. $this->flags = $this->flags | 1;
  560. ///////////////////////////////////
  561. // hhea - Horizontal header table
  562. ///////////////////////////////////
  563. $this->seek_table("hhea");
  564. if ($debug) {
  565. $ver_maj = $this->read_ushort();
  566. $ver_min = $this->read_ushort();
  567. if ($ver_maj != 1) {
  568. throw new MpdfException('Unknown hhea table version ' . $ver_maj);
  569. }
  570. $this->skip(28);
  571. }
  572. else {
  573. $this->skip(32);
  574. }
  575. $metricDataFormat = $this->read_ushort();
  576. if ($metricDataFormat != 0) {
  577. throw new MpdfException('Unknown horizontal metric data format ' . $metricDataFormat);
  578. }
  579. $numberOfHMetrics = $this->read_ushort();
  580. if ($numberOfHMetrics == 0) {
  581. throw new MpdfException('Number of horizontal metrics is 0');
  582. }
  583. ///////////////////////////////////
  584. // maxp - Maximum profile table
  585. ///////////////////////////////////
  586. $this->seek_table("maxp");
  587. if ($debug) {
  588. $ver_maj = $this->read_ushort();
  589. $ver_min = $this->read_ushort();
  590. if ($ver_maj != 1) {
  591. throw new MpdfException('Unknown maxp table version ' . $ver_maj);
  592. }
  593. }
  594. else {
  595. $this->skip(4);
  596. }
  597. $numGlyphs = $this->read_ushort();
  598. ///////////////////////////////////
  599. // cmap - Character to glyph index mapping table
  600. ///////////////////////////////////
  601. $cmap_offset = $this->seek_table("cmap");
  602. $this->skip(2);
  603. $cmapTableCount = $this->read_ushort();
  604. $unicode_cmap_offset = 0;
  605. for ($i = 0; $i < $cmapTableCount; $i++) {
  606. $platformID = $this->read_ushort();
  607. $encodingID = $this->read_ushort();
  608. $offset = $this->read_ulong();
  609. $save_pos = $this->_pos;
  610. if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode
  611. $format = $this->get_ushort($cmap_offset + $offset);
  612. if ($format == 4) {
  613. if (!$unicode_cmap_offset)
  614. $unicode_cmap_offset = $cmap_offset + $offset;
  615. if ($BMPonly)
  616. break;
  617. }
  618. }
  619. // Microsoft, Unicode Format 12 table HKCS
  620. else if ((($platformID == 3 && $encodingID == 10) || $platformID == 0) && !$BMPonly) {
  621. $format = $this->get_ushort($cmap_offset + $offset);
  622. if ($format == 12) {
  623. $unicode_cmap_offset = $cmap_offset + $offset;
  624. break;
  625. }
  626. }
  627. $this->seek($save_pos);
  628. }
  629. if (!$unicode_cmap_offset) {
  630. throw new MpdfException('Font (' . $this->filename . ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)');
  631. }
  632. $sipset = false;
  633. $smpset = false;
  634. // mPDF 5.7.1
  635. $this->GSUBScriptLang = array();
  636. $this->rtlPUAstr = '';
  637. $this->rtlPUAarr = array();
  638. $this->GSUBFeatures = array();
  639. $this->GSUBLookups = array();
  640. $this->GPOSScriptLang = array();
  641. $this->GPOSFeatures = array();
  642. $this->GPOSLookups = array();
  643. $this->glyphIDtoUni = '';
  644. // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above
  645. if ($format == 12 && !$BMPonly) {
  646. $this->maxUniChar = 0;
  647. $this->seek($unicode_cmap_offset + 4);
  648. $length = $this->read_ulong();
  649. $limit = $unicode_cmap_offset + $length;
  650. $this->skip(4);
  651. $nGroups = $this->read_ulong();
  652. $glyphToChar = array();
  653. $charToGlyph = array();
  654. for ($i = 0; $i < $nGroups; $i++) {
  655. $startCharCode = $this->read_ulong();
  656. $endCharCode = $this->read_ulong();
  657. $startGlyphCode = $this->read_ulong();
  658. if ($endCharCode > 0x20000 && $endCharCode < 0x2FFFF) {
  659. $sipset = true;
  660. } else if ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) {
  661. $smpset = true;
  662. }
  663. $offset = 0;
  664. for ($unichar = $startCharCode; $unichar <= $endCharCode; $unichar++) {
  665. $glyph = $startGlyphCode + $offset;
  666. $offset++;
  667. if ($unichar < 0x30000) {
  668. $charToGlyph[$unichar] = $glyph;
  669. $this->maxUniChar = max($unichar, $this->maxUniChar);
  670. $glyphToChar[$glyph][] = $unichar;
  671. }
  672. }
  673. }
  674. } else {
  675. $glyphToChar = array();
  676. $charToGlyph = array();
  677. $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph);
  678. }
  679. $this->sipset = $sipset;
  680. $this->smpset = $smpset;
  681. ///////////////////////////////////
  682. // mPDF 5.7.1
  683. // Map Unmapped glyphs - from $numGlyphs
  684. if ($this->useOTL) {
  685. $bctr = 0xE000;
  686. for ($gid = 1; $gid < $numGlyphs; $gid++) {
  687. if (!isset($glyphToChar[$gid])) {
  688. while (isset($charToGlyph[$bctr])) {
  689. $bctr++;
  690. } // Avoid overwriting a glyph already mapped in PUA
  691. if (($bctr > 0xF8FF) && ($bctr < 0x2CEB0)) {
  692. if (!$BMPonly) {
  693. $bctr = 0x2CEB0; // Use unassigned area 0x2CEB0 to 0x2F7FF (space for 10,000 characters)
  694. $this->sipset = $sipset = true; // forces subsetting; also ensure charwidths are saved
  695. while (isset($charToGlyph[$bctr])) {
  696. $bctr++;
  697. }
  698. } else {
  699. throw new MpdfException($names[1] . " : WARNING - The font does not have enough space to map all (unmapped) included glyphs into Private Use Area U+E000 - U+F8FF");
  700. }
  701. }
  702. $glyphToChar[$gid][] = $bctr;
  703. $charToGlyph[$bctr] = $gid;
  704. $this->maxUniChar = max($bctr, $this->maxUniChar);
  705. $bctr++;
  706. }
  707. }
  708. }
  709. $this->glyphToChar = $glyphToChar;
  710. $this->charToGlyph = $charToGlyph;
  711. ///////////////////////////////////
  712. // mPDF 5.7.1 OpenType Layout tables
  713. $this->GSUBScriptLang = array();
  714. $this->rtlPUAstr = '';
  715. $this->rtlPUAarr = array();
  716. if ($useOTL) {
  717. $this->_getGDEFtables();
  718. list($this->GSUBScriptLang, $this->GSUBFeatures, $this->GSUBLookups, $this->rtlPUAstr, $this->rtlPUAarr) = $this->_getGSUBtables();
  719. list($this->GPOSScriptLang, $this->GPOSFeatures, $this->GPOSLookups) = $this->_getGPOStables();
  720. $this->glyphIDtoUni = str_pad('', 256 * 256 * 3, "\x00");
  721. foreach ($glyphToChar AS $gid => $arr) {
  722. if (isset($glyphToChar[$gid][0])) {
  723. $char = $glyphToChar[$gid][0];
  724. if ($char != 0 && $char != 65535) {
  725. $this->glyphIDtoUni[$gid * 3] = chr($char >> 16);
  726. $this->glyphIDtoUni[$gid * 3 + 1] = chr(($char >> 8) & 0xFF);
  727. $this->glyphIDtoUni[$gid * 3 + 2] = chr($char & 0xFF);
  728. }
  729. }
  730. }
  731. }
  732. ///////////////////////////////////
  733. ///////////////////////////////////
  734. // hmtx - Horizontal metrics table
  735. ///////////////////////////////////
  736. $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale);
  737. ///////////////////////////////////
  738. // kern - Kerning pair table
  739. ///////////////////////////////////
  740. if ($kerninfo) {
  741. // Recognises old form of Kerning table - as required by Windows - Format 0 only
  742. $kern_offset = $this->seek_table("kern");
  743. $version = $this->read_ushort();
  744. $nTables = $this->read_ushort();
  745. // subtable header
  746. $sversion = $this->read_ushort();
  747. $slength = $this->read_ushort();
  748. $scoverage = $this->read_ushort();
  749. $format = $scoverage >> 8;
  750. if ($kern_offset && $version == 0 && $format == 0) {
  751. // Format 0
  752. $nPairs = $this->read_ushort();
  753. $this->skip(6);
  754. for ($i = 0; $i < $nPairs; $i++) {
  755. $left = $this->read_ushort();
  756. $right = $this->read_ushort();
  757. $val = $this->read_short();
  758. if (count($glyphToChar[$left]) == 1 && count($glyphToChar[$right]) == 1) {
  759. if ($left != 32 && $right != 32) {
  760. $this->kerninfo[$glyphToChar[$left][0]][$glyphToChar[$right][0]] = intval($val * $scale);
  761. }
  762. }
  763. }
  764. }
  765. }
  766. }
  767. /////////////////////////////////////////////////////////////////////////////////////////
  768. function _getGDEFtables()
  769. {
  770. ///////////////////////////////////
  771. // GDEF - Glyph Definition
  772. ///////////////////////////////////
  773. // http://www.microsoft.com/typography/otspec/gdef.htm
  774. if (isset($this->tables["GDEF"])) {
  775. if ($this->mode == 'summary') {
  776. $this->mpdf->WriteHTML('<h1>GDEF table</h1>');
  777. }
  778. $gdef_offset = $this->seek_table("GDEF");
  779. // ULONG Version of the GDEF table-currently 0x00010000
  780. $ver_maj = $this->read_ushort();
  781. $ver_min = $this->read_ushort();
  782. // Version 0x00010002 of GDEF header contains additional Offset to a list defining mark glyph set definitions (MarkGlyphSetDef)
  783. $GlyphClassDef_offset = $this->read_ushort();
  784. $AttachList_offset = $this->read_ushort();
  785. $LigCaretList_offset = $this->read_ushort();
  786. $MarkAttachClassDef_offset = $this->read_ushort();
  787. if ($ver_min == 2) {
  788. $MarkGlyphSetsDef_offset = $this->read_ushort();
  789. }
  790. // GlyphClassDef
  791. $this->seek($gdef_offset + $GlyphClassDef_offset);
  792. /*
  793. 1 Base glyph (single character, spacing glyph)
  794. 2 Ligature glyph (multiple character, spacing glyph)
  795. 3 Mark glyph (non-spacing combining glyph)
  796. 4 Component glyph (part of single character, spacing glyph)
  797. */
  798. $GlyphByClass = $this->_getClassDefinitionTable();
  799. if ($this->mode == 'summary') {
  800. $this->mpdf->WriteHTML('<h2>Glyph classes</h2>');
  801. }
  802. if (isset($GlyphByClass[1]) && count($GlyphByClass[1]) > 0) {
  803. $this->GlyphClassBases = $this->formatClassArr($GlyphByClass[1]);
  804. if ($this->mode == 'summary') {
  805. $this->mpdf->WriteHTML('<h3>Glyph class 1</h3>');
  806. $this->mpdf->WriteHTML('<h5>Base glyph (single character, spacing glyph)</h5>');
  807. $html = '';
  808. $html .= '<div class="glyphs">';
  809. foreach ($GlyphByClass[1] AS $g) {
  810. $html .= '&#x' . $g . '; ';
  811. }
  812. $html .= '</div>';
  813. $this->mpdf->WriteHTML($html);
  814. }
  815. } else {
  816. $this->GlyphClassBases = '';
  817. }
  818. if (isset($GlyphByClass[2]) && count($GlyphByClass[2]) > 0) {
  819. $this->GlyphClassLigatures = $this->formatClassArr($GlyphByClass[2]);
  820. if ($this->mode == 'summary') {
  821. $this->mpdf->WriteHTML('<h3>Glyph class 2</h3>');
  822. $this->mpdf->WriteHTML('<h5>Ligature glyph (multiple character, spacing glyph)</h5>');
  823. $html = '';
  824. $html .= '<div class="glyphs">';
  825. foreach ($GlyphByClass[2] AS $g) {
  826. $html .= '&#x' . $g . '; ';
  827. }
  828. $html .= '</div>';
  829. $this->mpdf->WriteHTML($html);
  830. }
  831. } else {
  832. $this->GlyphClassLigatures = '';
  833. }
  834. if (isset($GlyphByClass[3]) && count($GlyphByClass[3]) > 0) {
  835. $this->GlyphClassMarks = $this->formatClassArr($GlyphByClass[3]);
  836. if ($this->mode == 'summary') {
  837. $this->mpdf->WriteHTML('<h3>Glyph class 3</h3>');
  838. $this->mpdf->WriteHTML('<h5>Mark glyph (non-spacing combining glyph)</h5>');
  839. $html = '';
  840. $html .= '<div class="glyphs">';
  841. foreach ($GlyphByClass[3] AS $g) {
  842. $html .= '&#x25cc;&#x' . $g . '; ';
  843. }
  844. $html .= '</div>';
  845. $this->mpdf->WriteHTML($html);
  846. }
  847. } else {
  848. $this->GlyphClassMarks = '';
  849. }
  850. if (isset($GlyphByClass[4]) && count($GlyphByClass[4]) > 0) {
  851. $this->GlyphClassComponents = $this->formatClassArr($GlyphByClass[4]);
  852. if ($this->mode == 'summary') {
  853. $this->mpdf->WriteHTML('<h3>Glyph class 4</h3>');
  854. $this->mpdf->WriteHTML('<h5>Component glyph (part of single character, spacing glyph)</h5>');
  855. $html = '';
  856. $html .= '<div class="glyphs">';
  857. foreach ($GlyphByClass[4] AS $g) {
  858. $html .= '&#x' . $g . '; ';
  859. }
  860. $html .= '</div>';
  861. $this->mpdf->WriteHTML($html);
  862. }
  863. } else {
  864. $this->GlyphClassComponents = '';
  865. }
  866. $Marks = $GlyphByClass[3]; // to use for MarkAttachmentType
  867. /* Required for GPOS
  868. // Attachment List
  869. if ($AttachList_offset) {
  870. $this->seek($gdef_offset+$AttachList_offset );
  871. }
  872. The Attachment Point List table (AttachmentList) identifies all the attachment points defined in the GPOS table and their associated glyphs so a client can quickly access coordinates for each glyph's attachment points. As a result, the client can cache coordinates for attachment points along with glyph bitmaps and avoid recalculating the attachment points each time it displays a glyph. Without this table, processing speed would be slower because the client would have to decode the GPOS lookups that define attachment points and compile the points in a list.
  873. The Attachment List table (AttachList) may be used to cache attachment point coordinates along with glyph bitmaps.
  874. The table consists of an offset to a Coverage table (Coverage) listing all glyphs that define attachment points in the GPOS table, a count of the glyphs with attachment points (GlyphCount), and an array of offsets to AttachPoint tables (AttachPoint). The array lists the AttachPoint tables, one for each glyph in the Coverage table, in the same order as the Coverage Index.
  875. AttachList table
  876. Type Name Description
  877. Offset Coverage Offset to Coverage table - from beginning of AttachList table
  878. uint16 GlyphCount Number of glyphs with attachment points
  879. Offset AttachPoint[GlyphCount] Array of offsets to AttachPoint tables-from beginning of AttachList table-in Coverage Index order
  880. An AttachPoint table consists of a count of the attachment points on a single glyph (PointCount) and an array of contour indices of those points (PointIndex), listed in increasing numerical order.
  881. AttachPoint table
  882. Type Name Description
  883. uint16 PointCount Number of attachment points on this glyph
  884. uint16 PointIndex[PointCount] Array of contour point indices -in increasing numerical order
  885. See Example 3 - http://www.microsoft.com/typography/otspec/gdef.htm
  886. */
  887. // Ligature Caret List
  888. // The Ligature Caret List table (LigCaretList) defines caret positions for all the ligatures in a font.
  889. // Not required for mDPF
  890. // MarkAttachmentType
  891. if ($MarkAttachClassDef_offset) {
  892. if ($this->mode == 'summary') {
  893. $this->mpdf->WriteHTML('<h1>Mark Attachment Types</h1>');
  894. }
  895. $this->seek($gdef_offset + $MarkAttachClassDef_offset);
  896. $MarkAttachmentTypes = $this->_getClassDefinitionTable();
  897. foreach ($MarkAttachmentTypes AS $class => $glyphs) {
  898. if (is_array($Marks) && count($Marks)) {
  899. $mat = array_diff($Marks, $MarkAttachmentTypes[$class]);
  900. sort($mat, SORT_STRING);
  901. } else {
  902. $mat = array();
  903. }
  904. $this->MarkAttachmentType[$class] = $this->formatClassArr($mat);
  905. if ($this->mode == 'summary') {
  906. $this->mpdf->WriteHTML('<h3>Mark Attachment Type: ' . $class . '</h3>');
  907. $html = '';
  908. $html .= '<div class="glyphs">';
  909. foreach ($glyphs AS $g) {
  910. $html .= '&#x25cc;&#x' . $g . '; ';
  911. }
  912. $html .= '</div>';
  913. $this->mpdf->WriteHTML($html);
  914. }
  915. }
  916. } else {
  917. $this->MarkAttachmentType = array();
  918. }
  919. // MarkGlyphSets only in Version 0x00010002 of GDEF
  920. if ($ver_min == 2 && $MarkGlyphSetsDef_offset) {
  921. if ($this->mode == 'summary') {
  922. $this->mpdf->WriteHTML('<h1>Mark Glyph Sets</h1>');
  923. }
  924. $this->seek($gdef_offset + $MarkGlyphSetsDef_offset);
  925. $MarkSetTableFormat = $this->read_ushort();
  926. $MarkSetCount = $this->read_ushort();
  927. $MarkSetOffset = array();
  928. for ($i = 0; $i < $MarkSetCount; $i++) {
  929. $MarkSetOffset[] = $this->read_ulong();
  930. }
  931. for ($i = 0; $i < $MarkSetCount; $i++) {
  932. $this->seek($MarkSetOffset[$i]);
  933. $glyphs = $this->_getCoverage();
  934. $this->MarkGlyphSets[$i] = $this->formatClassArr($glyphs);
  935. if ($this->mode == 'summary') {
  936. $this->mpdf->WriteHTML('<h3>Mark Glyph Set class: ' . $i . '</h3>');
  937. $html = '';
  938. $html .= '<div class="glyphs">';
  939. foreach ($glyphs AS $g) {
  940. $html .= '&#x25cc;&#x' . $g . '; ';
  941. }
  942. $html .= '</div>';
  943. $this->mpdf->WriteHTML($html);
  944. }
  945. }
  946. } else {
  947. $this->MarkGlyphSets = array();
  948. }
  949. } else {
  950. $this->mpdf->WriteHTML('<div>GDEF table not defined</div>');
  951. }
  952. //echo $this->GlyphClassMarks ; exit;
  953. //print_r($GlyphClass); exit;
  954. //print_r($GlyphByClass); exit;
  955. }
  956. function _getClassDefinitionTable($offset = 0)
  957. {
  958. if ($offset > 0) {
  959. $this->seek($offset);
  960. }
  961. // NB Any glyph not included in the range of covered GlyphIDs automatically belongs to Class 0. This is not returned by this function
  962. $ClassFormat = $this->read_ushort();
  963. $GlyphByClass = array();
  964. if ($ClassFormat == 1) {
  965. $StartGlyph = $this->read_ushort();
  966. $GlyphCount = $this->read_ushort();
  967. for ($i = 0; $i < $GlyphCount; $i++) {
  968. $gid = $StartGlyph + $i;
  969. $class = $this->read_ushort();
  970. $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]);
  971. }
  972. } else if ($ClassFormat == 2) {
  973. $tableCount = $this->read_ushort();
  974. for ($i = 0; $i < $tableCount; $i++) {
  975. $startGlyphID = $this->read_ushort();
  976. $endGlyphID = $this->read_ushort();
  977. $class = $this->read_ushort();
  978. for ($gid = $startGlyphID; $gid <= $endGlyphID; $gid++) {
  979. $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]);
  980. }
  981. }
  982. }
  983. ksort($GlyphByClass);
  984. return $GlyphByClass;
  985. }
  986. function _getGSUBtables()
  987. {
  988. ///////////////////////////////////
  989. // GSUB - Glyph Substitution
  990. ///////////////////////////////////
  991. if (isset($this->tables["GSUB"])) {
  992. $this->mpdf->WriteHTML('<h1>GSUB Tables</h1>');
  993. $ffeats = array();
  994. $gsub_offset = $this->seek_table("GSUB");
  995. $this->skip(4);
  996. $ScriptList_offset = $gsub_offset + $this->read_ushort();
  997. $FeatureList_offset = $gsub_offset + $this->read_ushort();
  998. $LookupList_offset = $gsub_offset + $this->read_ushort();
  999. // ScriptList
  1000. $this->seek($ScriptList_offset);
  1001. $ScriptCount = $this->read_ushort();
  1002. for ($i = 0; $i < $ScriptCount; $i++) {
  1003. $ScriptTag = $this->read_tag(); // = "beng", "deva" etc.
  1004. $ScriptTableOffset = $this->read_ushort();
  1005. $ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset;
  1006. }
  1007. // Script Table
  1008. foreach ($ffeats AS $t => $o) {
  1009. $ls = array();
  1010. $this->seek($o);
  1011. $DefLangSys_offset = $this->read_ushort();
  1012. if ($DefLangSys_offset > 0) {
  1013. $ls['DFLT'] = $DefLangSys_offset + $o;
  1014. }
  1015. $LangSysCount = $this->read_ushort();
  1016. for ($i = 0; $i < $LangSysCount; $i++) {
  1017. $LangTag = $this->read_tag(); // =
  1018. $LangTableOffset = $this->read_ushort();
  1019. $ls[$LangTag] = $o + $LangTableOffset;
  1020. }
  1021. $ffeats[$t] = $ls;
  1022. }
  1023. //print_r($ffeats); exit;
  1024. // Get FeatureIndexList
  1025. // LangSys Table - from first listed langsys
  1026. foreach ($ffeats AS $st => $scripts) {
  1027. foreach ($scripts AS $t => $o) {
  1028. $FeatureIndex = array();
  1029. $langsystable_offset = $o;
  1030. $this->seek($langsystable_offset);
  1031. $LookUpOrder = $this->read_ushort(); //==NULL
  1032. $ReqFeatureIndex = $this->read_ushort();
  1033. if ($ReqFeatureIndex != 0xFFFF) {
  1034. $FeatureIndex[] = $ReqFeatureIndex;
  1035. }
  1036. $FeatureCount = $this->read_ushort();
  1037. for ($i = 0; $i < $FeatureCount; $i++) {
  1038. $FeatureIndex[] = $this->read_ushort(); // = index of feature
  1039. }
  1040. $ffeats[$st][$t] = $FeatureIndex;
  1041. }
  1042. }
  1043. //print_r($ffeats); exit;
  1044. // Feauture List => LookupListIndex es
  1045. $this->seek($FeatureList_offset);
  1046. $FeatureCount = $this->read_ushort();
  1047. $Feature = array();
  1048. for ($i = 0; $i < $FeatureCount; $i++) {
  1049. $Feature[$i] = array('tag' => $this->read_tag());
  1050. $Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort();
  1051. }
  1052. for ($i = 0; $i < $FeatureCount; $i++) {
  1053. $this->seek($Feature[$i]['offset']);
  1054. $this->read_ushort(); // null
  1055. $Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort();
  1056. $Feature[$i]['LookupListIndex'] = array();
  1057. for ($c = 0; $c < $Lookupcount; $c++) {
  1058. $Feature[$i]['LookupListIndex'][] = $this->read_ushort();
  1059. }
  1060. }
  1061. foreach ($ffeats AS $st => $scripts) {
  1062. foreach ($scripts AS $t => $o) {
  1063. $FeatureIndex = $ffeats[$st][$t];
  1064. foreach ($FeatureIndex AS $k => $fi) {
  1065. $ffeats[$st][$t][$k] = $Feature[$fi];
  1066. }
  1067. }
  1068. }
  1069. //=====================================================================================
  1070. $gsub = array();
  1071. $GSUBScriptLang = array();
  1072. foreach ($ffeats AS $st => $scripts) {
  1073. foreach ($scripts AS $t => $langsys) {
  1074. $lg = array();
  1075. foreach ($langsys AS $ft) {
  1076. $lg[$ft['LookupListIndex'][0]] = $ft;
  1077. }
  1078. // list of Lookups in order they need to be run i.e. order listed in Lookup table
  1079. ksort($lg);
  1080. foreach ($lg AS $ft) {
  1081. $gsub[$st][$t][$ft['tag']] = $ft['LookupListIndex'];
  1082. }
  1083. if (!isset($GSUBScriptLang[$st])) {
  1084. $GSUBScriptLang[$st] = '';
  1085. }
  1086. $GSUBScriptLang[$st] .= $t . ' ';
  1087. }
  1088. }
  1089. //print_r($gsub); exit;
  1090. if ($this->mode == 'summary') {
  1091. $this->mpdf->WriteHTML('<h3>GSUB Scripts &amp; Languages</h3>');
  1092. $this->mpdf->WriteHTML('<div class="glyphs">');
  1093. $html = '';
  1094. if (count($gsub)) {
  1095. foreach ($gsub AS $st => $g) {
  1096. $html .= '<h5>' . $st . '</h5>';
  1097. foreach ($g AS $l => $t) {
  1098. $html .= '<div><a href="font_dump_OTL.php?script=' . $st . '&lang=' . $l . '">' . $l . '</a></b>: ';
  1099. foreach ($t AS $tag => $o) {
  1100. $html .= $tag . ' ';
  1101. }
  1102. $html .= '</div>';
  1103. }
  1104. }
  1105. } else {
  1106. $html .= '<div>No entries in GSUB table.</div>';
  1107. }
  1108. $this->mpdf->WriteHTML($html);
  1109. $this->mpdf->WriteHTML('</div>');
  1110. return 0;
  1111. }
  1112. //=====================================================================================
  1113. // Get metadata and offsets for whole Lookup List table
  1114. $this->seek($LookupList_offset);
  1115. $LookupCount = $this->read_ushort();
  1116. $GSLookup = array();
  1117. $Offsets = array();
  1118. $SubtableCount = array();
  1119. for ($i = 0; $i < $LookupCount; $i++) {
  1120. $Offsets[$i] = $LookupList_offset + $this->read_ushort();
  1121. }
  1122. for ($i = 0; $i < $LookupCount; $i++) {
  1123. $this->seek($Offsets[$i]);
  1124. $GSLookup[$i]['Type'] = $this->read_ushort();
  1125. $GSLookup[$i]['Flag'] = $flag = $this->read_ushort();
  1126. $GSLookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort();
  1127. for ($c = 0; $c < $SubtableCount[$i]; $c++) {
  1128. $GSLookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort();
  1129. }
  1130. // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure
  1131. if (($flag & 0x0010) == 0x0010) {
  1132. $GSLookup[$i]['MarkFilteringSet'] = $this->read_ushort();
  1133. }
  1134. // else { $GSLookup[$i]['MarkFilteringSet'] = ''; }
  1135. // Lookup Type 7: Extension
  1136. if ($GSLookup[$i]['Type'] == 7) {
  1137. // Overwrites new offset (32-bit) for each subtable, and a new lookup Type
  1138. for ($c = 0; $c < $SubtableCount[$i]; $c++) {
  1139. $this->seek($GSLookup[$i]['Subtables'][$c]);
  1140. $ExtensionPosFormat = $this->read_ushort();
  1141. $type = $this->read_ushort();
  1142. $GSLookup[$i]['Subtables'][$c] = $GSLookup[$i]['Subtables'][$c] + $this->read_ulong();
  1143. }
  1144. $GSLookup[$i]['Type'] = $type;
  1145. }
  1146. }
  1147. //print_r($GSLookup); exit;
  1148. //=====================================================================================
  1149. // Process Whole LookupList - Get LuCoverage = Lookup coverage just for first glyph
  1150. $this->GSLuCoverage = array();
  1151. for ($i = 0; $i < $LookupCount; $i++) {
  1152. for ($c = 0; $c < $GSLookup[$i]['SubtableCount']; $c++) {
  1153. $this->seek($GSLookup[$i]['Subtables'][$c]);
  1154. $PosFormat = $this->read_ushort();
  1155. if ($GSLookup[$i]['Type'] == 5 && $PosFormat == 3) {
  1156. $this->skip(4);
  1157. } else if ($GSLookup[$i]['Type'] == 6 && $PosFormat == 3) {
  1158. $BacktrackGlyphCount = $this->read_ushort();
  1159. $this->skip(2 * $BacktrackGlyphCount + 2);
  1160. }
  1161. // NB Coverage only looks at glyphs for position 1 (i.e. 5.3 and 6.3) // NEEDS TO READ ALL ********************
  1162. $Coverage = $GSLookup[$i]['Subtables'][$c] + $this->read_ushort();
  1163. $this->seek($Coverage);
  1164. $glyphs = $this->_getCoverage();
  1165. $this->GSLuCoverage[$i][$c] = implode('|', $glyphs);
  1166. }
  1167. }
  1168. // $this->GSLuCoverage and $GSLookup
  1169. //=====================================================================================
  1170. $s = '<?php
  1171. $GSLuCoverage = ' . var_export($this->GSLuCoverage, true) . ';
  1172. ?>';
  1173. //=====================================================================================
  1174. $s = '<?php
  1175. $GlyphClassBases = \'' . $this->GlyphClassBases . '\';
  1176. $GlyphClassMarks = \'' . $this->GlyphClassMarks . '\';
  1177. $GlyphClassLigatures = \'' . $this->GlyphClassLigatures . '\';
  1178. $GlyphClassComponents = \'' . $this->GlyphClassComponents . '\';
  1179. $MarkGlyphSets = ' . var_export($this->MarkGlyphSets, true) . ';
  1180. $MarkAttachmentType = ' . var_export($this->MarkAttachmentType, true) . ';
  1181. ?>';
  1182. //=====================================================================================
  1183. //=====================================================================================
  1184. //=====================================================================================
  1185. // Now repeats as original to get Substitution rules
  1186. //=====================================================================================
  1187. //=====================================================================================
  1188. //=====================================================================================
  1189. // Get metadata and offsets for whole Lookup List table
  1190. $this->seek($LookupList_offset);
  1191. $LookupCount = $this->read_ushort();
  1192. $Lookup = array();
  1193. for ($i = 0; $i < $LookupCount; $i++) {
  1194. $Lookup[$i]['offset'] = $LookupList_offset + $this->read_ushort();
  1195. }
  1196. for ($i = 0; $i < $LookupCount; $i++) {
  1197. $this->seek($Lookup[$i]['offset']);
  1198. $Lookup[$i]['Type'] = $this->read_ushort();
  1199. $Lookup[$i]['Flag'] = $flag = $this->read_ushort();
  1200. $Lookup[$i]['SubtableCount'] = $this->read_ushort();
  1201. for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
  1202. $Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['offset'] + $this->read_ushort();
  1203. }
  1204. // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure
  1205. if (($flag & 0x0010) == 0x0010) {
  1206. $Lookup[$i]['MarkFilteringSet'] = $this->read_ushort();
  1207. } else {
  1208. $Lookup[$i]['MarkFilteringSet'] = '';
  1209. }
  1210. // Lookup Type 7: Extension
  1211. if ($Lookup[$i]['Type'] == 7) {
  1212. // Overwrites new offset (32-bit) for each subtable, and a new lookup Type
  1213. for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
  1214. $this->seek($Lookup[$i]['Subtable'][$c]['Offset']);
  1215. $ExtensionPosFormat = $this->read_ushort();
  1216. $type = $this->read_ushort();
  1217. $Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ulong();
  1218. }
  1219. $Lookup[$i]['Type'] = $type;
  1220. }
  1221. }
  1222. //print_r($Lookup); exit;
  1223. //=====================================================================================
  1224. // Process (1) Whole LookupList
  1225. for ($i = 0; $i < $LookupCount; $i++) {
  1226. for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
  1227. $this->seek($Lookup[$i]['Subtable'][$c]['Offset']);
  1228. $SubstFormat = $this->read_ushort();
  1229. $Lookup[$i]['Subtable'][$c]['Format'] = $SubstFormat;
  1230. /*
  1231. Lookup['Type'] Enumeration table for glyph substitution
  1232. Value Type Description
  1233. 1 Single Replace one glyph with one glyph
  1234. 2 Multiple Replace one glyph with more than one glyph
  1235. 3 Alternate Replace one glyph with one of many glyphs
  1236. 4 Ligature Replace multiple glyphs with one glyph
  1237. 5 Context Replace one or more glyphs in context
  1238. 6 Chaining Context Replace one or more glyphs in chained context
  1239. 7 Extension Substitution Extension mechanism for other substitutions (i.e. this excludes the Extension type substitution itself)
  1240. 8 Reverse chaining context single Applied in reverse order, replace single glyph in chaining context
  1241. */
  1242. // LookupType 1: Single Substitution Subtable
  1243. if ($Lookup[$i]['Type'] == 1) {
  1244. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1245. if ($SubstFormat == 1) { // Calculated output glyph indices
  1246. $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'] = $this->read_short();
  1247. } else if ($SubstFormat == 2) { // Specified output glyph indices
  1248. $GlyphCount = $this->read_ushort();
  1249. for ($g = 0; $g < $GlyphCount; $g++) {
  1250. $Lookup[$i]['Subtable'][$c]['Glyphs'][] = $this->read_ushort();
  1251. }
  1252. }
  1253. }
  1254. // LookupType 2: Multiple Substitution Subtable
  1255. else if ($Lookup[$i]['Type'] == 2) {
  1256. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1257. $Lookup[$i]['Subtable'][$c]['SequenceCount'] = $SequenceCount = $this->read_short();
  1258. for ($s = 0; $s < $SequenceCount; $s++) {
  1259. $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
  1260. }
  1261. for ($s = 0; $s < $SequenceCount; $s++) {
  1262. // Sequence Tables
  1263. $this->seek($Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset']);
  1264. $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount'] = $this->read_short();
  1265. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount']; $g++) {
  1266. $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['SubstituteGlyphID'][] = $this->read_ushort();
  1267. }
  1268. }
  1269. }
  1270. // LookupType 3: Alternate Forms
  1271. else if ($Lookup[$i]['Type'] == 3) {
  1272. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1273. $Lookup[$i]['Subtable'][$c]['AlternateSetCount'] = $AlternateSetCount = $this->read_short();
  1274. for ($s = 0; $s < $AlternateSetCount; $s++) {
  1275. $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
  1276. }
  1277. for ($s = 0; $s < $AlternateSetCount; $s++) {
  1278. // AlternateSet Tables
  1279. $this->seek($Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset']);
  1280. $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount'] = $this->read_short();
  1281. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount']; $g++) {
  1282. $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['SubstituteGlyphID'][] = $this->read_ushort();
  1283. }
  1284. }
  1285. }
  1286. // LookupType 4: Ligature Substitution Subtable
  1287. else if ($Lookup[$i]['Type'] == 4) {
  1288. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1289. $Lookup[$i]['Subtable'][$c]['LigSetCount'] = $LigSetCount = $this->read_short();
  1290. for ($s = 0; $s < $LigSetCount; $s++) {
  1291. $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
  1292. }
  1293. for ($s = 0; $s < $LigSetCount; $s++) {
  1294. // LigatureSet Tables
  1295. $this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset']);
  1296. $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount'] = $this->read_short();
  1297. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
  1298. $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g] = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] + $this->read_ushort();
  1299. }
  1300. }
  1301. for ($s = 0; $s < $LigSetCount; $s++) {
  1302. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
  1303. // Ligature tables
  1304. $this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g]);
  1305. $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph'] = $this->read_ushort();
  1306. $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount'] = $this->read_ushort();
  1307. for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) {
  1308. $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l] = $this->read_ushort();
  1309. }
  1310. }
  1311. }
  1312. }
  1313. // LookupType 5: Contextual Substitution Subtable
  1314. else if ($Lookup[$i]['Type'] == 5) {
  1315. // Format 1: Context Substitution
  1316. if ($SubstFormat == 1) {
  1317. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1318. $Lookup[$i]['Subtable'][$c]['SubRuleSetCount'] = $SubRuleSetCount = $this->read_short();
  1319. for ($s = 0; $s < $SubRuleSetCount; $s++) {
  1320. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short();
  1321. }
  1322. for ($s = 0; $s < $SubRuleSetCount; $s++) {
  1323. // SubRuleSet Tables
  1324. $this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset']);
  1325. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount'] = $this->read_short();
  1326. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) {
  1327. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] + $this->read_ushort();
  1328. }
  1329. }
  1330. for ($s = 0; $s < $SubRuleSetCount; $s++) {
  1331. // SubRule Tables
  1332. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) {
  1333. // Ligature tables
  1334. $this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g]);
  1335. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount'] = $this->read_ushort();
  1336. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount'] = $this->read_ushort();
  1337. // "Input"::[GlyphCount - 1]::Array of input GlyphIDs-start with second glyph
  1338. for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount']; $l++) {
  1339. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['Input'][$l] = $this->read_ushort();
  1340. }
  1341. // "SubstLookupRecord"::[SubstCount]::Array of SubstLookupRecords-in design order
  1342. for ($l = 0; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount']; $l++) {
  1343. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['SequenceIndex'] = $this->read_ushort();
  1344. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['LookupListIndex'] = $this->read_ushort();
  1345. }
  1346. }
  1347. }
  1348. }
  1349. // Format 2: Class-based Context Glyph Substitution
  1350. else if ($SubstFormat == 2) {
  1351. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1352. $Lookup[$i]['Subtable'][$c]['ClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1353. $Lookup[$i]['Subtable'][$c]['SubClassSetCnt'] = $this->read_ushort();
  1354. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $b++) {
  1355. $offset = $this->read_ushort();
  1356. if ($offset == 0x0000) {
  1357. $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = 0;
  1358. } else {
  1359. $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset;
  1360. }
  1361. }
  1362. } else {
  1363. throw new MpdfException("GPOS Lookup Type " . $Lookup[$i]['Type'] . ", Format " . $SubstFormat . " not supported (ttfontsuni.php).");
  1364. }
  1365. }
  1366. // LookupType 6: Chaining Contextual Substitution Subtable
  1367. else if ($Lookup[$i]['Type'] == 6) {
  1368. // Format 1: Simple Chaining Context Glyph Substitution p255
  1369. if ($SubstFormat == 1) {
  1370. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1371. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount'] = $this->read_ushort();
  1372. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $b++) {
  1373. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1374. }
  1375. }
  1376. // Format 2: Class-based Chaining Context Glyph Substitution p257
  1377. else if ($SubstFormat == 2) {
  1378. $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1379. $Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1380. $Lookup[$i]['Subtable'][$c]['InputClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1381. $Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1382. $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt'] = $this->read_ushort();
  1383. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $b++) {
  1384. $offset = $this->read_ushort();
  1385. if ($offset == 0x0000) {
  1386. $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $offset;
  1387. } else {
  1388. $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset;
  1389. }
  1390. }
  1391. }
  1392. // Format 3: Coverage-based Chaining Context Glyph Substitution p259
  1393. else if ($SubstFormat == 3) {
  1394. $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount'] = $this->read_ushort();
  1395. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) {
  1396. $Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1397. }
  1398. $Lookup[$i]['Subtable'][$c]['InputGlyphCount'] = $this->read_ushort();
  1399. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) {
  1400. $Lookup[$i]['Subtable'][$c]['CoverageInput'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1401. }
  1402. $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount'] = $this->read_ushort();
  1403. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) {
  1404. $Lookup[$i]['Subtable'][$c]['CoverageLookahead'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort();
  1405. }
  1406. $Lookup[$i]['Subtable'][$c]['SubstCount'] = $this->read_ushort();
  1407. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) {
  1408. $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'] = $this->read_ushort();
  1409. $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'] = $this->read_ushort();
  1410. /*
  1411. Substitution Lookup Record
  1412. All contextual substitution subtables specify the substitution data in a Substitution Lookup Record (SubstLookupRecord). Each record contains a SequenceIndex, which indicates the position where the substitution will occur in the glyph sequence. In addition, a LookupListIndex identifies the lookup to be applied at the glyph position specified by the SequenceIndex.
  1413. */
  1414. }
  1415. }
  1416. } else {
  1417. throw new MpdfException("Lookup Type " . $Lookup[$i]['Type'] . " not supported.");
  1418. }
  1419. }
  1420. }
  1421. //print_r($Lookup); exit;
  1422. //=====================================================================================
  1423. // Process (2) Whole LookupList
  1424. // Get Coverage tables and prepare preg_replace
  1425. for ($i = 0; $i < $LookupCount; $i++) {
  1426. for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
  1427. $SubstFormat = $Lookup[$i]['Subtable'][$c]['Format'];
  1428. // LookupType 1: Single Substitution Subtable 1 => 1
  1429. if ($Lookup[$i]['Type'] == 1) {
  1430. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1431. $glyphs = $this->_getCoverage(false);
  1432. for ($g = 0; $g < count($glyphs); $g++) {
  1433. $replace = array();
  1434. $substitute = array();
  1435. $replace[] = unicode_hex($this->glyphToChar[$glyphs[$g]][0]);
  1436. // Flag = Ignore
  1437. if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
  1438. continue;
  1439. }
  1440. if (isset($Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])) { // Format 1
  1441. $substitute[] = unicode_hex($this->glyphToChar[($glyphs[$g] + $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])][0]);
  1442. } else { // Format 2
  1443. $substitute[] = unicode_hex($this->glyphToChar[($Lookup[$i]['Subtable'][$c]['Glyphs'][$g])][0]);
  1444. }
  1445. $Lookup[$i]['Subtable'][$c]['subs'][] = array('Replace' => $replace, 'substitute' => $substitute);
  1446. }
  1447. }
  1448. // LookupType 2: Multiple Substitution Subtable 1 => n
  1449. else if ($Lookup[$i]['Type'] == 2) {
  1450. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1451. $glyphs = $this->_getCoverage();
  1452. for ($g = 0; $g < count($glyphs); $g++) {
  1453. $replace = array();
  1454. $substitute = array();
  1455. $replace[] = $glyphs[$g];
  1456. // Flag = Ignore
  1457. if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
  1458. continue;
  1459. }
  1460. if (!isset($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) || count($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) == 0) {
  1461. continue;
  1462. } // Illegal for GlyphCount to be 0; either error in font, or something has gone wrong - lets carry on for now!
  1463. foreach ($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID'] AS $sub) {
  1464. $substitute[] = unicode_hex($this->glyphToChar[$sub][0]);
  1465. }
  1466. $Lookup[$i]['Subtable'][$c]['subs'][] = array('Replace' => $replace, 'substitute' => $substitute);
  1467. }
  1468. }
  1469. // LookupType 3: Alternate Forms 1 => 1 (only first alternate form is used)
  1470. else if ($Lookup[$i]['Type'] == 3) {
  1471. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1472. $glyphs = $this->_getCoverage();
  1473. for ($g = 0; $g < count($glyphs); $g++) {
  1474. $replace = array();
  1475. $substitute = array();
  1476. $replace[] = $glyphs[$g];
  1477. // Flag = Ignore
  1478. if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
  1479. continue;
  1480. }
  1481. for ($gl = 0; $gl < $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['GlyphCount']; $gl++) {
  1482. $gid = $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['SubstituteGlyphID'][$gl];
  1483. $substitute[] = unicode_hex($this->glyphToChar[$gid][0]);
  1484. }
  1485. //$gid = $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['SubstituteGlyphID'][0];
  1486. //$substitute[] = unicode_hex($this->glyphToChar[$gid][0]);
  1487. $Lookup[$i]['Subtable'][$c]['subs'][] = array('Replace' => $replace, 'substitute' => $substitute);
  1488. }
  1489. if ($i == 166) {
  1490. print_r($Lookup[$i]['Subtable']);
  1491. exit;
  1492. }
  1493. }
  1494. // LookupType 4: Ligature Substitution Subtable n => 1
  1495. else if ($Lookup[$i]['Type'] == 4) {
  1496. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1497. $glyphs = $this->_getCoverage();
  1498. $LigSetCount = $Lookup[$i]['Subtable'][$c]['LigSetCount'];
  1499. for ($s = 0; $s < $LigSetCount; $s++) {
  1500. for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) {
  1501. $replace = array();
  1502. $substitute = array();
  1503. $replace[] = $glyphs[$s];
  1504. // Flag = Ignore
  1505. if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) {
  1506. continue;
  1507. }
  1508. for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) {
  1509. $gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l];
  1510. $rpl = unicode_hex($this->glyphToChar[$gid][0]);
  1511. // Flag = Ignore
  1512. if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $rpl, $Lookup[$i]['MarkFilteringSet'])) {
  1513. continue 2;
  1514. }
  1515. $replace[] = $rpl;
  1516. }
  1517. $gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph'];
  1518. $substitute[] = unicode_hex($this->glyphToChar[$gid][0]);
  1519. $Lookup[$i]['Subtable'][$c]['subs'][] = array('Replace' => $replace, 'substitute' => $substitute, 'CompCount' => $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']);
  1520. }
  1521. }
  1522. }
  1523. // LookupType 5: Contextual Substitution Subtable
  1524. else if ($Lookup[$i]['Type'] == 5) {
  1525. // Format 1: Context Substitution
  1526. if ($SubstFormat == 1) {
  1527. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1528. $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
  1529. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) {
  1530. $SubRuleSet = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s];
  1531. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph'] = $CoverageGlyphs[$s];
  1532. for ($r = 0; $r < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $r++) {
  1533. $GlyphCount = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['GlyphCount'];
  1534. for ($g = 1; $g < $GlyphCount; $g++) {
  1535. $glyphID = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['Input'][$g];
  1536. $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
  1537. }
  1538. }
  1539. }
  1540. }
  1541. // Format 2: Class-based Context Glyph Substitution
  1542. else if ($SubstFormat == 2) {
  1543. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1544. $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
  1545. $InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['ClassDefOffset']);
  1546. $Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses;
  1547. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) {
  1548. if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) {
  1549. $this->seek($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s]);
  1550. $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt'] = $SubClassRuleCnt = $this->read_ushort();
  1551. $SubClassRule = array();
  1552. for ($b = 0; $b < $SubClassRuleCnt; $b++) {
  1553. $SubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] + $this->read_ushort();
  1554. $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $SubClassRule[$b];
  1555. }
  1556. }
  1557. }
  1558. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) {
  1559. $SubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt'];
  1560. for ($b = 0; $b < $SubClassRuleCnt; $b++) {
  1561. if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) {
  1562. $this->seek($Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b]);
  1563. $Rule = array();
  1564. $Rule['InputGlyphCount'] = $this->read_ushort();
  1565. $Rule['SubstCount'] = $this->read_ushort();
  1566. for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) {
  1567. $Rule['Input'][$r] = $this->read_ushort();
  1568. }
  1569. for ($r = 0; $r < $Rule['SubstCount']; $r++) {
  1570. $Rule['SequenceIndex'][$r] = $this->read_ushort();
  1571. $Rule['LookupListIndex'][$r] = $this->read_ushort();
  1572. }
  1573. $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $Rule;
  1574. }
  1575. }
  1576. }
  1577. }
  1578. // Format 3: Coverage-based Context Glyph Substitution
  1579. else if ($SubstFormat == 3) {
  1580. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) {
  1581. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]);
  1582. $glyphs = $this->_getCoverage();
  1583. $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs);
  1584. }
  1585. throw new MpdfException("Lookup Type 5, SubstFormat 3 not tested. Please report this with the name of font used - " . $this->fontkey);
  1586. }
  1587. }
  1588. // LookupType 6: Chaining Contextual Substitution Subtable
  1589. else if ($Lookup[$i]['Type'] == 6) {
  1590. // Format 1: Simple Chaining Context Glyph Substitution p255
  1591. if ($SubstFormat == 1) {
  1592. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1593. $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
  1594. $ChainSubRuleSetCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount'];
  1595. for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) {
  1596. $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s]);
  1597. $ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount'] = $this->read_ushort();
  1598. for ($r = 0; $r < $ChainSubRuleCnt; $r++) {
  1599. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r] = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s] + $this->read_ushort();
  1600. }
  1601. }
  1602. for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) {
  1603. $ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount'];
  1604. for ($r = 0; $r < $ChainSubRuleCnt; $r++) {
  1605. // ChainSubRule
  1606. $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r]);
  1607. $BacktrackGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphCount'] = $this->read_ushort();
  1608. for ($g = 0; $g < $BacktrackGlyphCount; $g++) {
  1609. $glyphID = $this->read_ushort();
  1610. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
  1611. }
  1612. $InputGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphCount'] = $this->read_ushort();
  1613. for ($g = 1; $g < $InputGlyphCount; $g++) {
  1614. $glyphID = $this->read_ushort();
  1615. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
  1616. }
  1617. $LookaheadGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphCount'] = $this->read_ushort();
  1618. for ($g = 0; $g < $LookaheadGlyphCount; $g++) {
  1619. $glyphID = $this->read_ushort();
  1620. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]);
  1621. }
  1622. $SubstCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SubstCount'] = $this->read_ushort();
  1623. for ($lu = 0; $lu < $SubstCount; $lu++) {
  1624. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SequenceIndex'][$lu] = $this->read_ushort();
  1625. $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookupListIndex'][$lu] = $this->read_ushort();
  1626. }
  1627. }
  1628. }
  1629. }
  1630. // Format 2: Class-based Chaining Context Glyph Substitution p257
  1631. else if ($SubstFormat == 2) {
  1632. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']);
  1633. $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage();
  1634. $BacktrackClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset']);
  1635. $Lookup[$i]['Subtable'][$c]['BacktrackClasses'] = $BacktrackClasses;
  1636. $InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['InputClassDefOffset']);
  1637. $Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses;
  1638. $LookaheadClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset']);
  1639. $Lookup[$i]['Subtable'][$c]['LookaheadClasses'] = $LookaheadClasses;
  1640. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) {
  1641. if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) {
  1642. $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s]);
  1643. $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'] = $ChainSubClassRuleCnt = $this->read_ushort();
  1644. $ChainSubClassRule = array();
  1645. for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) {
  1646. $ChainSubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] + $this->read_ushort();
  1647. $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $ChainSubClassRule[$b];
  1648. }
  1649. }
  1650. }
  1651. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) {
  1652. $ChainSubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'];
  1653. for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) {
  1654. if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) {
  1655. $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b]);
  1656. $Rule = array();
  1657. $Rule['BacktrackGlyphCount'] = $this->read_ushort();
  1658. for ($r = 0; $r < $Rule['BacktrackGlyphCount']; $r++) {
  1659. $Rule['Backtrack'][$r] = $this->read_ushort();
  1660. }
  1661. $Rule['InputGlyphCount'] = $this->read_ushort();
  1662. for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) {
  1663. $Rule['Input'][$r] = $this->read_ushort();
  1664. }
  1665. $Rule['LookaheadGlyphCount'] = $this->read_ushort();
  1666. for ($r = 0; $r < $Rule['LookaheadGlyphCount']; $r++) {
  1667. $Rule['Lookahead'][$r] = $this->read_ushort();
  1668. }
  1669. $Rule['SubstCount'] = $this->read_ushort();
  1670. for ($r = 0; $r < $Rule['SubstCount']; $r++) {
  1671. $Rule['SequenceIndex'][$r] = $this->read_ushort();
  1672. $Rule['LookupListIndex'][$r] = $this->read_ushort();
  1673. }
  1674. $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $Rule;
  1675. }
  1676. }
  1677. }
  1678. }
  1679. // Format 3: Coverage-based Chaining Context Glyph Substitution p259
  1680. else if ($SubstFormat == 3) {
  1681. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) {
  1682. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][$b]);
  1683. $glyphs = $this->_getCoverage();
  1684. $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'][] = implode("|", $glyphs);
  1685. }
  1686. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) {
  1687. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]);
  1688. $glyphs = $this->_getCoverage();
  1689. $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs);
  1690. // Don't use above value as these are ordered numerically not as need to process
  1691. }
  1692. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) {
  1693. $this->seek($Lookup[$i]['Subtable'][$c]['CoverageLookahead'][$b]);
  1694. $glyphs = $this->_getCoverage();
  1695. $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'][] = implode("|", $glyphs);
  1696. }
  1697. }
  1698. }
  1699. }
  1700. }
  1701. //=====================================================================================
  1702. //=====================================================================================
  1703. //=====================================================================================
  1704. $st = $this->mpdf->OTLscript;
  1705. $t = $this->mpdf->OTLlang;
  1706. $langsys = $gsub[$st][$t];
  1707. $lul = array(); // array of LookupListIndexes
  1708. $tags = array(); // corresponding array of feature tags e.g. 'ccmp'
  1709. foreach ($langsys AS $tag => $ft) {
  1710. foreach ($ft AS $ll) {
  1711. $lul[$ll] = $tag;
  1712. }
  1713. }
  1714. ksort($lul); // Order the Lookups in the order they are in the GUSB table, regardless of Feature order
  1715. $this->_getGSUBarray($Lookup, $lul, $st);
  1716. //print_r($lul); exit;
  1717. }
  1718. //print_r($Lookup); exit;
  1719. return array($GSUBScriptLang, $gsub, $GSLookup, $rtlPUAstr, $rtlPUAarr);
  1720. }
  1721. /////////////////////////////////////////////////////////////////////////////////////////
  1722. // GSUB functions
  1723. function _getGSUBarray(&$Lookup, &$lul, $scripttag, $level = 1, $coverage = '', $exB = '', $exL = '')
  1724. {
  1725. // Process (3) LookupList for specific Script-LangSys
  1726. // Generate preg_replace
  1727. $html = '';
  1728. if ($level == 1) {
  1729. $html .= '<bookmark level="0" content="GSUB features">';
  1730. }
  1731. foreach ($lul AS $i => $tag) {
  1732. $html .= '<div class="level' . $level . '">';
  1733. $html .= '<h5 class="level' . $level . '">';
  1734. if ($level == 1) {
  1735. $html .= '<bookmark level="1" content="' . $tag . ' [#' . $i . ']">';
  1736. }
  1737. $html .= 'Lookup #' . $i . ' [tag: <span style="color:#000066;">' . $tag . '</span>]</h5>';
  1738. $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']);
  1739. if ($ignore) {
  1740. $html .= '<div class="ignore">Ignoring: ' . $ignore . '</div> ';
  1741. }
  1742. $Type = $Lookup[$i]['Type'];
  1743. $Flag = $Lookup[$i]['Flag'];
  1744. if (($Flag & 0x0001) == 1) {
  1745. $dir = 'RTL';
  1746. } else {
  1747. $dir = 'LTR';
  1748. }
  1749. for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) {
  1750. $html .= '<div class="subtable">Subtable #' . $c;
  1751. if ($level == 1) {
  1752. $html .= '<bookmark level="2" content="Subtable #' . $c . '">';
  1753. }
  1754. $html .= '</div>';
  1755. $SubstFormat = $Lookup[$i]['Subtable'][$c]['Format'];
  1756. // LookupType 1: Single Substitution Subtable
  1757. if ($Lookup[$i]['Type'] == 1) {
  1758. $html .= '<div class="lookuptype">LookupType 1: Single Substitution Subtable</div>';
  1759. for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
  1760. $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
  1761. $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
  1762. if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) {
  1763. continue;
  1764. }
  1765. $html .= '<div class="substitution">';
  1766. $html .= '<span class="unicode">' . $this->formatUni($inputGlyphs[0]) . '&nbsp;</span> ';
  1767. if ($level == 2 && $exB) {
  1768. $html .= $exB;
  1769. }
  1770. $html .= '<span class="unchanged">&nbsp;' . $this->formatEntity($inputGlyphs[0]) . '</span>';
  1771. if ($level == 2 && $exL) {
  1772. $html .= $exL;
  1773. }
  1774. $html .= '&nbsp; &raquo; &raquo; &nbsp;';
  1775. if ($level == 2 && $exB) {
  1776. $html .= $exB;
  1777. }
  1778. $html .= '<span class="changed">&nbsp;' . $this->formatEntity($substitute) . '</span>';
  1779. if ($level == 2 && $exL) {
  1780. $html .= $exL;
  1781. }
  1782. $html .= '&nbsp; <span class="unicode">' . $this->formatUni($substitute) . '</span> ';
  1783. $html .= '</div>';
  1784. }
  1785. }
  1786. // LookupType 2: Multiple Substitution Subtable
  1787. else if ($Lookup[$i]['Type'] == 2) {
  1788. $html .= '<div class="lookuptype">LookupType 2: Multiple Substitution Subtable</div>';
  1789. for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
  1790. $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
  1791. $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'];
  1792. if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) {
  1793. continue;
  1794. }
  1795. $html .= '<div class="substitution">';
  1796. $html .= '<span class="unicode">' . $this->formatUni($inputGlyphs[0]) . '&nbsp;</span> ';
  1797. if ($level == 2 && $exB) {
  1798. $html .= $exB;
  1799. }
  1800. $html .= '<span class="unchanged">&nbsp;' . $this->formatEntity($inputGlyphs[0]) . '</span>';
  1801. if ($level == 2 && $exL) {
  1802. $html .= $exL;
  1803. }
  1804. $html .= '&nbsp; &raquo; &raquo; &nbsp;';
  1805. if ($level == 2 && $exB) {
  1806. $html .= $exB;
  1807. }
  1808. $html .= '<span class="changed">&nbsp;' . $this->formatEntityArr($substitute) . '</span>';
  1809. if ($level == 2 && $exL) {
  1810. $html .= $exL;
  1811. }
  1812. $html .= '&nbsp; <span class="unicode">' . $this->formatUniArr($substitute) . '</span> ';
  1813. $html .= '</div>';
  1814. }
  1815. }
  1816. // LookupType 3: Alternate Forms
  1817. else if ($Lookup[$i]['Type'] == 3) {
  1818. $html .= '<div class="lookuptype">LookupType 3: Alternate Forms</div>';
  1819. for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
  1820. $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
  1821. $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
  1822. if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) {
  1823. continue;
  1824. }
  1825. $html .= '<div class="substitution">';
  1826. $html .= '<span class="unicode">' . $this->formatUni($inputGlyphs[0]) . '&nbsp;</span> ';
  1827. if ($level == 2 && $exB) {
  1828. $html .= $exB;
  1829. }
  1830. $html .= '<span class="unchanged">&nbsp;' . $this->formatEntity($inputGlyphs[0]) . '</span>';
  1831. if ($level == 2 && $exL) {
  1832. $html .= $exL;
  1833. }
  1834. $html .= '&nbsp; &raquo; &raquo; &nbsp;';
  1835. if ($level == 2 && $exB) {
  1836. $html .= $exB;
  1837. }
  1838. $html .= '<span class="changed">&nbsp;' . $this->formatEntity($substitute) . '</span>';
  1839. if ($level == 2 && $exL) {
  1840. $html .= $exL;
  1841. }
  1842. $html .= '&nbsp; <span class="unicode">' . $this->formatUni($substitute) . '</span> ';
  1843. if (count($Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']) > 1) {
  1844. for ($alt = 1; $alt < count($Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']); $alt++) {
  1845. $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][$alt];
  1846. $html .= '&nbsp; | &nbsp; ALT #' . $alt . ' &nbsp; ';
  1847. $html .= '<span class="changed">&nbsp;' . $this->formatEntity($substitute) . '</span>';
  1848. $html .= '&nbsp; <span class="unicode">' . $this->formatUni($substitute) . '</span> ';
  1849. }
  1850. }
  1851. $html .= '</div>';
  1852. }
  1853. }
  1854. // LookupType 4: Ligature Substitution Subtable
  1855. else if ($Lookup[$i]['Type'] == 4) {
  1856. $html .= '<div class="lookuptype">LookupType 4: Ligature Substitution Subtable</div>';
  1857. for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
  1858. $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
  1859. $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
  1860. if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) {
  1861. continue;
  1862. }
  1863. $html .= '<div class="substitution">';
  1864. $html .= '<span class="unicode">' . $this->formatUniArr($inputGlyphs) . '&nbsp;</span> ';
  1865. if ($level == 2 && $exB) {
  1866. $html .= $exB;
  1867. }
  1868. $html .= '<span class="unchanged">&nbsp;' . $this->formatEntityArr($inputGlyphs) . '</span>';
  1869. if ($level == 2 && $exL) {
  1870. $html .= $exL;
  1871. }
  1872. $html .= '&nbsp; &raquo; &raquo; &nbsp;';
  1873. if ($level == 2 && $exB) {
  1874. $html .= $exB;
  1875. }
  1876. $html .= '<span class="changed">&nbsp;' . $this->formatEntity($substitute) . '</span>';
  1877. if ($level == 2 && $exL) {
  1878. $html .= $exL;
  1879. }
  1880. $html .= '&nbsp; <span class="unicode">' . $this->formatUni($substitute) . '</span> ';
  1881. $html .= '</div>';
  1882. }
  1883. }
  1884. // LookupType 5: Contextual Substitution Subtable
  1885. else if ($Lookup[$i]['Type'] == 5) {
  1886. $html .= '<div class="lookuptype">LookupType 5: Contextual Substitution Subtable</div>';
  1887. // Format 1: Context Substitution
  1888. if ($SubstFormat == 1) {
  1889. $html .= '<div class="lookuptypesub">Format 1: Context Substitution</div>';
  1890. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) {
  1891. // SubRuleSet
  1892. $subRule = array();
  1893. $html .= '<div class="rule">Subrule Set: ' . $s . '</div>';
  1894. foreach ($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'] AS $rctr => $rule) {
  1895. // SubRule
  1896. $html .= '<div class="rule">SubRule: ' . $rctr . '</div>';
  1897. $inputGlyphs = array();
  1898. if ($rule['GlyphCount'] > 1) {
  1899. $inputGlyphs = $rule['InputGlyphs'];
  1900. }
  1901. $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph'];
  1902. ksort($inputGlyphs);
  1903. $nInput = count($inputGlyphs);
  1904. $exampleI = array();
  1905. $html .= '<div class="context">CONTEXT: ';
  1906. for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
  1907. $html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
  1908. $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
  1909. }
  1910. $html .= '</div>';
  1911. for ($b = 0; $b < $rule['SubstCount']; $b++) {
  1912. $lup = $rule['SubstLookupRecord'][$b]['LookupListIndex'];
  1913. $seqIndex = $rule['SubstLookupRecord'][$b]['SequenceIndex'];
  1914. // GENERATE exampleI[<seqIndex] .... exampleI[>seqIndex]
  1915. $exB = '';
  1916. $exL = '';
  1917. if ($seqIndex > 0) {
  1918. $exB .= '<span class="inputother">';
  1919. for ($ip = 0; $ip < $seqIndex; $ip++) {
  1920. $exB .= $this->formatEntity($inputGlyphs[$ip]) . '&#x200d;';
  1921. }
  1922. $exB .= '</span>';
  1923. }
  1924. if (count($inputGlyphs) > ($seqIndex + 1)) {
  1925. $exL .= '<span class="inputother">';
  1926. for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
  1927. $exL .= $this->formatEntity($inputGlyphs[$ip]) . '&#x200d;';
  1928. }
  1929. $exL .= '</span>';
  1930. }
  1931. $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
  1932. $lul2 = array($lup => $tag);
  1933. // Only apply if the (first) 'Replace' glyph from the
  1934. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  1935. // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
  1936. // to level 2 and only apply if first Replace glyph is in this list
  1937. $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
  1938. }
  1939. if (count($subRule['rules']))
  1940. $volt[] = $subRule;
  1941. }
  1942. }
  1943. }
  1944. // Format 2: Class-based Context Glyph Substitution
  1945. else if ($SubstFormat == 2) {
  1946. $html .= '<div class="lookuptypesub">Format 2: Class-based Context Glyph Substitution</div>';
  1947. foreach ($Lookup[$i]['Subtable'][$c]['SubClassSet'] AS $inputClass => $cscs) {
  1948. $html .= '<div class="rule">Input Class: ' . $inputClass . '</div>';
  1949. for ($cscrule = 0; $cscrule < $cscs['SubClassRuleCnt']; $cscrule++) {
  1950. $html .= '<div class="rule">Rule: ' . $cscrule . '</div>';
  1951. $rule = $cscs['SubClassRule'][$cscrule];
  1952. $inputGlyphs = array();
  1953. $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass];
  1954. if ($rule['InputGlyphCount'] > 1) {
  1955. // NB starts at 1
  1956. for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) {
  1957. $classindex = $rule['Input'][$gcl];
  1958. $inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex];
  1959. }
  1960. }
  1961. // Class 0 contains all the glyphs NOT in the other classes
  1962. $class0excl = implode('|', $Lookup[$i]['Subtable'][$c]['InputClasses']);
  1963. $exampleI = array();
  1964. $html .= '<div class="context">CONTEXT: ';
  1965. for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
  1966. if (!$inputGlyphs[$ff]) {
  1967. $html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;[NOT ' . $this->formatEntityStr($class0excl) . ']&nbsp;</span></div>';
  1968. $exampleI[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']';
  1969. } else {
  1970. $html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
  1971. $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
  1972. }
  1973. }
  1974. $html .= '</div>';
  1975. for ($b = 0; $b < $rule['SubstCount']; $b++) {
  1976. $lup = $rule['LookupListIndex'][$b];
  1977. $seqIndex = $rule['SequenceIndex'][$b];
  1978. // GENERATE exampleI[<seqIndex] .... exampleI[>seqIndex]
  1979. $exB = '';
  1980. $exL = '';
  1981. if ($seqIndex > 0) {
  1982. $exB .= '<span class="inputother">';
  1983. for ($ip = 0; $ip < $seqIndex; $ip++) {
  1984. if (!$inputGlyphs[$ip]) {
  1985. $exB .= '[*]';
  1986. } else {
  1987. $exB .= $this->formatEntityFirst($inputGlyphs[$ip]) . '&#x200d;';
  1988. }
  1989. }
  1990. $exB .= '</span>';
  1991. }
  1992. if (count($inputGlyphs) > ($seqIndex + 1)) {
  1993. $exL .= '<span class="inputother">';
  1994. for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
  1995. if (!$inputGlyphs[$ip]) {
  1996. $exL .= '[*]';
  1997. } else {
  1998. $exL .= $this->formatEntityFirst($inputGlyphs[$ip]) . '&#x200d;';
  1999. }
  2000. }
  2001. $exL .= '</span>';
  2002. }
  2003. $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
  2004. $lul2 = array($lup => $tag);
  2005. // Only apply if the (first) 'Replace' glyph from the
  2006. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  2007. // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
  2008. // to level 2 and only apply if first Replace glyph is in this list
  2009. $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
  2010. }
  2011. if (count($subRule['rules']))
  2012. $volt[] = $subRule;
  2013. }
  2014. }
  2015. }
  2016. // Format 3: Coverage-based Context Glyph Substitution p259
  2017. else if ($SubstFormat == 3) {
  2018. $html .= '<div class="lookuptypesub">Format 3: Coverage-based Context Glyph Substitution </div>';
  2019. // IgnoreMarks flag set on main Lookup table
  2020. $inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'];
  2021. $CoverageInputGlyphs = implode('|', $inputGlyphs);
  2022. $nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount'];
  2023. $exampleI = array();
  2024. $html .= '<div class="context">CONTEXT: ';
  2025. for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
  2026. $html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
  2027. $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
  2028. }
  2029. $html .= '</div>';
  2030. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) {
  2031. $lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'];
  2032. $seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'];
  2033. // GENERATE exampleI[<seqIndex] .... exampleI[>seqIndex]
  2034. $exB = '';
  2035. $exL = '';
  2036. if ($seqIndex > 0) {
  2037. $exB .= '<span class="inputother">';
  2038. for ($ip = 0; $ip < $seqIndex; $ip++) {
  2039. $exB .= $exampleI[$ip] . '&#x200d;';
  2040. }
  2041. $exB .= '</span>';
  2042. }
  2043. if (count($inputGlyphs) > ($seqIndex + 1)) {
  2044. $exL .= '<span class="inputother">';
  2045. for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
  2046. $exL .= $exampleI[$ip] . '&#x200d;';
  2047. }
  2048. $exL .= '</span>';
  2049. }
  2050. $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
  2051. $lul2 = array($lup => $tag);
  2052. // Only apply if the (first) 'Replace' glyph from the
  2053. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  2054. // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
  2055. // to level 2 and only apply if first Replace glyph is in this list
  2056. $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
  2057. }
  2058. if (count($subRule['rules']))
  2059. $volt[] = $subRule;
  2060. }
  2061. //print_r($Lookup[$i]);
  2062. //print_r($volt[(count($volt)-1)]); exit;
  2063. }
  2064. // LookupType 6: Chaining Contextual Substitution Subtable
  2065. else if ($Lookup[$i]['Type'] == 6) {
  2066. $html .= '<div class="lookuptype">LookupType 6: Chaining Contextual Substitution Subtable</div>';
  2067. // Format 1: Simple Chaining Context Glyph Substitution p255
  2068. if ($SubstFormat == 1) {
  2069. $html .= '<div class="lookuptypesub">Format 1: Simple Chaining Context Glyph Substitution </div>';
  2070. for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $s++) {
  2071. // ChainSubRuleSet
  2072. $subRule = array();
  2073. $html .= '<div class="rule">Subrule Set: ' . $s . '</div>';
  2074. $firstInputGlyph = $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'][$s]; // First input gyyph
  2075. foreach ($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'] AS $rctr => $rule) {
  2076. $html .= '<div class="rule">SubRule: ' . $rctr . '</div>';
  2077. // ChainSubRule
  2078. $inputGlyphs = array();
  2079. if ($rule['InputGlyphCount'] > 1) {
  2080. $inputGlyphs = $rule['InputGlyphs'];
  2081. }
  2082. $inputGlyphs[0] = $firstInputGlyph;
  2083. ksort($inputGlyphs);
  2084. $nInput = count($inputGlyphs);
  2085. if ($rule['BacktrackGlyphCount']) {
  2086. $backtrackGlyphs = $rule['BacktrackGlyphs'];
  2087. } else {
  2088. $backtrackGlyphs = array();
  2089. }
  2090. if ($rule['LookaheadGlyphCount']) {
  2091. $lookaheadGlyphs = $rule['LookaheadGlyphs'];
  2092. } else {
  2093. $lookaheadGlyphs = array();
  2094. }
  2095. $exampleB = array();
  2096. $exampleI = array();
  2097. $exampleL = array();
  2098. $html .= '<div class="context">CONTEXT: ';
  2099. for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) {
  2100. $html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>';
  2101. $exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]);
  2102. }
  2103. for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
  2104. $html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
  2105. $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
  2106. }
  2107. for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) {
  2108. $html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>';
  2109. $exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]);
  2110. }
  2111. $html .= '</div>';
  2112. for ($b = 0; $b < $rule['SubstCount']; $b++) {
  2113. $lup = $rule['LookupListIndex'][$b];
  2114. $seqIndex = $rule['SequenceIndex'][$b];
  2115. // GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n]
  2116. $exB = '';
  2117. $exL = '';
  2118. if (count($exampleB)) {
  2119. $exB .= '<span class="backtrack">' . implode('&#x200d;', $exampleB) . '</span>';
  2120. }
  2121. if ($seqIndex > 0) {
  2122. $exB .= '<span class="inputother">';
  2123. for ($ip = 0; $ip < $seqIndex; $ip++) {
  2124. $exB .= $this->formatEntity($inputGlyphs[$ip]) . '&#x200d;';
  2125. }
  2126. $exB .= '</span>';
  2127. }
  2128. if (count($inputGlyphs) > ($seqIndex + 1)) {
  2129. $exL .= '<span class="inputother">';
  2130. for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
  2131. $exL .= $this->formatEntity($inputGlyphs[$ip]) . '&#x200d;';
  2132. }
  2133. $exL .= '</span>';
  2134. }
  2135. if (count($exampleL)) {
  2136. $exL .= '<span class="lookahead">' . implode('&#x200d;', $exampleL) . '</span>';
  2137. }
  2138. $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
  2139. $lul2 = array($lup => $tag);
  2140. // Only apply if the (first) 'Replace' glyph from the
  2141. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  2142. // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
  2143. // to level 2 and only apply if first Replace glyph is in this list
  2144. $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
  2145. }
  2146. if (count($subRule['rules']))
  2147. $volt[] = $subRule;
  2148. }
  2149. }
  2150. }
  2151. // Format 2: Class-based Chaining Context Glyph Substitution p257
  2152. else if ($SubstFormat == 2) {
  2153. $html .= '<div class="lookuptypesub">Format 2: Class-based Chaining Context Glyph Substitution </div>';
  2154. foreach ($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'] AS $inputClass => $cscs) {
  2155. $html .= '<div class="rule">Input Class: ' . $inputClass . '</div>';
  2156. for ($cscrule = 0; $cscrule < $cscs['ChainSubClassRuleCnt']; $cscrule++) {
  2157. $html .= '<div class="rule">Rule: ' . $cscrule . '</div>';
  2158. $rule = $cscs['ChainSubClassRule'][$cscrule];
  2159. // These contain classes of glyphs as strings
  2160. // $Lookup[$i]['Subtable'][$c]['InputClasses'][(class)] e.g. 02E6|02E7|02E8
  2161. // $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][(class)]
  2162. // $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][(class)]
  2163. // These contain arrays of classIndexes
  2164. // [Backtrack] [Lookahead] and [Input] (Input is from the second position only)
  2165. $inputGlyphs = array();
  2166. $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass];
  2167. if ($rule['InputGlyphCount'] > 1) {
  2168. // NB starts at 1
  2169. for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) {
  2170. $classindex = $rule['Input'][$gcl];
  2171. $inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex];
  2172. }
  2173. }
  2174. // Class 0 contains all the glyphs NOT in the other classes
  2175. $class0excl = implode('|', $Lookup[$i]['Subtable'][$c]['InputClasses']);
  2176. $nInput = $rule['InputGlyphCount'];
  2177. if ($rule['BacktrackGlyphCount']) {
  2178. for ($gcl = 0; $gcl < $rule['BacktrackGlyphCount']; $gcl++) {
  2179. $classindex = $rule['Backtrack'][$gcl];
  2180. $backtrackGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][$classindex];
  2181. }
  2182. } else {
  2183. $backtrackGlyphs = array();
  2184. }
  2185. if ($rule['LookaheadGlyphCount']) {
  2186. for ($gcl = 0; $gcl < $rule['LookaheadGlyphCount']; $gcl++) {
  2187. $classindex = $rule['Lookahead'][$gcl];
  2188. $lookaheadGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][$classindex];
  2189. }
  2190. } else {
  2191. $lookaheadGlyphs = array();
  2192. }
  2193. $exampleB = array();
  2194. $exampleI = array();
  2195. $exampleL = array();
  2196. $html .= '<div class="context">CONTEXT: ';
  2197. for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) {
  2198. if (!$backtrackGlyphs[$ff]) {
  2199. $html .= '<div>Backtrack #' . $ff . ': <span class="unchanged">&nbsp;[NOT ' . $this->formatEntityStr($class0excl) . ']&nbsp;</span></div>';
  2200. $exampleB[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']';
  2201. } else {
  2202. $html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>';
  2203. $exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]);
  2204. }
  2205. }
  2206. for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
  2207. if (!$inputGlyphs[$ff]) {
  2208. $html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;[NOT ' . $this->formatEntityStr($class0excl) . ']&nbsp;</span></div>';
  2209. $exampleI[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']';
  2210. } else {
  2211. $html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
  2212. $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
  2213. }
  2214. }
  2215. for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) {
  2216. if (!$lookaheadGlyphs[$ff]) {
  2217. $html .= '<div>Lookahead #' . $ff . ': <span class="unchanged">&nbsp;[NOT ' . $this->formatEntityStr($class0excl) . ']&nbsp;</span></div>';
  2218. $exampleL[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']';
  2219. } else {
  2220. $html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>';
  2221. $exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]);
  2222. }
  2223. }
  2224. $html .= '</div>';
  2225. for ($b = 0; $b < $rule['SubstCount']; $b++) {
  2226. $lup = $rule['LookupListIndex'][$b];
  2227. $seqIndex = $rule['SequenceIndex'][$b];
  2228. // GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n]
  2229. $exB = '';
  2230. $exL = '';
  2231. if (count($exampleB)) {
  2232. $exB .= '<span class="backtrack">' . implode('&#x200d;', $exampleB) . '</span>';
  2233. }
  2234. if ($seqIndex > 0) {
  2235. $exB .= '<span class="inputother">';
  2236. for ($ip = 0; $ip < $seqIndex; $ip++) {
  2237. if (!$inputGlyphs[$ip]) {
  2238. $exB .= '[*]';
  2239. } else {
  2240. $exB .= $this->formatEntityFirst($inputGlyphs[$ip]) . '&#x200d;';
  2241. }
  2242. }
  2243. $exB .= '</span>';
  2244. }
  2245. if (count($inputGlyphs) > ($seqIndex + 1)) {
  2246. $exL .= '<span class="inputother">';
  2247. for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
  2248. if (!$inputGlyphs[$ip]) {
  2249. $exL .= '[*]';
  2250. } else {
  2251. $exL .= $this->formatEntityFirst($inputGlyphs[$ip]) . '&#x200d;';
  2252. }
  2253. }
  2254. $exL .= '</span>';
  2255. }
  2256. if (count($exampleL)) {
  2257. $exL .= '<span class="lookahead">' . implode('&#x200d;', $exampleL) . '</span>';
  2258. }
  2259. $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
  2260. $lul2 = array($lup => $tag);
  2261. // Only apply if the (first) 'Replace' glyph from the
  2262. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  2263. // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
  2264. // to level 2 and only apply if first Replace glyph is in this list
  2265. $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
  2266. }
  2267. }
  2268. }
  2269. //print_r($Lookup[$i]['Subtable'][$c]); exit;
  2270. }
  2271. // Format 3: Coverage-based Chaining Context Glyph Substitution p259
  2272. else if ($SubstFormat == 3) {
  2273. $html .= '<div class="lookuptypesub">Format 3: Coverage-based Chaining Context Glyph Substitution </div>';
  2274. // IgnoreMarks flag set on main Lookup table
  2275. $inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'];
  2276. $CoverageInputGlyphs = implode('|', $inputGlyphs);
  2277. $nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount'];
  2278. if ($Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']) {
  2279. $backtrackGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'];
  2280. } else {
  2281. $backtrackGlyphs = array();
  2282. }
  2283. if ($Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']) {
  2284. $lookaheadGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'];
  2285. } else {
  2286. $lookaheadGlyphs = array();
  2287. }
  2288. $exampleB = array();
  2289. $exampleI = array();
  2290. $exampleL = array();
  2291. $html .= '<div class="context">CONTEXT: ';
  2292. for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) {
  2293. $html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>';
  2294. $exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]);
  2295. }
  2296. for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
  2297. $html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
  2298. $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
  2299. }
  2300. for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) {
  2301. $html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>';
  2302. $exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]);
  2303. }
  2304. $html .= '</div>';
  2305. for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) {
  2306. $lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'];
  2307. $seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'];
  2308. // GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n]
  2309. $exB = '';
  2310. $exL = '';
  2311. if (count($exampleB)) {
  2312. $exB .= '<span class="backtrack">' . implode('&#x200d;', $exampleB) . '</span>';
  2313. }
  2314. if ($seqIndex > 0) {
  2315. $exB .= '<span class="inputother">';
  2316. for ($ip = 0; $ip < $seqIndex; $ip++) {
  2317. $exB .= $exampleI[$ip] . '&#x200d;';
  2318. }
  2319. $exB .= '</span>';
  2320. }
  2321. if (count($inputGlyphs) > ($seqIndex + 1)) {
  2322. $exL .= '<span class="inputother">';
  2323. for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
  2324. $exL .= $exampleI[$ip] . '&#x200d;';
  2325. }
  2326. $exL .= '</span>';
  2327. }
  2328. if (count($exampleL)) {
  2329. $exL .= '<span class="lookahead">' . implode('&#x200d;', $exampleL) . '</span>';
  2330. }
  2331. $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
  2332. $lul2 = array($lup => $tag);
  2333. // Only apply if the (first) 'Replace' glyph from the
  2334. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  2335. // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
  2336. // to level 2 and only apply if first Replace glyph is in this list
  2337. $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
  2338. }
  2339. }
  2340. }
  2341. }
  2342. $html .= '</div>';
  2343. }
  2344. if ($level == 1) {
  2345. $this->mpdf->WriteHTML($html);
  2346. } else {
  2347. return $html;
  2348. }
  2349. //print_r($Lookup); exit;
  2350. }
  2351. //=====================================================================================
  2352. //=====================================================================================
  2353. // mPDF 5.7.1
  2354. function _checkGSUBignore($flag, $glyph, $MarkFilteringSet)
  2355. {
  2356. $ignore = false;
  2357. // Flag & 0x0008 = Ignore Marks
  2358. if ((($flag & 0x0008) == 0x0008) && strpos($this->GlyphClassMarks, $glyph)) {
  2359. $ignore = true;
  2360. }
  2361. if ((($flag & 0x0004) == 0x0004) && strpos($this->GlyphClassLigatures, $glyph)) {
  2362. $ignore = true;
  2363. }
  2364. if ((($flag & 0x0002) == 0x0002) && strpos($this->GlyphClassBases, $glyph)) {
  2365. $ignore = true;
  2366. }
  2367. // Flag & 0xFF?? = MarkAttachmentType
  2368. if (($flag & 0xFF00) && strpos($this->MarkAttachmentType[($flag >> 8)], $glyph)) {
  2369. $ignore = true;
  2370. }
  2371. // Flag & 0x0010 = UseMarkFilteringSet
  2372. if (($flag & 0x0010) && strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) {
  2373. $ignore = true;
  2374. }
  2375. return $ignore;
  2376. }
  2377. function _getGSUBignoreString($flag, $MarkFilteringSet)
  2378. {
  2379. // If ignoreFlag set, combine all ignore glyphs into -> "((?:(?: FBA1| FBA2| FBA3))*)"
  2380. // else "()"
  2381. // for Input - set on secondary Lookup table if in Context, and set Backtrack and Lookahead on Context Lookup
  2382. $str = "";
  2383. $ignoreflag = 0;
  2384. // Flag & 0xFF?? = MarkAttachmentType
  2385. if ($flag & 0xFF00) {
  2386. $MarkAttachmentType = $flag >> 8;
  2387. $ignoreflag = $flag;
  2388. //$str = $this->MarkAttachmentType[$MarkAttachmentType];
  2389. $str = "MarkAttachmentType[" . $MarkAttachmentType . "] ";
  2390. }
  2391. // Flag & 0x0010 = UseMarkFilteringSet
  2392. if ($flag & 0x0010) {
  2393. throw new MpdfException("This font " . $this->fontkey . " contains MarkGlyphSets");
  2394. $str = "Mark Glyph Set: ";
  2395. $str .= $this->MarkGlyphSets[$MarkFilteringSet];
  2396. }
  2397. // If Ignore Marks set, supercedes any above
  2398. // Flag & 0x0008 = Ignore Marks
  2399. if (($flag & 0x0008) == 0x0008) {
  2400. $ignoreflag = 8;
  2401. //$str = $this->GlyphClassMarks;
  2402. $str = "Mark Glyphs ";
  2403. }
  2404. // Flag & 0x0004 = Ignore Ligatures
  2405. if (($flag & 0x0004) == 0x0004) {
  2406. $ignoreflag += 4;
  2407. if ($str) {
  2408. $str .= "|";
  2409. }
  2410. //$str .= $this->GlyphClassLigatures;
  2411. $str .= "Ligature Glyphs ";
  2412. }
  2413. // Flag & 0x0002 = Ignore BaseGlyphs
  2414. if (($flag & 0x0002) == 0x0002) {
  2415. $ignoreflag += 2;
  2416. if ($str) {
  2417. $str .= "|";
  2418. }
  2419. //$str .= $this->GlyphClassBases;
  2420. $str .= "Base Glyphs ";
  2421. }
  2422. if ($str) {
  2423. return $str;
  2424. } else
  2425. return "";
  2426. }
  2427. // GSUB Patterns
  2428. /*
  2429. BACKTRACK INPUT LOOKAHEAD
  2430. ================================== ================== ==================================
  2431. (FEEB|FEEC)(ign) ¦(FD12|FD13)(ign) ¦(0612)¦(ign) (0613)¦(ign) (FD12|FD13)¦(ign) (FEEB|FEEC)
  2432. ---------------- ---------------- ----- ------------ --------------- ---------------
  2433. Backtrack 1 Backtrack 2 Input 1 Input 2 Lookahead 1 Lookahead 2
  2434. -------- --- --------- --- ---- --- ---- --- --------- --- -------
  2435. \${1} \${2} \${3} \${4} \${5+} \${6+} \${7+} \${8+}
  2436. nBacktrack = 2 nInput = 2 nLookahead = 2
  2437. nBsubs = 2xnBack nIsubs = (nBsubs+) nLsubs = (nBsubs+nIsubs+) 2xnLookahead
  2438. "\${1}\${2} " (nInput*2)-1 "\${5+} \${6+}"
  2439. "REPL"
  2440. ¦\${1}\${2} ¦\${3}\${4} ¦REPL¦\${5+} \${6+}¦\${7+} \${8+}¦
  2441. INPUT nInput = 5
  2442. ============================================================
  2443. ¦(0612)¦(ign) (0613)¦(ign) (0614)¦(ign) (0615)¦(ign) (0615)¦
  2444. \${1} \${2} \${3} \${4} \${5} \${6} \${7} \${8} \${9} (All backreference numbers are + nBsubs)
  2445. ----- ------------ ------------ ------------ ------------
  2446. Input 1 Input 2 Input 3 Input 4 Input 5
  2447. A====== SequenceIndex=1 ; Lookup match nGlyphs=1
  2448. B=================== SequenceIndex=1 ; Lookup match nGlyphs=2
  2449. C=============================== SequenceIndex=1 ; Lookup match nGlyphs=3
  2450. D======================= SequenceIndex=2 ; Lookup match nGlyphs=2
  2451. E===================================== SequenceIndex=2 ; Lookup match nGlyphs=3
  2452. F====================== SequenceIndex=4 ; Lookup match nGlyphs=2
  2453. All backreference numbers are + nBsubs
  2454. A - "REPL\${2} \${3}\${4} \${5}\${6} \${7}\${8} \${9}"
  2455. B - "REPL\${2}\${4} \${5}\${6} \${7}\${8} \${9}"
  2456. C - "REPL\${2}\${4}\${6} \${7}\${8} \${9}"
  2457. D - "\${1} REPL\${2}\${4}\${6} \${7}\${8} \${9}"
  2458. E - "\${1} REPL\${2}\${4}\${6}\${8} \${9}"
  2459. F - "\${1}\${2} \${3}\${4} \${5} REPL\${6}\${8}"
  2460. */
  2461. function _makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex)
  2462. {
  2463. // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
  2464. // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
  2465. // $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context
  2466. // $lookupGlyphs = array of glyphs (single Glyphs) making up Lookup Input sequence
  2467. $mLen = count($lookupGlyphs); // nGlyphs in the secondary Lookup match
  2468. $nInput = count($inputGlyphs); // nGlyphs in the Primary Input sequence
  2469. $str = "";
  2470. for ($i = 0; $i < $nInput; $i++) {
  2471. if ($i > 0) {
  2472. $str .= $ignore . " ";
  2473. }
  2474. if ($i >= $seqIndex && $i < ($seqIndex + $mLen)) {
  2475. $str .= "" . $lookupGlyphs[($i - $seqIndex)] . "";
  2476. } else {
  2477. $str .= "" . $inputGlyphs[($i)] . "";
  2478. }
  2479. }
  2480. return $str;
  2481. }
  2482. function _makeGSUBinputMatch($inputGlyphs, $ignore)
  2483. {
  2484. // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
  2485. // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦
  2486. // $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context
  2487. // $lookupGlyphs = array of glyphs making up Lookup Input sequence - if applicable
  2488. $str = "";
  2489. for ($i = 1; $i <= count($inputGlyphs); $i++) {
  2490. if ($i > 1) {
  2491. $str .= $ignore . " ";
  2492. }
  2493. $str .= "" . $inputGlyphs[($i - 1)] . "";
  2494. }
  2495. return $str;
  2496. }
  2497. function _makeGSUBbacktrackMatch($backtrackGlyphs, $ignore)
  2498. {
  2499. // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
  2500. // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦
  2501. // $backtrackGlyphs = array of glyphstrings making up Backtrack sequence
  2502. // 3 2 1 0
  2503. // each item being e.g. E0AD|E0AF|F1FD
  2504. $str = "";
  2505. for ($i = (count($backtrackGlyphs) - 1); $i >= 0; $i--) {
  2506. $str .= "" . $backtrackGlyphs[$i] . " " . $ignore . " ";
  2507. }
  2508. return $str;
  2509. }
  2510. function _makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore)
  2511. {
  2512. // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
  2513. // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦
  2514. // $lookaheadGlyphs = array of glyphstrings making up Lookahead sequence
  2515. // 0 1 2 3
  2516. // each item being e.g. E0AD|E0AF|F1FD
  2517. $str = "";
  2518. for ($i = 0; $i < count($lookaheadGlyphs); $i++) {
  2519. $str .= $ignore . " " . $lookaheadGlyphs[$i] . "";
  2520. }
  2521. return $str;
  2522. }
  2523. function _makeGSUBinputReplacement($nInput, $REPL, $ignore, $nBsubs, $mLen, $seqIndex)
  2524. {
  2525. // Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}"
  2526. // $nInput nGlyphs in the Primary Input sequence
  2527. // $REPL replacement glyphs from secondary lookup
  2528. // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()"
  2529. // $nBsubs Number of Backtrack substitutions (= 2x Number of Backtrack glyphs)
  2530. // $mLen nGlyphs in the secondary Lookup match - if no secondary lookup, should=$nInput
  2531. // $seqIndex Sequence Index to apply the secondary match
  2532. if ($ignore == "()") {
  2533. $ign = false;
  2534. } else {
  2535. $ign = true;
  2536. }
  2537. $str = "";
  2538. if ($nInput == 1) {
  2539. $str = $REPL;
  2540. } else if ($nInput > 1) {
  2541. if ($mLen == $nInput) { // whole string replaced
  2542. $str = $REPL;
  2543. if ($ign) {
  2544. // for every nInput over 1, add another replacement backreference, to move IGNORES after replacement
  2545. for ($x = 2; $x <= $nInput; $x++) {
  2546. $str .= '\\' . ($nBsubs + (2 * ($x - 1)));
  2547. }
  2548. }
  2549. } else { // if only part of string replaced:
  2550. for ($x = 1; $x < ($seqIndex + 1); $x++) {
  2551. if ($x == 1) {
  2552. $str .= '\\' . ($nBsubs + 1);
  2553. } else {
  2554. if ($ign) {
  2555. $str .= '\\' . ($nBsubs + (2 * ($x - 1)));
  2556. }
  2557. $str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1)));
  2558. }
  2559. }
  2560. if ($seqIndex > 0) {
  2561. $str .= " ";
  2562. }
  2563. $str .= $REPL;
  2564. if ($ign) {
  2565. for ($x = (max(($seqIndex + 1), 2)); $x < ($seqIndex + 1 + $mLen); $x++) { // move IGNORES after replacement
  2566. $str .= '\\' . ($nBsubs + (2 * ($x - 1)));
  2567. }
  2568. }
  2569. for ($x = ($seqIndex + 1 + $mLen); $x <= $nInput; $x++) {
  2570. if ($ign) {
  2571. $str .= '\\' . ($nBsubs + (2 * ($x - 1)));
  2572. }
  2573. $str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1)));
  2574. }
  2575. }
  2576. }
  2577. return $str;
  2578. }
  2579. //////////////////////////////////////////////////////////////////////////////////
  2580. function _getCoverage($convert2hex = true)
  2581. {
  2582. $g = array();
  2583. $CoverageFormat = $this->read_ushort();
  2584. if ($CoverageFormat == 1) {
  2585. $CoverageGlyphCount = $this->read_ushort();
  2586. for ($gid = 0; $gid < $CoverageGlyphCount; $gid++) {
  2587. $glyphID = $this->read_ushort();
  2588. if ($convert2hex) {
  2589. $g[] = unicode_hex($this->glyphToChar[$glyphID][0]);
  2590. } else {
  2591. $g[] = $glyphID;
  2592. }
  2593. }
  2594. }
  2595. if ($CoverageFormat == 2) {
  2596. $RangeCount = $this->read_ushort();
  2597. for ($r = 0; $r < $RangeCount; $r++) {
  2598. $start = $this->read_ushort();
  2599. $end = $this->read_ushort();
  2600. $StartCoverageIndex = $this->read_ushort(); // n/a
  2601. for ($gid = $start; $gid <= $end; $gid++) {
  2602. $glyphID = $gid;
  2603. if ($convert2hex) {
  2604. $g[] = unicode_hex($this->glyphToChar[$glyphID][0]);
  2605. } else {
  2606. $g[] = $glyphID;
  2607. }
  2608. }
  2609. }
  2610. }
  2611. return $g;
  2612. }
  2613. //////////////////////////////////////////////////////////////////////////////////
  2614. function _getClasses($offset)
  2615. {
  2616. $this->seek($offset);
  2617. $ClassFormat = $this->read_ushort();
  2618. $GlyphByClass = array();
  2619. if ($ClassFormat == 1) {
  2620. $StartGlyph = $this->read_ushort();
  2621. $GlyphCount = $this->read_ushort();
  2622. for ($i = 0; $i < $GlyphCount; $i++) {
  2623. $startGlyphID = $StartGlyph + $i;
  2624. $endGlyphID = $StartGlyph + $i;
  2625. $class = $this->read_ushort();
  2626. for ($g = $startGlyphID; $g <= $endGlyphID; $g++) {
  2627. if ($this->glyphToChar[$g][0]) {
  2628. $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]);
  2629. }
  2630. }
  2631. }
  2632. } else if ($ClassFormat == 2) {
  2633. $tableCount = $this->read_ushort();
  2634. for ($i = 0; $i < $tableCount; $i++) {
  2635. $startGlyphID = $this->read_ushort();
  2636. $endGlyphID = $this->read_ushort();
  2637. $class = $this->read_ushort();
  2638. for ($g = $startGlyphID; $g <= $endGlyphID; $g++) {
  2639. if ($this->glyphToChar[$g][0]) {
  2640. $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]);
  2641. }
  2642. }
  2643. }
  2644. }
  2645. $gbc = array();
  2646. foreach ($GlyphByClass AS $class => $garr) {
  2647. $gbc[$class] = implode('|', $garr);
  2648. }
  2649. return $gbc;
  2650. }
  2651. //////////////////////////////////////////////////////////////////////////////////
  2652. //////////////////////////////////////////////////////////////////////////////////
  2653. //////////////////////////////////////////////////////////////////////////////////
  2654. //////////////////////////////////////////////////////////////////////////////////
  2655. //////////////////////////////////////////////////////////////////////////////////
  2656. function _getGPOStables()
  2657. {
  2658. ///////////////////////////////////
  2659. // GPOS - Glyph Positioning
  2660. ///////////////////////////////////
  2661. if (isset($this->tables["GPOS"])) {
  2662. $this->mpdf->WriteHTML('<h1>GPOS Tables</h1>');
  2663. $ffeats = array();
  2664. $gpos_offset = $this->seek_table("GPOS");
  2665. $this->skip(4);
  2666. $ScriptList_offset = $gpos_offset + $this->read_ushort();
  2667. $FeatureList_offset = $gpos_offset + $this->read_ushort();
  2668. $LookupList_offset = $gpos_offset + $this->read_ushort();
  2669. // ScriptList
  2670. $this->seek($ScriptList_offset);
  2671. $ScriptCount = $this->read_ushort();
  2672. for ($i = 0; $i < $ScriptCount; $i++) {
  2673. $ScriptTag = $this->read_tag(); // = "beng", "deva" etc.
  2674. $ScriptTableOffset = $this->read_ushort();
  2675. $ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset;
  2676. }
  2677. // Script Table
  2678. foreach ($ffeats AS $t => $o) {
  2679. $ls = array();
  2680. $this->seek($o);
  2681. $DefLangSys_offset = $this->read_ushort();
  2682. if ($DefLangSys_offset > 0) {
  2683. $ls['DFLT'] = $DefLangSys_offset + $o;
  2684. }
  2685. $LangSysCount = $this->read_ushort();
  2686. for ($i = 0; $i < $LangSysCount; $i++) {
  2687. $LangTag = $this->read_tag(); // =
  2688. $LangTableOffset = $this->read_ushort();
  2689. $ls[$LangTag] = $o + $LangTableOffset;
  2690. }
  2691. $ffeats[$t] = $ls;
  2692. }
  2693. // Get FeatureIndexList
  2694. // LangSys Table - from first listed langsys
  2695. foreach ($ffeats AS $st => $scripts) {
  2696. foreach ($scripts AS $t => $o) {
  2697. $FeatureIndex = array();
  2698. $langsystable_offset = $o;
  2699. $this->seek($langsystable_offset);
  2700. $LookUpOrder = $this->read_ushort(); //==NULL
  2701. $ReqFeatureIndex = $this->read_ushort();
  2702. if ($ReqFeatureIndex != 0xFFFF) {
  2703. $FeatureIndex[] = $ReqFeatureIndex;
  2704. }
  2705. $FeatureCount = $this->read_ushort();
  2706. for ($i = 0; $i < $FeatureCount; $i++) {
  2707. $FeatureIndex[] = $this->read_ushort(); // = index of feature
  2708. }
  2709. $ffeats[$st][$t] = $FeatureIndex;
  2710. }
  2711. }
  2712. //print_r($ffeats); exit;
  2713. // Feauture List => LookupListIndex es
  2714. $this->seek($FeatureList_offset);
  2715. $FeatureCount = $this->read_ushort();
  2716. $Feature = array();
  2717. for ($i = 0; $i < $FeatureCount; $i++) {
  2718. $Feature[$i] = array('tag' => $this->read_tag());
  2719. $Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort();
  2720. }
  2721. for ($i = 0; $i < $FeatureCount; $i++) {
  2722. $this->seek($Feature[$i]['offset']);
  2723. $this->read_ushort(); // null
  2724. $Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort();
  2725. $Feature[$i]['LookupListIndex'] = array();
  2726. for ($c = 0; $c < $Lookupcount; $c++) {
  2727. $Feature[$i]['LookupListIndex'][] = $this->read_ushort();
  2728. }
  2729. }
  2730. foreach ($ffeats AS $st => $scripts) {
  2731. foreach ($scripts AS $t => $o) {
  2732. $FeatureIndex = $ffeats[$st][$t];
  2733. foreach ($FeatureIndex AS $k => $fi) {
  2734. $ffeats[$st][$t][$k] = $Feature[$fi];
  2735. }
  2736. }
  2737. }
  2738. //print_r($ffeats); exit;
  2739. //=====================================================================================
  2740. $gpos = array();
  2741. $GPOSScriptLang = array();
  2742. foreach ($ffeats AS $st => $scripts) {
  2743. foreach ($scripts AS $t => $langsys) {
  2744. $lg = array();
  2745. foreach ($langsys AS $ft) {
  2746. $lg[$ft['LookupListIndex'][0]] = $ft;
  2747. }
  2748. // list of Lookups in order they need to be run i.e. order listed in Lookup table
  2749. ksort($lg);
  2750. foreach ($lg AS $ft) {
  2751. $gpos[$st][$t][$ft['tag']] = $ft['LookupListIndex'];
  2752. }
  2753. if (!isset($GPOSScriptLang[$st])) {
  2754. $GPOSScriptLang[$st] = '';
  2755. }
  2756. $GPOSScriptLang[$st] .= $t . ' ';
  2757. }
  2758. }
  2759. if ($this->mode == 'summary') {
  2760. $this->mpdf->WriteHTML('<h3>GPOS Scripts &amp; Languages</h3>');
  2761. $html = '';
  2762. if (count($gpos)) {
  2763. foreach ($gpos AS $st => $g) {
  2764. $html .= '<h5>' . $st . '</h5>';
  2765. foreach ($g AS $l => $t) {
  2766. $html .= '<div><a href="font_dump_OTL.php?script=' . $st . '&lang=' . $l . '">' . $l . '</a></b>: ';
  2767. foreach ($t AS $tag => $o) {
  2768. $html .= $tag . ' ';
  2769. }
  2770. $html .= '</div>';
  2771. }
  2772. }
  2773. } else {
  2774. $html .= '<div>No entries in GPOS table.</div>';
  2775. }
  2776. $this->mpdf->WriteHTML($html);
  2777. $this->mpdf->WriteHTML('</div>');
  2778. return 0;
  2779. }
  2780. //=====================================================================================
  2781. // Get metadata and offsets for whole Lookup List table
  2782. $this->seek($LookupList_offset);
  2783. $LookupCount = $this->read_ushort();
  2784. $Lookup = array();
  2785. $Offsets = array();
  2786. $SubtableCount = array();
  2787. for ($i = 0; $i < $LookupCount; $i++) {
  2788. $Offsets[$i] = $LookupList_offset + $this->read_ushort();
  2789. }
  2790. for ($i = 0; $i < $LookupCount; $i++) {
  2791. $this->seek($Offsets[$i]);
  2792. $Lookup[$i]['Type'] = $this->read_ushort();
  2793. $Lookup[$i]['Flag'] = $flag = $this->read_ushort();
  2794. $Lookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort();
  2795. for ($c = 0; $c < $SubtableCount[$i]; $c++) {
  2796. $Lookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort();
  2797. }
  2798. // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure
  2799. if (($flag & 0x0010) == 0x0010) {
  2800. $Lookup[$i]['MarkFilteringSet'] = $this->read_ushort();
  2801. }
  2802. // else { $Lookup[$i]['MarkFilteringSet'] = ''; }
  2803. // Lookup Type 9: Extension
  2804. if ($Lookup[$i]['Type'] == 9) {
  2805. // Overwrites new offset (32-bit) for each subtable, and a new lookup Type
  2806. for ($c = 0; $c < $SubtableCount[$i]; $c++) {
  2807. $this->seek($Lookup[$i]['Subtables'][$c]);
  2808. $ExtensionPosFormat = $this->read_ushort();
  2809. $type = $this->read_ushort();
  2810. $Lookup[$i]['Subtables'][$c] = $Lookup[$i]['Subtables'][$c] + $this->read_ulong();
  2811. }
  2812. $Lookup[$i]['Type'] = $type;
  2813. }
  2814. }
  2815. //=====================================================================================
  2816. $st = $this->mpdf->OTLscript;
  2817. $t = $this->mpdf->OTLlang;
  2818. $langsys = $gpos[$st][$t];
  2819. $lul = array(); // array of LookupListIndexes
  2820. $tags = array(); // corresponding array of feature tags e.g. 'ccmp'
  2821. if (count($langsys)) {
  2822. foreach ($langsys AS $tag => $ft) {
  2823. foreach ($ft AS $ll) {
  2824. $lul[$ll] = $tag;
  2825. }
  2826. }
  2827. }
  2828. ksort($lul); // Order the Lookups in the order they are in the GUSB table, regardless of Feature order
  2829. $this->_getGPOSarray($Lookup, $lul, $st);
  2830. //print_r($lul); exit;
  2831. return array($GPOSScriptLang, $gpos, $Lookup);
  2832. } // end if GPOS
  2833. }
  2834. //////////////////////////////////////////////////////////////////////////////////
  2835. //=====================================================================================
  2836. //=====================================================================================
  2837. //=====================================================================================
  2838. /////////////////////////////////////////////////////////////////////////////////////////
  2839. // GPOS functions
  2840. function _getGPOSarray(&$Lookup, $lul, $scripttag, $level = 1, $lcoverage = '', $exB = '', $exL = '')
  2841. {
  2842. // Process (3) LookupList for specific Script-LangSys
  2843. $html = '';
  2844. if ($level == 1) {
  2845. $html .= '<bookmark level="0" content="GPOS features">';
  2846. }
  2847. foreach ($lul AS $luli => $tag) {
  2848. $html .= '<div class="level' . $level . '">';
  2849. $html .= '<h5 class="level' . $level . '">';
  2850. if ($level == 1) {
  2851. $html .= '<bookmark level="1" content="' . $tag . ' [#' . $luli . ']">';
  2852. }
  2853. $html .= 'Lookup #' . $luli . ' [tag: <span style="color:#000066;">' . $tag . '</span>]</h5>';
  2854. $ignore = $this->_getGSUBignoreString($Lookup[$luli]['Flag'], $Lookup[$luli]['MarkFilteringSet']);
  2855. if ($ignore) {
  2856. $html .= '<div class="ignore">Ignoring: ' . $ignore . '</div> ';
  2857. }
  2858. $Type = $Lookup[$luli]['Type'];
  2859. $Flag = $Lookup[$luli]['Flag'];
  2860. if (($Flag & 0x0001) == 1) {
  2861. $dir = 'RTL';
  2862. } else {
  2863. $dir = 'LTR';
  2864. }
  2865. for ($c = 0; $c < $Lookup[$luli]['SubtableCount']; $c++) {
  2866. $html .= '<div class="subtable">Subtable #' . $c;
  2867. if ($level == 1) {
  2868. $html .= '<bookmark level="2" content="Subtable #' . $c . '">';
  2869. }
  2870. $html .= '</div>';
  2871. // Lets start
  2872. $subtable_offset = $Lookup[$luli]['Subtables'][$c];
  2873. $this->seek($subtable_offset);
  2874. $PosFormat = $this->read_ushort();
  2875. ////////////////////////////////////////////////////////////////////////////////
  2876. // LookupType 1: Single adjustment Adjust position of a single glyph (e.g. SmallCaps/Sups/Subs)
  2877. ////////////////////////////////////////////////////////////////////////////////
  2878. if ($Lookup[$luli]['Type'] == 1) {
  2879. $html .= '<div class="lookuptype">LookupType 1: Single adjustment [Format ' . $PosFormat . ']</div>';
  2880. //===========
  2881. // Format 1:
  2882. //===========
  2883. if ($PosFormat == 1) {
  2884. $Coverage = $subtable_offset + $this->read_ushort();
  2885. $ValueFormat = $this->read_ushort();
  2886. $Value = $this->_getValueRecord($ValueFormat);
  2887. $this->seek($Coverage);
  2888. $glyphs = $this->_getCoverage(); // Array of Hex Glyphs
  2889. for ($g = 0; $g < count($glyphs); $g++) {
  2890. if ($level == 2 && strpos($lcoverage, $glyphs[$g]) === false) {
  2891. continue;
  2892. }
  2893. $html .= '<div class="substitution">';
  2894. $html .= '<span class="unicode">' . $this->formatUni($glyphs[$g]) . '&nbsp;</span> ';
  2895. if ($level == 2 && $exB) {
  2896. $html .= $exB;
  2897. }
  2898. $html .= '<span class="unchanged">&nbsp;' . $this->formatEntity($glyphs[$g]) . '</span>';
  2899. if ($level == 2 && $exL) {
  2900. $html .= $exL;
  2901. }
  2902. $html .= '&nbsp; &raquo; &raquo; &nbsp;';
  2903. if ($level == 2 && $exB) {
  2904. $html .= $exB;
  2905. }
  2906. $html .= '<span class="changed" style="font-feature-settings:\'' . $tag . '\' 1;">&nbsp;' . $this->formatEntity($glyphs[$g]) . '</span>';
  2907. if ($level == 2 && $exL) {
  2908. $html .= $exL;
  2909. }
  2910. $html .= ' <span class="unicode">';
  2911. if ($Value['XPlacement']) {
  2912. $html .= ' Xpl: ' . $Value['XPlacement'] . ';';
  2913. }
  2914. if ($Value['YPlacement']) {
  2915. $html .= ' YPl: ' . $Value['YPlacement'] . ';';
  2916. }
  2917. if ($Value['XAdvance']) {
  2918. $html .= ' Xadv: ' . $Value['XAdvance'];
  2919. }
  2920. $html .= '</span>';
  2921. $html .= '</div>';
  2922. }
  2923. }
  2924. //===========
  2925. // Format 2:
  2926. //===========
  2927. else if ($PosFormat == 2) {
  2928. $Coverage = $subtable_offset + $this->read_ushort();
  2929. $ValueFormat = $this->read_ushort();
  2930. $ValueCount = $this->read_ushort();
  2931. $Values = array();
  2932. for ($v = 0; $v < $ValueCount; $v++) {
  2933. $Values[] = $this->_getValueRecord($ValueFormat);
  2934. }
  2935. $this->seek($Coverage);
  2936. $glyphs = $this->_getCoverage(); // Array of Hex Glyphs
  2937. for ($g = 0; $g < count($glyphs); $g++) {
  2938. if ($level == 2 && strpos($lcoverage, $glyphs[$g]) === false) {
  2939. continue;
  2940. }
  2941. $Value = $Values[$g];
  2942. $html .= '<div class="substitution">';
  2943. $html .= '<span class="unicode">' . $this->formatUni($glyphs[$g]) . '&nbsp;</span> ';
  2944. if ($level == 2 && $exB) {
  2945. $html .= $exB;
  2946. }
  2947. $html .= '<span class="unchanged">&nbsp;' . $this->formatEntity($glyphs[$g]) . '</span>';
  2948. if ($level == 2 && $exL) {
  2949. $html .= $exL;
  2950. }
  2951. $html .= '&nbsp; &raquo; &raquo; &nbsp;';
  2952. if ($level == 2 && $exB) {
  2953. $html .= $exB;
  2954. }
  2955. $html .= '<span class="changed" style="font-feature-settings:\'' . $tag . '\' 1;">&nbsp;' . $this->formatEntity($glyphs[$g]) . '</span>';
  2956. if ($level == 2 && $exL) {
  2957. $html .= $exL;
  2958. }
  2959. $html .= ' <span class="unicode">';
  2960. if ($Value['XPlacement']) {
  2961. $html .= ' Xpl: ' . $Value['XPlacement'] . ';';
  2962. }
  2963. if ($Value['YPlacement']) {
  2964. $html .= ' YPl: ' . $Value['YPlacement'] . ';';
  2965. }
  2966. if ($Value['XAdvance']) {
  2967. $html .= ' Xadv: ' . $Value['XAdvance'];
  2968. }
  2969. $html .= '</span>';
  2970. $html .= '</div>';
  2971. }
  2972. }
  2973. }
  2974. ////////////////////////////////////////////////////////////////////////////////
  2975. // LookupType 2: Pair adjustment Adjust position of a pair of glyphs (Kerning)
  2976. ////////////////////////////////////////////////////////////////////////////////
  2977. else if ($Lookup[$luli]['Type'] == 2) {
  2978. $html .= '<div class="lookuptype">LookupType 2: Pair adjustment e.g. Kerning [Format ' . $PosFormat . ']</div>';
  2979. $Coverage = $subtable_offset + $this->read_ushort();
  2980. $ValueFormat1 = $this->read_ushort();
  2981. $ValueFormat2 = $this->read_ushort();
  2982. //===========
  2983. // Format 1:
  2984. //===========
  2985. if ($PosFormat == 1) {
  2986. $PairSetCount = $this->read_ushort();
  2987. $PairSetOffset = array();
  2988. for ($p = 0; $p < $PairSetCount; $p++) {
  2989. $PairSetOffset[] = $subtable_offset + $this->read_ushort();
  2990. }
  2991. $this->seek($Coverage);
  2992. $glyphs = $this->_getCoverage(); // Array of Hex Glyphs
  2993. for ($p = 0; $p < $PairSetCount; $p++) {
  2994. if ($level == 2 && strpos($lcoverage, $glyphs[$p]) === false) {
  2995. continue;
  2996. }
  2997. $this->seek($PairSetOffset[$p]);
  2998. // First Glyph = $glyphs[$p]
  2999. // Takes too long e.g. Calibri font - just list kerning pairs with this:
  3000. $html .= '<div class="glyphs">';
  3001. $html .= '<span class="unchanged">&nbsp;' . $this->formatEntity($glyphs[$p]) . ' </span>';
  3002. //PairSet table
  3003. $PairValueCount = $this->read_ushort();
  3004. for ($pv = 0; $pv < $PairValueCount; $pv++) {
  3005. //PairValueRecord
  3006. $gid = $this->read_ushort();
  3007. $SecondGlyph = unicode_hex($this->glyphToChar[$gid][0]);
  3008. $Value1 = $this->_getValueRecord($ValueFormat1);
  3009. $Value2 = $this->_getValueRecord($ValueFormat2);
  3010. // If RTL pairs, GPOS declares a XPlacement e.g. -180 for an XAdvance of -180 to take
  3011. // account of direction. mPDF does not need the XPlacement adjustment
  3012. if ($dir == 'RTL' && $Value1['XPlacement']) {
  3013. $Value1['XPlacement'] -= $Value1['XAdvance'];
  3014. }
  3015. if ($ValueFormat2) {
  3016. // If RTL pairs, GPOS declares a XPlacement e.g. -180 for an XAdvance of -180 to take
  3017. // account of direction. mPDF does not need the XPlacement adjustment
  3018. if ($dir == 'RTL' && $Value2['XPlacement'] && $Value2['XAdvance']) {
  3019. $Value2['XPlacement'] -= $Value2['XAdvance'];
  3020. }
  3021. }
  3022. $html .= ' ' . $this->formatEntity($SecondGlyph) . ' ';
  3023. /*
  3024. $html .= '<div class="substitution">';
  3025. $html .= '<span class="unicode">'.$this->formatUni($glyphs[$p]).'&nbsp;</span> ';
  3026. if ($level==2 && $exB) { $html .= $exB; }
  3027. $html .= '<span class="unchanged">&nbsp;'.$this->formatEntity($glyphs[$p]).$this->formatEntity($SecondGlyph).'</span>';
  3028. if ($level==2 && $exL) { $html .= $exL; }
  3029. $html .= '&nbsp; &raquo; &raquo; &nbsp;';
  3030. if ($level==2 && $exB) { $html .= $exB; }
  3031. $html .= '<span class="changed" style="font-feature-settings:\''.$tag.'\' 1;">&nbsp;'.$this->formatEntity($glyphs[$p]).$this->formatEntity($SecondGlyph).'</span>';
  3032. if ($level==2 && $exL) { $html .= $exL; }
  3033. $html .= ' <span class="unicode">';
  3034. if ($Value1['XPlacement']) { $html .= ' Xpl[1]: '.$Value1['XPlacement'].';'; }
  3035. if ($Value1['YPlacement']) { $html .= ' YPl[1]: '.$Value1['YPlacement'].';'; }
  3036. if ($Value1['XAdvance']) { $html .= ' Xadv[1]: '.$Value1['XAdvance']; }
  3037. if ($Value2['XPlacement']) { $html .= ' Xpl[2]: '.$Value2['XPlacement'].';'; }
  3038. if ($Value2['YPlacement']) { $html .= ' YPl[2]: '.$Value2['YPlacement'].';'; }
  3039. if ($Value2['XAdvance']) { $html .= ' Xadv[2]: '.$Value2['XAdvance']; }
  3040. $html .= '</span>';
  3041. $html .= '</div>';
  3042. */
  3043. }
  3044. $html .= '</div>';
  3045. }
  3046. }
  3047. //===========
  3048. // Format 2:
  3049. //===========
  3050. else if ($PosFormat == 2) {
  3051. $ClassDef1 = $subtable_offset + $this->read_ushort();
  3052. $ClassDef2 = $subtable_offset + $this->read_ushort();
  3053. $Class1Count = $this->read_ushort();
  3054. $Class2Count = $this->read_ushort();
  3055. $sizeOfPair = ( 2 * $this->count_bits($ValueFormat1) ) + ( 2 * $this->count_bits($ValueFormat2) );
  3056. $sizeOfValueRecords = $Class1Count * $Class2Count * $sizeOfPair;
  3057. // NB Class1Count includes Class 0 even though it is not defined by $ClassDef1
  3058. // i.e. Class1Count = 5; Class1 will contain array(indices 1-4);
  3059. $Class1 = $this->_getClassDefinitionTable($ClassDef1);
  3060. $Class2 = $this->_getClassDefinitionTable($ClassDef2);
  3061. $this->seek($subtable_offset + 16);
  3062. for ($i = 0; $i < $Class1Count; $i++) {
  3063. for ($j = 0; $j < $Class2Count; $j++) {
  3064. $Value1 = $this->_getValueRecord($ValueFormat1);
  3065. $Value2 = $this->_getValueRecord($ValueFormat2);
  3066. // If RTL pairs, GPOS declares a XPlacement e.g. -180 for an XAdvance of -180
  3067. // of direction. mPDF does not need the XPlacement adjustment
  3068. if ($dir == 'RTL' && $Value1['XPlacement'] && $Value1['XAdvance']) {
  3069. $Value1['XPlacement'] -= $Value1['XAdvance'];
  3070. }
  3071. if ($ValueFormat2) {
  3072. if ($dir == 'RTL' && $Value2['XPlacement'] && $Value2['XAdvance']) {
  3073. $Value2['XPlacement'] -= $Value2['XAdvance'];
  3074. }
  3075. }
  3076. for ($c1 = 0; $c1 < count($Class1[$i]); $c1++) {
  3077. $FirstGlyph = $Class1[$i][$c1];
  3078. if ($level == 2 && strpos($lcoverage, $FirstGlyph) === false) {
  3079. continue;
  3080. }
  3081. for ($c2 = 0; $c2 < count($Class2[$j]); $c2++) {
  3082. $SecondGlyph = $Class2[$j][$c2];
  3083. if (!$Value1['XPlacement'] && !$Value1['YPlacement'] && !$Value1['XAdvance'] && !$Value2['XPlacement'] && !$Value2['YPlacement'] && !$Value2['XAdvance']) {
  3084. continue;
  3085. }
  3086. $html .= '<div class="substitution">';
  3087. $html .= '<span class="unicode">' . $this->formatUni($FirstGlyph) . '&nbsp;</span> ';
  3088. if ($level == 2 && $exB) {
  3089. $html .= $exB;
  3090. }
  3091. $html .= '<span class="unchanged">&nbsp;' . $this->formatEntity($FirstGlyph) . $this->formatEntity($SecondGlyph) . '</span>';
  3092. if ($level == 2 && $exL) {
  3093. $html .= $exL;
  3094. }
  3095. $html .= '&nbsp; &raquo; &raquo; &nbsp;';
  3096. if ($level == 2 && $exB) {
  3097. $html .= $exB;
  3098. }
  3099. $html .= '<span class="changed" style="font-feature-settings:\'' . $tag . '\' 1;">&nbsp;' . $this->formatEntity($FirstGlyph) . $this->formatEntity($SecondGlyph) . '</span>';
  3100. if ($level == 2 && $exL) {
  3101. $html .= $exL;
  3102. }
  3103. $html .= ' <span class="unicode">';
  3104. if ($Value1['XPlacement']) {
  3105. $html .= ' Xpl[1]: ' . $Value1['XPlacement'] . ';';
  3106. }
  3107. if ($Value1['YPlacement']) {
  3108. $html .= ' YPl[1]: ' . $Value1['YPlacement'] . ';';
  3109. }
  3110. if ($Value1['XAdvance']) {
  3111. $html .= ' Xadv[1]: ' . $Value1['XAdvance'];
  3112. }
  3113. if ($Value2['XPlacement']) {
  3114. $html .= ' Xpl[2]: ' . $Value2['XPlacement'] . ';';
  3115. }
  3116. if ($Value2['YPlacement']) {
  3117. $html .= ' YPl[2]: ' . $Value2['YPlacement'] . ';';
  3118. }
  3119. if ($Value2['XAdvance']) {
  3120. $html .= ' Xadv[2]: ' . $Value2['XAdvance'];
  3121. }
  3122. $html .= '</span>';
  3123. $html .= '</div>';
  3124. }
  3125. }
  3126. }
  3127. }
  3128. }
  3129. }
  3130. ////////////////////////////////////////////////////////////////////////////////
  3131. // LookupType 3: Cursive attachment Attach cursive glyphs
  3132. ////////////////////////////////////////////////////////////////////////////////
  3133. else if ($Lookup[$luli]['Type'] == 3) {
  3134. $html .= '<div class="lookuptype">LookupType 3: Cursive attachment </div>';
  3135. $Coverage = $subtable_offset + $this->read_ushort();
  3136. $EntryExitCount = $this->read_ushort();
  3137. $EntryAnchors = array();
  3138. $ExitAnchors = array();
  3139. for ($i = 0; $i < $EntryExitCount; $i++) {
  3140. $EntryAnchors[$i] = $this->read_ushort();
  3141. $ExitAnchors[$i] = $this->read_ushort();
  3142. }
  3143. $this->seek($Coverage);
  3144. $Glyphs = $this->_getCoverage();
  3145. for ($i = 0; $i < $EntryExitCount; $i++) {
  3146. // Need default XAdvance for glyph
  3147. $pdfWidth = $this->mpdf->_getCharWidth($this->mpdf->fonts[$this->fontkey]['cw'], hexdec($Glyphs[$i]));
  3148. $EntryAnchor = $EntryAnchors[$i];
  3149. $ExitAnchor = $ExitAnchors[$i];
  3150. $html .= '<div class="glyphs">';
  3151. $html .= '<span class="unchanged">' . $this->formatEntity($Glyphs[$i]) . ' </span> ';
  3152. $html .= '<span class="unicode"> ' . $this->formatUni($Glyphs[$i]) . ' => ';
  3153. if ($EntryAnchor != 0) {
  3154. $EntryAnchor += $subtable_offset;
  3155. list($x, $y) = $this->_getAnchorTable($EntryAnchor);
  3156. if ($dir == 'RTL') {
  3157. if (round($pdfWidth) == round($x * 1000 / $this->mpdf->fonts[$this->fontkey]['desc']['unitsPerEm'])) {
  3158. $x = 0;
  3159. } else {
  3160. $x = $x - ($pdfWidth * $this->mpdf->fonts[$this->fontkey]['desc']['unitsPerEm'] / 1000);
  3161. }
  3162. }
  3163. $html .= " Entry X: " . $x . " Y: " . $y . "; ";
  3164. }
  3165. if ($ExitAnchor != 0) {
  3166. $ExitAnchor += $subtable_offset;
  3167. list($x, $y) = $this->_getAnchorTable($ExitAnchor);
  3168. if ($dir == 'LTR') {
  3169. if (round($pdfWidth) == round($x * 1000 / $this->mpdf->fonts[$this->fontkey]['desc']['unitsPerEm'])) {
  3170. $x = 0;
  3171. } else {
  3172. $x = $x - ($pdfWidth * $this->mpdf->fonts[$this->fontkey]['desc']['unitsPerEm'] / 1000);
  3173. }
  3174. }
  3175. $html .= " Exit X: " . $x . " Y: " . $y . "; ";
  3176. }
  3177. $html .= '</span></div>';
  3178. }
  3179. }
  3180. ////////////////////////////////////////////////////////////////////////////////
  3181. // LookupType 4: MarkToBase attachment Attach a combining mark to a base glyph
  3182. ////////////////////////////////////////////////////////////////////////////////
  3183. else if ($Lookup[$luli]['Type'] == 4) {
  3184. $html .= '<div class="lookuptype">LookupType 4: MarkToBase attachment </div>';
  3185. $MarkCoverage = $subtable_offset + $this->read_ushort();
  3186. $BaseCoverage = $subtable_offset + $this->read_ushort();
  3187. $this->seek($MarkCoverage);
  3188. $MarkGlyphs = $this->_getCoverage();
  3189. $this->seek($BaseCoverage);
  3190. $BaseGlyphs = $this->_getCoverage();
  3191. $firstMark = '';
  3192. $html .= '<div class="glyphs">Marks: ';
  3193. for ($i = 0; $i < count($MarkGlyphs); $i++) {
  3194. if ($level == 2 && strpos($lcoverage, $MarkGlyphs[$i]) === false) {
  3195. continue;
  3196. } else {
  3197. if (!$firstMark) {
  3198. $firstMark = $MarkGlyphs[$i];
  3199. }
  3200. }
  3201. $html .= ' ' . $this->formatEntity($MarkGlyphs[$i]) . ' ';
  3202. }
  3203. $html .= '</div>';
  3204. if (!$firstMark) {
  3205. return;
  3206. }
  3207. $html .= '<div class="glyphs">Bases: ';
  3208. for ($j = 0; $j < count($BaseGlyphs); $j++) {
  3209. $html .= ' ' . $this->formatEntity($BaseGlyphs[$j]) . ' ';
  3210. }
  3211. $html .= '</div>';
  3212. // Example
  3213. $html .= '<div class="glyphs" style="font-feature-settings:\'' . $tag . '\' 1;">Example(s): ';
  3214. for ($j = 0; $j < min(count($BaseGlyphs), 20); $j++) {
  3215. $html .= ' ' . $this->formatEntity($BaseGlyphs[$j]) . $this->formatEntity($firstMark, true) . ' &nbsp; ';
  3216. }
  3217. $html .= '</div>';
  3218. }
  3219. ////////////////////////////////////////////////////////////////////////////////
  3220. // LookupType 5: MarkToLigature attachment Attach a combining mark to a ligature
  3221. ////////////////////////////////////////////////////////////////////////////////
  3222. else if ($Lookup[$luli]['Type'] == 5) {
  3223. $html .= '<div class="lookuptype">LookupType 5: MarkToLigature attachment </div>';
  3224. $MarkCoverage = $subtable_offset + $this->read_ushort();
  3225. //$MarkCoverage is already set in $lcoverage 00065|00073 etc
  3226. $LigatureCoverage = $subtable_offset + $this->read_ushort();
  3227. $ClassCount = $this->read_ushort(); // Number of classes defined for marks = Number of mark glyphs in the MarkCoverage table
  3228. $MarkArray = $subtable_offset + $this->read_ushort(); // Offset to MarkArray table
  3229. $LigatureArray = $subtable_offset + $this->read_ushort(); // Offset to LigatureArray table
  3230. $this->seek($MarkCoverage);
  3231. $MarkGlyphs = $this->_getCoverage();
  3232. $this->seek($LigatureCoverage);
  3233. $LigatureGlyphs = $this->_getCoverage();
  3234. $firstMark = '';
  3235. $html .= '<div class="glyphs">Marks: <span class="unchanged">';
  3236. $MarkRecord = array();
  3237. for ($i = 0; $i < count($MarkGlyphs); $i++) {
  3238. if ($level == 2 && strpos($lcoverage, $MarkGlyphs[$i]) === false) {
  3239. continue;
  3240. } else {
  3241. if (!$firstMark) {
  3242. $firstMark = $MarkGlyphs[$i];
  3243. }
  3244. }
  3245. // Get the relevant MarkRecord
  3246. $MarkRecord[$i] = $this->_getMarkRecord($MarkArray, $i);
  3247. //Mark Class is = $MarkRecord[$i]['Class']
  3248. $html .= ' ' . $this->formatEntity($MarkGlyphs[$i]) . ' ';
  3249. }
  3250. $html .= '</span></div>';
  3251. if (!$firstMark) {
  3252. return;
  3253. }
  3254. $this->seek($LigatureArray);
  3255. $LigatureCount = $this->read_ushort();
  3256. $LigatureAttach = array();
  3257. $html .= '<div class="glyphs">Ligatures: <span class="unchanged">';
  3258. for ($j = 0; $j < count($LigatureGlyphs); $j++) {
  3259. // Get the relevant LigatureRecord
  3260. $LigatureAttach[$j] = $LigatureArray + $this->read_ushort();
  3261. $html .= ' ' . $this->formatEntity($LigatureGlyphs[$j]) . ' ';
  3262. }
  3263. $html .= '</span></div>';
  3264. /*
  3265. for ($i=0;$i<count($MarkGlyphs);$i++) {
  3266. $html .= '<div class="glyphs">';
  3267. $html .= '<span class="unchanged">'.$this->formatEntity($MarkGlyphs[$i]).'</span>';
  3268. for ($j=0;$j<count($LigatureGlyphs);$j++) {
  3269. $this->seek($LigatureAttach[$j]);
  3270. $ComponentCount = $this->read_ushort();
  3271. $html .= '<span class="unchanged">'.$this->formatEntity($LigatureGlyphs[$j]).'</span>';
  3272. $offsets = array();
  3273. for ($comp=0;$comp<$ComponentCount;$comp++) {
  3274. // ComponentRecords
  3275. for ($class=0;$class<$ClassCount;$class++) {
  3276. $offset = $this->read_ushort();
  3277. if ($offset!= 0 && $class == $MarkRecord[$i]['Class']) {
  3278. $html .= ' ['.$comp.'] ';
  3279. }
  3280. }
  3281. }
  3282. }
  3283. $html .= '</span></div>';
  3284. }
  3285. */
  3286. }
  3287. ////////////////////////////////////////////////////////////////////////////////
  3288. // LookupType 6: MarkToMark attachment Attach a combining mark to another mark
  3289. ////////////////////////////////////////////////////////////////////////////////
  3290. else if ($Lookup[$luli]['Type'] == 6) {
  3291. $html .= '<div class="lookuptype">LookupType 6: MarkToMark attachment </div>';
  3292. $Mark1Coverage = $subtable_offset + $this->read_ushort(); // Combining Mark
  3293. //$Mark1Coverage is already set in $LuCoverage 0065|0073 etc
  3294. $Mark2Coverage = $subtable_offset + $this->read_ushort(); // Base Mark
  3295. $ClassCount = $this->read_ushort(); // Number of classes defined for marks = No. of Combining mark1 glyphs in the MarkCoverage table
  3296. $this->seek($Mark1Coverage);
  3297. $Mark1Glyphs = $this->_getCoverage();
  3298. $this->seek($Mark2Coverage);
  3299. $Mark2Glyphs = $this->_getCoverage();
  3300. $firstMark = '';
  3301. $html .= '<div class="glyphs">Marks: <span class="unchanged">';
  3302. for ($i = 0; $i < count($Mark1Glyphs); $i++) {
  3303. if ($level == 2 && strpos($lcoverage, $Mark1Glyphs[$i]) === false) {
  3304. continue;
  3305. } else {
  3306. if (!$firstMark) {
  3307. $firstMark = $Mark1Glyphs[$i];
  3308. }
  3309. }
  3310. $html .= ' ' . $this->formatEntity($Mark1Glyphs[$i]) . ' ';
  3311. }
  3312. $html .= '</span></div>';
  3313. if ($firstMark) {
  3314. $html .= '<div class="glyphs">Bases: <span class="unchanged">';
  3315. for ($j = 0; $j < count($Mark2Glyphs); $j++) {
  3316. $html .= ' ' . $this->formatEntity($Mark2Glyphs[$j]) . ' ';
  3317. }
  3318. $html .= '</span></div>';
  3319. // Example
  3320. $html .= '<div class="glyphs" style="font-feature-settings:\'' . $tag . '\' 1;">Example(s): <span class="changed">';
  3321. for ($j = 0; $j < min(count($Mark2Glyphs), 20); $j++) {
  3322. $html .= ' ' . $this->formatEntity($Mark2Glyphs[$j]) . $this->formatEntity($firstMark, true) . ' &nbsp; ';
  3323. }
  3324. $html .= '</span></div>';
  3325. }
  3326. }
  3327. ////////////////////////////////////////////////////////////////////////////////
  3328. // LookupType 7: Context positioning Position one or more glyphs in context
  3329. ////////////////////////////////////////////////////////////////////////////////
  3330. else if ($Lookup[$luli]['Type'] == 7) {
  3331. $html .= '<div class="lookuptype">LookupType 7: Context positioning [Format ' . $PosFormat . ']</div>';
  3332. //===========
  3333. // Format 1:
  3334. //===========
  3335. if ($PosFormat == 1) {
  3336. throw new MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not YET TESTED.");
  3337. }
  3338. //===========
  3339. // Format 2:
  3340. //===========
  3341. else if ($PosFormat == 2) {
  3342. throw new MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not YET TESTED.");
  3343. }
  3344. //===========
  3345. // Format 3:
  3346. //===========
  3347. else if ($PosFormat == 3) {
  3348. throw new MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not YET TESTED.");
  3349. } else {
  3350. throw new MpdfException("GPOS Lookup Type " . $Type . ", Format " . $PosFormat . " not supported.");
  3351. }
  3352. }
  3353. ////////////////////////////////////////////////////////////////////////////////
  3354. // LookupType 8: Chained Context positioning Position one or more glyphs in chained context
  3355. ////////////////////////////////////////////////////////////////////////////////
  3356. else if ($Lookup[$luli]['Type'] == 8) {
  3357. $html .= '<div class="lookuptype">LookupType 8: Chained Context positioning [Format ' . $PosFormat . ']</div>';
  3358. //===========
  3359. // Format 1:
  3360. //===========
  3361. if ($PosFormat == 1) {
  3362. throw new MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not TESTED YET.");
  3363. }
  3364. //===========
  3365. // Format 2:
  3366. //===========
  3367. else if ($PosFormat == 2) {
  3368. $html .= '<div>GPOS Lookup Type 8: Format 2 not yet supported in OTL dump</div>';
  3369. continue;
  3370. /* NB When developing - cf. GSUB 6.2 */
  3371. throw new MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not TESTED YET.");
  3372. }
  3373. //===========
  3374. // Format 3:
  3375. //===========
  3376. else if ($PosFormat == 3) {
  3377. $BacktrackGlyphCount = $this->read_ushort();
  3378. $CoverageBacktrackOffset = array();
  3379. for ($b = 0; $b < $BacktrackGlyphCount; $b++) {
  3380. $CoverageBacktrackOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order
  3381. }
  3382. $InputGlyphCount = $this->read_ushort();
  3383. $CoverageInputOffset = array();
  3384. for ($b = 0; $b < $InputGlyphCount; $b++) {
  3385. $CoverageInputOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order
  3386. }
  3387. $LookaheadGlyphCount = $this->read_ushort();
  3388. $CoverageLookaheadOffset = array();
  3389. for ($b = 0; $b < $LookaheadGlyphCount; $b++) {
  3390. $CoverageLookaheadOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order
  3391. }
  3392. $PosCount = $this->read_ushort();
  3393. $PosLookupRecord = array();
  3394. for ($p = 0; $p < $PosCount; $p++) {
  3395. // PosLookupRecord
  3396. $PosLookupRecord[$p]['SequenceIndex'] = $this->read_ushort();
  3397. $PosLookupRecord[$p]['LookupListIndex'] = $this->read_ushort();
  3398. }
  3399. $backtrackGlyphs = array();
  3400. for ($b = 0; $b < $BacktrackGlyphCount; $b++) {
  3401. $this->seek($CoverageBacktrackOffset[$b]);
  3402. $backtrackGlyphs[$b] = implode('|', $this->_getCoverage());
  3403. }
  3404. $inputGlyphs = array();
  3405. for ($b = 0; $b < $InputGlyphCount; $b++) {
  3406. $this->seek($CoverageInputOffset[$b]);
  3407. $inputGlyphs[$b] = implode('|', $this->_getCoverage());
  3408. }
  3409. $lookaheadGlyphs = array();
  3410. for ($b = 0; $b < $LookaheadGlyphCount; $b++) {
  3411. $this->seek($CoverageLookaheadOffset[$b]);
  3412. $lookaheadGlyphs[$b] = implode('|', $this->_getCoverage());
  3413. }
  3414. $exampleB = array();
  3415. $exampleI = array();
  3416. $exampleL = array();
  3417. $html .= '<div class="context">CONTEXT: ';
  3418. for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) {
  3419. $html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>';
  3420. $exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]);
  3421. }
  3422. for ($ff = 0; $ff < count($inputGlyphs); $ff++) {
  3423. $html .= '<div>Input #' . $ff . ': <span class="unchanged">&nbsp;' . $this->formatEntityStr($inputGlyphs[$ff]) . '&nbsp;</span></div>';
  3424. $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]);
  3425. }
  3426. for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) {
  3427. $html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>';
  3428. $exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]);
  3429. }
  3430. $html .= '</div>';
  3431. for ($p = 0; $p < $PosCount; $p++) {
  3432. $lup = $PosLookupRecord[$p]['LookupListIndex'];
  3433. $seqIndex = $PosLookupRecord[$p]['SequenceIndex'];
  3434. // GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n]
  3435. $exB = '';
  3436. $exL = '';
  3437. if (count($exampleB)) {
  3438. $exB .= '<span class="backtrack">' . implode('&#x200d;', $exampleB) . '</span>';
  3439. }
  3440. if ($seqIndex > 0) {
  3441. $exB .= '<span class="inputother">';
  3442. for ($ip = 0; $ip < $seqIndex; $ip++) {
  3443. $exB .= $exampleI[$ip] . '&#x200d;';
  3444. }
  3445. $exB .= '</span>';
  3446. }
  3447. if (count($inputGlyphs) > ($seqIndex + 1)) {
  3448. $exL .= '<span class="inputother">';
  3449. for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) {
  3450. $exL .= '&#x200d;' . $exampleI[$ip];
  3451. }
  3452. $exL .= '</span>';
  3453. }
  3454. if (count($exampleL)) {
  3455. $exL .= '<span class="lookahead">' . implode('&#x200d;', $exampleL) . '</span>';
  3456. }
  3457. $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>';
  3458. $lul2 = array($lup => $tag);
  3459. // Only apply if the (first) 'Replace' glyph from the
  3460. // Lookup list is in the [inputGlyphs] at ['SequenceIndex']
  3461. // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656
  3462. // to level 2 and only apply if first Replace glyph is in this list
  3463. $html .= $this->_getGPOSarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL);
  3464. }
  3465. }
  3466. }
  3467. }
  3468. $html .= '</div>';
  3469. }
  3470. if ($level == 1) {
  3471. $this->mpdf->WriteHTML($html);
  3472. } else {
  3473. return $html;
  3474. }
  3475. //print_r($Lookup); exit;
  3476. }
  3477. //=====================================================================================
  3478. //=====================================================================================
  3479. // GPOS FUNCTIONS
  3480. //=====================================================================================
  3481. function count_bits($n)
  3482. {
  3483. for ($c = 0; $n; $c++) {
  3484. $n &= $n - 1; // clear the least significant bit set
  3485. }
  3486. return $c;
  3487. }
  3488. function _getValueRecord($ValueFormat)
  3489. { // Common ValueRecord for GPOS
  3490. // Only returns 3 possible: $vra['XPlacement'] $vra['YPlacement'] $vra['XAdvance']
  3491. $vra = array();
  3492. // Horizontal adjustment for placement-in design units
  3493. if (($ValueFormat & 0x0001) == 0x0001) {
  3494. $vra['XPlacement'] = $this->read_short();
  3495. }
  3496. // Vertical adjustment for placement-in design units
  3497. if (($ValueFormat & 0x0002) == 0x0002) {
  3498. $vra['YPlacement'] = $this->read_short();
  3499. }
  3500. // Horizontal adjustment for advance-in design units (only used for horizontal writing)
  3501. if (($ValueFormat & 0x0004) == 0x0004) {
  3502. $vra['XAdvance'] = $this->read_short();
  3503. }
  3504. // Vertical adjustment for advance-in design units (only used for vertical writing)
  3505. if (($ValueFormat & 0x0008) == 0x0008) {
  3506. $this->read_short();
  3507. }
  3508. // Offset to Device table for horizontal placement-measured from beginning of PosTable (may be NULL)
  3509. if (($ValueFormat & 0x0010) == 0x0010) {
  3510. $this->read_ushort();
  3511. }
  3512. // Offset to Device table for vertical placement-measured from beginning of PosTable (may be NULL)
  3513. if (($ValueFormat & 0x0020) == 0x0020) {
  3514. $this->read_ushort();
  3515. }
  3516. // Offset to Device table for horizontal advance-measured from beginning of PosTable (may be NULL)
  3517. if (($ValueFormat & 0x0040) == 0x0040) {
  3518. $this->read_ushort();
  3519. }
  3520. // Offset to Device table for vertical advance-measured from beginning of PosTable (may be NULL)
  3521. if (($ValueFormat & 0x0080) == 0x0080) {
  3522. $this->read_ushort();
  3523. }
  3524. return $vra;
  3525. }
  3526. function _getAnchorTable($offset = 0)
  3527. {
  3528. if ($offset) {
  3529. $this->seek($offset);
  3530. }
  3531. $AnchorFormat = $this->read_ushort();
  3532. $XCoordinate = $this->read_short();
  3533. $YCoordinate = $this->read_short();
  3534. // Format 2 specifies additional link to contour point; Format 3 additional Device table
  3535. return array($XCoordinate, $YCoordinate);
  3536. }
  3537. function _getMarkRecord($offset, $MarkPos)
  3538. {
  3539. $this->seek($offset);
  3540. $MarkCount = $this->read_ushort();
  3541. $this->skip($MarkPos * 4);
  3542. $Class = $this->read_ushort();
  3543. $MarkAnchor = $offset + $this->read_ushort(); // = Offset to anchor table
  3544. list($x, $y) = $this->_getAnchorTable($MarkAnchor);
  3545. $MarkRecord = array('Class' => $Class, 'AnchorX' => $x, 'AnchorY' => $y);
  3546. return $MarkRecord;
  3547. }
  3548. //////////////////////////////////////////////////////////////////////////////////
  3549. // Recursively get composite glyph data
  3550. function getGlyphData($originalGlyphIdx, &$maxdepth, &$depth, &$points, &$contours)
  3551. {
  3552. $depth++;
  3553. $maxdepth = max($maxdepth, $depth);
  3554. if (count($this->glyphdata[$originalGlyphIdx]['compGlyphs'])) {
  3555. foreach ($this->glyphdata[$originalGlyphIdx]['compGlyphs'] AS $glyphIdx) {
  3556. $this->getGlyphData($glyphIdx, $maxdepth, $depth, $points, $contours);
  3557. }
  3558. } else if (($this->glyphdata[$originalGlyphIdx]['nContours'] > 0) && $depth > 0) { // simple
  3559. $contours += $this->glyphdata[$originalGlyphIdx]['nContours'];
  3560. $points += $this->glyphdata[$originalGlyphIdx]['nPoints'];
  3561. }
  3562. $depth--;
  3563. }
  3564. //////////////////////////////////////////////////////////////////////////////////
  3565. // Recursively get composite glyphs
  3566. function getGlyphs($originalGlyphIdx, &$start, &$glyphSet, &$subsetglyphs)
  3567. {
  3568. $glyphPos = $this->glyphPos[$originalGlyphIdx];
  3569. $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos;
  3570. if (!$glyphLen) {
  3571. return;
  3572. }
  3573. $this->seek($start + $glyphPos);
  3574. $numberOfContours = $this->read_short();
  3575. if ($numberOfContours < 0) {
  3576. $this->skip(8);
  3577. $flags = GF_MORE;
  3578. while ($flags & GF_MORE) {
  3579. $flags = $this->read_ushort();
  3580. $glyphIdx = $this->read_ushort();
  3581. if (!isset($glyphSet[$glyphIdx])) {
  3582. $glyphSet[$glyphIdx] = count($subsetglyphs); // old glyphID to new glyphID
  3583. $subsetglyphs[$glyphIdx] = true;
  3584. }
  3585. $savepos = ftell($this->fh);
  3586. $this->getGlyphs($glyphIdx, $start, $glyphSet, $subsetglyphs);
  3587. $this->seek($savepos);
  3588. if ($flags & GF_WORDS)
  3589. $this->skip(4);
  3590. else
  3591. $this->skip(2);
  3592. if ($flags & GF_SCALE)
  3593. $this->skip(2);
  3594. else if ($flags & GF_XYSCALE)
  3595. $this->skip(4);
  3596. else if ($flags & GF_TWOBYTWO)
  3597. $this->skip(8);
  3598. }
  3599. }
  3600. }
  3601. //////////////////////////////////////////////////////////////////////////////////
  3602. function getHMTX($numberOfHMetrics, $numGlyphs, &$glyphToChar, $scale)
  3603. {
  3604. $start = $this->seek_table("hmtx");
  3605. $aw = 0;
  3606. $this->charWidths = str_pad('', 256 * 256 * 2, "\x00");
  3607. if ($this->maxUniChar > 65536) {
  3608. $this->charWidths .= str_pad('', 256 * 256 * 2, "\x00");
  3609. } // Plane 1 SMP
  3610. if ($this->maxUniChar > 131072) {
  3611. $this->charWidths .= str_pad('', 256 * 256 * 2, "\x00");
  3612. } // Plane 2 SMP
  3613. $nCharWidths = 0;
  3614. if (($numberOfHMetrics * 4) < $this->maxStrLenRead) {
  3615. $data = $this->get_chunk($start, ($numberOfHMetrics * 4));
  3616. $arr = unpack("n*", $data);
  3617. } else {
  3618. $this->seek($start);
  3619. }
  3620. for ($glyph = 0; $glyph < $numberOfHMetrics; $glyph++) {
  3621. if (($numberOfHMetrics * 4) < $this->maxStrLenRead) {
  3622. $aw = $arr[($glyph * 2) + 1];
  3623. } else {
  3624. $aw = $this->read_ushort();
  3625. $lsb = $this->read_ushort();
  3626. }
  3627. if (isset($glyphToChar[$glyph]) || $glyph == 0) {
  3628. if ($aw >= (1 << 15)) {
  3629. $aw = 0;
  3630. } // 1.03 Some (arabic) fonts have -ve values for width
  3631. // although should be unsigned value - comes out as e.g. 65108 (intended -50)
  3632. if ($glyph == 0) {
  3633. $this->defaultWidth = $scale * $aw;
  3634. continue;
  3635. }
  3636. foreach ($glyphToChar[$glyph] AS $char) {
  3637. //$this->charWidths[$char] = intval(round($scale*$aw));
  3638. if ($char != 0 && $char != 65535) {
  3639. $w = intval(round($scale * $aw));
  3640. if ($w == 0) {
  3641. $w = 65535;
  3642. }
  3643. if ($char < 196608) {
  3644. $this->charWidths[$char * 2] = chr($w >> 8);
  3645. $this->charWidths[$char * 2 + 1] = chr($w & 0xFF);
  3646. $nCharWidths++;
  3647. }
  3648. }
  3649. }
  3650. }
  3651. }
  3652. $data = $this->get_chunk(($start + $numberOfHMetrics * 4), ($numGlyphs * 2));
  3653. $arr = unpack("n*", $data);
  3654. $diff = $numGlyphs - $numberOfHMetrics;
  3655. $w = intval(round($scale * $aw));
  3656. if ($w == 0) {
  3657. $w = 65535;
  3658. }
  3659. for ($pos = 0; $pos < $diff; $pos++) {
  3660. $glyph = $pos + $numberOfHMetrics;
  3661. if (isset($glyphToChar[$glyph])) {
  3662. foreach ($glyphToChar[$glyph] AS $char) {
  3663. if ($char != 0 && $char != 65535) {
  3664. if ($char < 196608) {
  3665. $this->charWidths[$char * 2] = chr($w >> 8);
  3666. $this->charWidths[$char * 2 + 1] = chr($w & 0xFF);
  3667. $nCharWidths++;
  3668. }
  3669. }
  3670. }
  3671. }
  3672. }
  3673. // NB 65535 is a set width of 0
  3674. // First bytes define number of chars in font
  3675. $this->charWidths[0] = chr($nCharWidths >> 8);
  3676. $this->charWidths[1] = chr($nCharWidths & 0xFF);
  3677. }
  3678. function getHMetric($numberOfHMetrics, $gid)
  3679. {
  3680. $start = $this->seek_table("hmtx");
  3681. if ($gid < $numberOfHMetrics) {
  3682. $this->seek($start + ($gid * 4));
  3683. $hm = fread($this->fh, 4);
  3684. } else {
  3685. $this->seek($start + (($numberOfHMetrics - 1) * 4));
  3686. $hm = fread($this->fh, 2);
  3687. $this->seek($start + ($numberOfHMetrics * 2) + ($gid * 2));
  3688. $hm .= fread($this->fh, 2);
  3689. }
  3690. return $hm;
  3691. }
  3692. function getLOCA($indexToLocFormat, $numGlyphs)
  3693. {
  3694. $start = $this->seek_table('loca');
  3695. $this->glyphPos = array();
  3696. if ($indexToLocFormat == 0) {
  3697. $data = $this->get_chunk($start, ($numGlyphs * 2) + 2);
  3698. $arr = unpack("n*", $data);
  3699. for ($n = 0; $n <= $numGlyphs; $n++) {
  3700. $this->glyphPos[] = ($arr[$n + 1] * 2);
  3701. }
  3702. } else if ($indexToLocFormat == 1) {
  3703. $data = $this->get_chunk($start, ($numGlyphs * 4) + 4);
  3704. $arr = unpack("N*", $data);
  3705. for ($n = 0; $n <= $numGlyphs; $n++) {
  3706. $this->glyphPos[] = ($arr[$n + 1]);
  3707. }
  3708. } else {
  3709. throw new MpdfException('Unknown location table format ' . $indexToLocFormat);
  3710. }
  3711. }
  3712. // CMAP Format 4
  3713. function getCMAP4($unicode_cmap_offset, &$glyphToChar, &$charToGlyph)
  3714. {
  3715. $this->maxUniChar = 0;
  3716. $this->seek($unicode_cmap_offset + 2);
  3717. $length = $this->read_ushort();
  3718. $limit = $unicode_cmap_offset + $length;
  3719. $this->skip(2);
  3720. $segCount = $this->read_ushort() / 2;
  3721. $this->skip(6);
  3722. $endCount = array();
  3723. for ($i = 0; $i < $segCount; $i++) {
  3724. $endCount[] = $this->read_ushort();
  3725. }
  3726. $this->skip(2);
  3727. $startCount = array();
  3728. for ($i = 0; $i < $segCount; $i++) {
  3729. $startCount[] = $this->read_ushort();
  3730. }
  3731. $idDelta = array();
  3732. for ($i = 0; $i < $segCount; $i++) {
  3733. $idDelta[] = $this->read_short();
  3734. } // ???? was unsigned short
  3735. $idRangeOffset_start = $this->_pos;
  3736. $idRangeOffset = array();
  3737. for ($i = 0; $i < $segCount; $i++) {
  3738. $idRangeOffset[] = $this->read_ushort();
  3739. }
  3740. for ($n = 0; $n < $segCount; $n++) {
  3741. $endpoint = ($endCount[$n] + 1);
  3742. for ($unichar = $startCount[$n]; $unichar < $endpoint; $unichar++) {
  3743. if ($idRangeOffset[$n] == 0)
  3744. $glyph = ($unichar + $idDelta[$n]) & 0xFFFF;
  3745. else {
  3746. $offset = ($unichar - $startCount[$n]) * 2 + $idRangeOffset[$n];
  3747. $offset = $idRangeOffset_start + 2 * $n + $offset;
  3748. if ($offset >= $limit)
  3749. $glyph = 0;
  3750. else {
  3751. $glyph = $this->get_ushort($offset);
  3752. if ($glyph != 0)
  3753. $glyph = ($glyph + $idDelta[$n]) & 0xFFFF;
  3754. }
  3755. }
  3756. $charToGlyph[$unichar] = $glyph;
  3757. if ($unichar < 196608) {
  3758. $this->maxUniChar = max($unichar, $this->maxUniChar);
  3759. }
  3760. $glyphToChar[$glyph][] = $unichar;
  3761. }
  3762. }
  3763. }
  3764. function formatUni($char)
  3765. {
  3766. $x = preg_replace('/^[0]*/', '', $char);
  3767. $x = str_pad($x, 4, '0', STR_PAD_LEFT);
  3768. $d = hexdec($x);
  3769. if (($d > 57343 && $d < 63744) || ($d > 122879 && $d < 126977)) {
  3770. $id = 'M';
  3771. } // E000 - F8FF, 1E000-1F000
  3772. else {
  3773. $id = 'U';
  3774. }
  3775. return $id . '+' . $x;
  3776. }
  3777. function formatEntity($char, $allowjoining = false)
  3778. {
  3779. $char = preg_replace('/^[0]/', '', $char);
  3780. $x = '&#x' . $char . ';';
  3781. if (strpos($this->GlyphClassMarks, $char) !== false) {
  3782. if (!$allowjoining) {
  3783. $x = '&#x25cc;' . $x;
  3784. }
  3785. }
  3786. return $x;
  3787. }
  3788. function formatUniArr($arr)
  3789. {
  3790. $s = array();
  3791. foreach ($arr AS $c) {
  3792. $x = preg_replace('/^[0]*/', '', $c);
  3793. $d = hexdec($x);
  3794. if (($d > 57343 && $d < 63744) || ($d > 122879 && $d < 126977)) {
  3795. $id = 'M';
  3796. } // E000 - F8FF, 1E000-1F000
  3797. else {
  3798. $id = 'U';
  3799. }
  3800. $s[] = $id . '+' . str_pad($x, 4, '0', STR_PAD_LEFT);
  3801. }
  3802. return implode(', ', $s);
  3803. }
  3804. function formatEntityArr($arr)
  3805. {
  3806. $s = array();
  3807. foreach ($arr AS $c) {
  3808. $c = preg_replace('/^[0]/', '', $c);
  3809. $x = '&#x' . $c . ';';
  3810. if (strpos($this->GlyphClassMarks, $c) !== false) {
  3811. $x = '&#x25cc;' . $x;
  3812. }
  3813. $s[] = $x;
  3814. }
  3815. return implode(' ', $s); // ZWNJ? &#x200d;
  3816. }
  3817. function formatClassArr($arr)
  3818. {
  3819. $s = array();
  3820. foreach ($arr AS $c) {
  3821. $x = preg_replace('/^[0]*/', '', $c);
  3822. $d = hexdec($x);
  3823. if (($d > 57343 && $d < 63744) || ($d > 122879 && $d < 126977)) {
  3824. $id = 'M';
  3825. } // E000 - F8FF, 1E000-1F000
  3826. else {
  3827. $id = 'U';
  3828. }
  3829. $s[] = $id . '+' . str_pad($x, 4, '0', STR_PAD_LEFT);
  3830. }
  3831. return implode(', ', $s);
  3832. }
  3833. function formatUniStr($str)
  3834. {
  3835. $s = array();
  3836. $arr = explode('|', $str);
  3837. foreach ($arr AS $c) {
  3838. $x = preg_replace('/^[0]*/', '', $c);
  3839. $d = hexdec($x);
  3840. if (($d > 57343 && $d < 63744) || ($d > 122879 && $d < 126977)) {
  3841. $id = 'M';
  3842. } // E000 - F8FF, 1E000-1F000
  3843. else {
  3844. $id = 'U';
  3845. }
  3846. $s[] = $id . '+' . str_pad($x, 4, '0', STR_PAD_LEFT);
  3847. }
  3848. return implode(', ', $s);
  3849. }
  3850. function formatEntityStr($str)
  3851. {
  3852. $s = array();
  3853. $arr = explode('|', $str);
  3854. foreach ($arr AS $c) {
  3855. $c = preg_replace('/^[0]/', '', $c);
  3856. $x = '&#x' . $c . ';';
  3857. if (strpos($this->GlyphClassMarks, $c) !== false) {
  3858. $x = '&#x25cc;' . $x;
  3859. }
  3860. $s[] = $x;
  3861. }
  3862. return implode(' ', $s); // ZWNJ? &#x200d;
  3863. }
  3864. function formatEntityFirst($str)
  3865. {
  3866. $arr = explode('|', $str);
  3867. $char = preg_replace('/^[0]/', '', $arr[0]);
  3868. $x = '&#x' . $char . ';';
  3869. if (strpos($this->GlyphClassMarks, $char) !== false) {
  3870. $x = '&#x25cc;' . $x;
  3871. }
  3872. return $x;
  3873. }
  3874. }