otl.php 250 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231
  1. <?php
  2. require_once __DIR__ . '/../MpdfException.php';
  3. define("_OTL_OLD_SPEC_COMPAT_1", true);
  4. define("_DICT_NODE_TYPE_SPLIT", 0x01);
  5. define("_DICT_NODE_TYPE_LINEAR", 0x02);
  6. define("_DICT_INTERMEDIATE_MATCH", 0x03);
  7. define("_DICT_FINAL_MATCH", 0x04);
  8. class otl
  9. {
  10. var $mpdf;
  11. var $arabLeftJoining;
  12. var $arabRightJoining;
  13. var $arabTransparentJoin;
  14. var $arabTransparent;
  15. var $GSUBdata;
  16. var $GPOSdata;
  17. var $GSUBfont;
  18. var $fontkey;
  19. var $ttfOTLdata;
  20. var $glyphIDtoUni;
  21. var $_pos;
  22. var $GSUB_offset;
  23. var $GPOS_offset;
  24. var $MarkAttachmentType;
  25. var $MarkGlyphSets;
  26. var $GlyphClassMarks;
  27. var $GlyphClassLigatures;
  28. var $GlyphClassBases;
  29. var $GlyphClassComponents;
  30. var $Ignores;
  31. var $LuCoverage;
  32. var $OTLdata;
  33. var $assocLigs;
  34. var $assocMarks;
  35. var $shaper;
  36. var $restrictToSyllable;
  37. var $lbdicts; // Line-breaking dictionaries
  38. var $LuDataCache;
  39. var $debugOTL = false;
  40. public function __construct(mPDF $mpdf)
  41. {
  42. $this->mpdf = $mpdf;
  43. $this->arabic_initialise();
  44. $this->current_fh = '';
  45. $this->lbdicts = array();
  46. $this->LuDataCache = array();
  47. }
  48. function applyOTL($str, $useOTL)
  49. {
  50. $this->OTLdata = array();
  51. if (trim($str) == '') {
  52. return $str;
  53. }
  54. if (!$useOTL) {
  55. return $str;
  56. }
  57. // 1. Load GDEF data
  58. //==============================
  59. $this->fontkey = $this->mpdf->CurrentFont['fontkey'];
  60. $this->glyphIDtoUni = $this->mpdf->CurrentFont['glyphIDtoUni'];
  61. if (!isset($this->GDEFdata[$this->fontkey])) {
  62. include(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GDEFdata.php');
  63. $this->GSUB_offset = $this->GDEFdata[$this->fontkey]['GSUB_offset'] = $GSUB_offset;
  64. $this->GPOS_offset = $this->GDEFdata[$this->fontkey]['GPOS_offset'] = $GPOS_offset;
  65. $this->GSUB_length = $this->GDEFdata[$this->fontkey]['GSUB_length'] = $GSUB_length;
  66. $this->MarkAttachmentType = $this->GDEFdata[$this->fontkey]['MarkAttachmentType'] = $MarkAttachmentType;
  67. $this->MarkGlyphSets = $this->GDEFdata[$this->fontkey]['MarkGlyphSets'] = $MarkGlyphSets;
  68. $this->GlyphClassMarks = $this->GDEFdata[$this->fontkey]['GlyphClassMarks'] = $GlyphClassMarks;
  69. $this->GlyphClassLigatures = $this->GDEFdata[$this->fontkey]['GlyphClassLigatures'] = $GlyphClassLigatures;
  70. $this->GlyphClassComponents = $this->GDEFdata[$this->fontkey]['GlyphClassComponents'] = $GlyphClassComponents;
  71. $this->GlyphClassBases = $this->GDEFdata[$this->fontkey]['GlyphClassBases'] = $GlyphClassBases;
  72. } else {
  73. $this->GSUB_offset = $this->GDEFdata[$this->fontkey]['GSUB_offset'];
  74. $this->GPOS_offset = $this->GDEFdata[$this->fontkey]['GPOS_offset'];
  75. $this->GSUB_length = $this->GDEFdata[$this->fontkey]['GSUB_length'];
  76. $this->MarkAttachmentType = $this->GDEFdata[$this->fontkey]['MarkAttachmentType'];
  77. $this->MarkGlyphSets = $this->GDEFdata[$this->fontkey]['MarkGlyphSets'];
  78. $this->GlyphClassMarks = $this->GDEFdata[$this->fontkey]['GlyphClassMarks'];
  79. $this->GlyphClassLigatures = $this->GDEFdata[$this->fontkey]['GlyphClassLigatures'];
  80. $this->GlyphClassComponents = $this->GDEFdata[$this->fontkey]['GlyphClassComponents'];
  81. $this->GlyphClassBases = $this->GDEFdata[$this->fontkey]['GlyphClassBases'];
  82. }
  83. // 2. Prepare string as HEX string and Analyse character properties
  84. //=================================================================
  85. $earr = $this->mpdf->UTF8StringToArray($str, false);
  86. $scriptblock = 0;
  87. $scriptblocks = array();
  88. $scriptblocks[0] = 0;
  89. $vstr = '';
  90. $OTLdata = array();
  91. $subchunk = 0;
  92. $charctr = 0;
  93. foreach ($earr as $char) {
  94. $ucd_record = UCDN::get_ucd_record($char);
  95. $sbl = $ucd_record[6];
  96. // Special case - Arabic End of Ayah
  97. if ($char == 1757) {
  98. $sbl = UCDN::SCRIPT_ARABIC;
  99. }
  100. if ($sbl && $sbl != 40 && $sbl != 102) {
  101. if ($scriptblock == 0) {
  102. $scriptblock = $sbl;
  103. $scriptblocks[$subchunk] = $scriptblock;
  104. } else if ($scriptblock > 0 && $scriptblock != $sbl) {
  105. // *************************************************
  106. // NEW (non-common) Script encountered in this chunk. Start a new subchunk
  107. $subchunk++;
  108. $scriptblock = $sbl;
  109. $charctr = 0;
  110. $scriptblocks[$subchunk] = $scriptblock;
  111. }
  112. }
  113. $OTLdata[$subchunk][$charctr]['general_category'] = $ucd_record[0];
  114. $OTLdata[$subchunk][$charctr]['bidi_type'] = $ucd_record[2];
  115. //$OTLdata[$subchunk][$charctr]['combining_class'] = $ucd_record[1];
  116. //$OTLdata[$subchunk][$charctr]['bidi_type'] = $ucd_record[2];
  117. //$OTLdata[$subchunk][$charctr]['mirrored'] = $ucd_record[3];
  118. //$OTLdata[$subchunk][$charctr]['east_asian_width'] = $ucd_record[4];
  119. //$OTLdata[$subchunk][$charctr]['normalization_check'] = $ucd_record[5];
  120. //$OTLdata[$subchunk][$charctr]['script'] = $ucd_record[6];
  121. $charasstr = $this->unicode_hex($char);
  122. if (strpos($this->GlyphClassMarks, $charasstr) !== false) {
  123. $OTLdata[$subchunk][$charctr]['group'] = 'M';
  124. } else if ($char == 32 || $char == 12288) {
  125. $OTLdata[$subchunk][$charctr]['group'] = 'S';
  126. } // 12288 = 0x3000 = CJK space
  127. else {
  128. $OTLdata[$subchunk][$charctr]['group'] = 'C';
  129. }
  130. $OTLdata[$subchunk][$charctr]['uni'] = $char;
  131. $OTLdata[$subchunk][$charctr]['hex'] = $charasstr;
  132. $charctr++;
  133. }
  134. /* PROCESS EACH SUBCHUNK WITH DIFFERENT SCRIPTS */
  135. for ($sch = 0; $sch <= $subchunk; $sch++) {
  136. $this->OTLdata = $OTLdata[$sch];
  137. $scriptblock = $scriptblocks[$sch];
  138. // 3. Get Appropriate Scripts, and Shaper engine from analysing text and list of available scripts/langsys in font
  139. //==============================
  140. // Based on actual script block of text, select shaper (and line-breaking dictionaries)
  141. if (UCDN::SCRIPT_DEVANAGARI <= $scriptblock && $scriptblock <= UCDN::SCRIPT_MALAYALAM) {
  142. $this->shaper = "I";
  143. } // INDIC shaper
  144. else if ($scriptblock == UCDN::SCRIPT_ARABIC || $scriptblock == UCDN::SCRIPT_SYRIAC) {
  145. $this->shaper = "A";
  146. } // ARABIC shaper
  147. else if ($scriptblock == UCDN::SCRIPT_NKO || $scriptblock == UCDN::SCRIPT_MANDAIC) {
  148. $this->shaper = "A";
  149. } // ARABIC shaper
  150. else if ($scriptblock == UCDN::SCRIPT_KHMER) {
  151. $this->shaper = "K";
  152. } // KHMER shaper
  153. else if ($scriptblock == UCDN::SCRIPT_THAI) {
  154. $this->shaper = "T";
  155. } // THAI shaper
  156. else if ($scriptblock == UCDN::SCRIPT_LAO) {
  157. $this->shaper = "L";
  158. } // LAO shaper
  159. else if ($scriptblock == UCDN::SCRIPT_SINHALA) {
  160. $this->shaper = "S";
  161. } // SINHALA shaper
  162. else if ($scriptblock == UCDN::SCRIPT_MYANMAR) {
  163. $this->shaper = "M";
  164. } // MYANMAR shaper
  165. else if ($scriptblock == UCDN::SCRIPT_NEW_TAI_LUE) {
  166. $this->shaper = "E";
  167. } // SEA South East Asian shaper
  168. else if ($scriptblock == UCDN::SCRIPT_CHAM) {
  169. $this->shaper = "E";
  170. } // SEA South East Asian shaper
  171. else if ($scriptblock == UCDN::SCRIPT_TAI_THAM) {
  172. $this->shaper = "E";
  173. } // SEA South East Asian shaper
  174. else
  175. $this->shaper = "";
  176. // Get scripttag based on actual text script
  177. $scripttag = UCDN::$uni_scriptblock[$scriptblock];
  178. $GSUBscriptTag = '';
  179. $GSUBlangsys = '';
  180. $GPOSscriptTag = '';
  181. $GPOSlangsys = '';
  182. $is_old_spec = false;
  183. $ScriptLang = $this->mpdf->CurrentFont['GSUBScriptLang'];
  184. if (count($ScriptLang)) {
  185. list($GSUBscriptTag, $is_old_spec) = $this->_getOTLscriptTag($ScriptLang, $scripttag, $scriptblock, $this->shaper, $useOTL, 'GSUB');
  186. if ($this->mpdf->fontLanguageOverride && strpos($ScriptLang[$GSUBscriptTag], $this->mpdf->fontLanguageOverride) !== false) {
  187. $GSUBlangsys = str_pad($this->mpdf->fontLanguageOverride, 4);
  188. } else if ($GSUBscriptTag && isset($ScriptLang[$GSUBscriptTag]) && $ScriptLang[$GSUBscriptTag] != '') {
  189. $GSUBlangsys = $this->_getOTLLangTag($this->mpdf->currentLang, $ScriptLang[$GSUBscriptTag]);
  190. }
  191. }
  192. $ScriptLang = $this->mpdf->CurrentFont['GPOSScriptLang'];
  193. // NB If after GSUB, the same script/lang exist for GPOS, just use these...
  194. if ($GSUBscriptTag && $GSUBlangsys && isset($ScriptLang[$GSUBscriptTag]) && strpos($ScriptLang[$GSUBscriptTag], $GSUBlangsys) !== false) {
  195. $GPOSlangsys = $GSUBlangsys;
  196. $GPOSscriptTag = $GSUBscriptTag;
  197. }
  198. // else repeat for GPOS
  199. // [Font XBRiyaz has GSUB tables for latn, but not GPOS for latn]
  200. else if (count($ScriptLang)) {
  201. list($GPOSscriptTag, $dummy) = $this->_getOTLscriptTag($ScriptLang, $scripttag, $scriptblock, $this->shaper, $useOTL, 'GPOS');
  202. if ($GPOSscriptTag && $this->mpdf->fontLanguageOverride && strpos($ScriptLang[$GPOSscriptTag], $this->mpdf->fontLanguageOverride) !== false) {
  203. $GPOSlangsys = str_pad($this->mpdf->fontLanguageOverride, 4);
  204. } else if ($GPOSscriptTag && isset($ScriptLang[$GPOSscriptTag]) && $ScriptLang[$GPOSscriptTag] != '') {
  205. $GPOSlangsys = $this->_getOTLLangTag($this->mpdf->currentLang, $ScriptLang[$GPOSscriptTag]);
  206. }
  207. }
  208. ////////////////////////////////////////////////////////////////
  209. // This is just for the font_dump_OTL utility to set script and langsys override
  210. if (isset($this->mpdf->overrideOTLsettings) && isset($this->mpdf->overrideOTLsettings[$this->fontkey])) {
  211. $GSUBscriptTag = $GPOSscriptTag = $this->mpdf->overrideOTLsettings[$this->fontkey]['script'];
  212. $GSUBlangsys = $GPOSlangsys = $this->mpdf->overrideOTLsettings[$this->fontkey]['lang'];
  213. }
  214. ////////////////////////////////////////////////////////////////
  215. if (!$GSUBscriptTag && !$GSUBlangsys && !$GPOSscriptTag && !$GPOSlangsys) {
  216. // Remove ZWJ and ZWNJ
  217. for ($i = 0; $i < count($this->OTLdata); $i++) {
  218. if ($this->OTLdata[$i]['uni'] == 8204 || $this->OTLdata[$i]['uni'] == 8205) {
  219. array_splice($this->OTLdata, $i, 1);
  220. }
  221. }
  222. $this->schOTLdata[$sch] = $this->OTLdata;
  223. $this->OTLdata = array();
  224. continue;
  225. }
  226. // Don't use MYANMAR shaper unless using v2 scripttag
  227. if ($this->shaper == 'M' && $GSUBscriptTag != 'mym2') {
  228. $this->shaper = '';
  229. }
  230. $GSUBFeatures = (isset($this->mpdf->CurrentFont['GSUBFeatures'][$GSUBscriptTag][$GSUBlangsys]) ? $this->mpdf->CurrentFont['GSUBFeatures'][$GSUBscriptTag][$GSUBlangsys] : false);
  231. $GPOSFeatures = (isset($this->mpdf->CurrentFont['GPOSFeatures'][$GPOSscriptTag][$GPOSlangsys]) ? $this->mpdf->CurrentFont['GPOSFeatures'][$GPOSscriptTag][$GPOSlangsys] : false);
  232. $this->assocLigs = array(); // Ligatures[$posarr lpos] => nc
  233. $this->assocMarks = array(); // assocMarks[$posarr mpos] => array(compID, ligPos)
  234. if (!isset($this->GDEFdata[$this->fontkey]['GSUBGPOStables'])) {
  235. $this->ttfOTLdata = $this->GDEFdata[$this->fontkey]['GSUBGPOStables'] = file_get_contents(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GSUBGPOStables.dat', 'rb');
  236. if (!$this->ttfOTLdata) {
  237. throw new MpdfException('Can\'t open file ' . _MPDF_TTFONTDATAPATH . $this->fontkey . '.GSUBGPOStables.dat');
  238. }
  239. } else {
  240. $this->ttfOTLdata = $this->GDEFdata[$this->fontkey]['GSUBGPOStables'];
  241. }
  242. if ($this->debugOTL) {
  243. $this->_dumpproc('BEGIN', '-', '-', '-', '-', -1, '-', 0);
  244. }
  245. ////////////////////////////////////////////////////////////////
  246. ////////////////////////////////////////////////////////////////
  247. ///////// LINE BREAKING FOR KHMER, THAI + LAO /////////////////
  248. ////////////////////////////////////////////////////////////////
  249. ////////////////////////////////////////////////////////////////
  250. // Insert U+200B at word boundaries using dictionaries
  251. if ($this->mpdf->useDictionaryLBR && ($this->shaper == "K" || $this->shaper == "T" || $this->shaper == "L")) {
  252. // Sets $this->OTLdata[$i]['wordend']=true at possible end of word boundaries
  253. $this->SEAlineBreaking();
  254. }
  255. // Insert U+200B at word boundaries for Tibetan
  256. else if ($this->mpdf->useTibetanLBR && $scriptblock == UCDN::SCRIPT_TIBETAN) {
  257. // Sets $this->OTLdata[$i]['wordend']=true at possible end of word boundaries
  258. $this->TibetanlineBreaking();
  259. }
  260. ////////////////////////////////////////////////////////////////
  261. ////////////////////////////////////////////////////////////////
  262. ////////// GSUB /////////////////////////////////
  263. ////////////////////////////////////////////////////////////////
  264. ////////////////////////////////////////////////////////////////
  265. if (($useOTL & 0xFF) && $GSUBscriptTag && $GSUBlangsys && $GSUBFeatures) {
  266. // 4. Load GSUB data, Coverage & Lookups
  267. //=================================================================
  268. $this->GSUBfont = $this->fontkey . '.GSUB.' . $GSUBscriptTag . '.' . $GSUBlangsys;
  269. if (!isset($this->GSUBdata[$this->GSUBfont])) {
  270. if (file_exists(_MPDF_TTFONTDATAPATH . $this->mpdf->CurrentFont['fontkey'] . '.GSUB.' . $GSUBscriptTag . '.' . $GSUBlangsys . '.php')) {
  271. include_once(_MPDF_TTFONTDATAPATH . $this->mpdf->CurrentFont['fontkey'] . '.GSUB.' . $GSUBscriptTag . '.' . $GSUBlangsys . '.php');
  272. $this->GSUBdata[$this->GSUBfont]['rtlSUB'] = $rtlSUB;
  273. $this->GSUBdata[$this->GSUBfont]['finals'] = $finals;
  274. if ($this->shaper == 'I') {
  275. $this->GSUBdata[$this->GSUBfont]['rphf'] = $rphf;
  276. $this->GSUBdata[$this->GSUBfont]['half'] = $half;
  277. $this->GSUBdata[$this->GSUBfont]['pref'] = $pref;
  278. $this->GSUBdata[$this->GSUBfont]['blwf'] = $blwf;
  279. $this->GSUBdata[$this->GSUBfont]['pstf'] = $pstf;
  280. }
  281. } else {
  282. $this->GSUBdata[$this->GSUBfont] = array('rtlSUB' => array(), 'rphf' => array(), 'rphf' => array(),
  283. 'pref' => array(), 'blwf' => array(), 'pstf' => array(), 'finals' => ''
  284. );
  285. }
  286. }
  287. if (!isset($this->GSUBdata[$this->fontkey])) {
  288. include(_MPDF_TTFONTDATAPATH . $this->fontkey . '.GSUBdata.php');
  289. $this->GSLuCoverage = $this->GSUBdata[$this->fontkey]['GSLuCoverage'] = $GSLuCoverage;
  290. } else {
  291. $this->GSLuCoverage = $this->GSUBdata[$this->fontkey]['GSLuCoverage'];
  292. }
  293. $this->GSUBLookups = $this->mpdf->CurrentFont['GSUBLookups'];
  294. // 5(A). GSUB - Shaper - ARABIC
  295. //==============================
  296. if ($this->shaper == 'A') {
  297. //-----------------------------------------------------------------------------------
  298. // a. Apply initial GSUB Lookups (in order specified in lookup list but only selecting from certain tags)
  299. //-----------------------------------------------------------------------------------
  300. $tags = 'locl ccmp';
  301. $omittags = '';
  302. $usetags = $tags;
  303. if (!empty($this->mpdf->OTLtags)) {
  304. $usetags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, true);
  305. }
  306. $this->_applyGSUBrules($usetags, $GSUBscriptTag, $GSUBlangsys);
  307. //-----------------------------------------------------------------------------------
  308. // b. Apply context-specific forms GSUB Lookups (initial, isolated, medial, final)
  309. //-----------------------------------------------------------------------------------
  310. // Arab and Syriac are the only scripts requiring the special joining - which takes the place of
  311. // isol fina medi init rules in GSUB (+ fin2 fin3 med2 in Syriac syrc)
  312. $tags = 'isol fina fin2 fin3 medi med2 init';
  313. $omittags = '';
  314. $usetags = $tags;
  315. if (!empty($this->mpdf->OTLtags)) {
  316. $usetags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, true);
  317. }
  318. $this->arabGlyphs = $this->GSUBdata[$this->GSUBfont]['rtlSUB'];
  319. $gcms = explode("| ", $this->GlyphClassMarks);
  320. $gcm = array();
  321. foreach ($gcms AS $g) {
  322. $gcm[hexdec($g)] = 1;
  323. }
  324. $this->arabTransparentJoin = $this->arabTransparent + $gcm;
  325. $this->arabic_shaper($usetags, $GSUBscriptTag);
  326. //-----------------------------------------------------------------------------------
  327. // c. Set Kashida points (after joining occurred - medi, fina, init) but before other substitutions
  328. //-----------------------------------------------------------------------------------
  329. //if ($scriptblock == UCDN::SCRIPT_ARABIC ) {
  330. for ($i = 0; $i < count($this->OTLdata); $i++) {
  331. // Put the kashida marker on the character BEFORE which is inserted the kashida
  332. // Kashida marker is inverse of priority i.e. Priority 1 => 7, Priority 7 => 1.
  333. // Priority 1 User-inserted Kashida 0640 = Tatweel
  334. // The user entered a Kashida in a position
  335. // Position: Before the user-inserted kashida
  336. if ($this->OTLdata[$i]['uni'] == 0x0640) {
  337. $this->OTLdata[$i]['GPOSinfo']['kashida'] = 8; // Put before the next character
  338. }
  339. // Priority 2 Seen (0633) FEB3, FEB4; Sad (0635) FEBB, FEBC
  340. // Initial or medial form
  341. // Connecting to the next character
  342. // Position: After the character
  343. else if ($this->OTLdata[$i]['uni'] == 0xFEB3 || $this->OTLdata[$i]['uni'] == 0xFEB4 || $this->OTLdata[$i]['uni'] == 0xFEBB || $this->OTLdata[$i]['uni'] == 0xFEBC) {
  344. $checkpos = $i + 1;
  345. while (isset($this->OTLdata[$checkpos]) && strpos($this->GlyphClassMarks, $this->OTLdata[$checkpos]['hex']) !== false) {
  346. $checkpos++;
  347. }
  348. if (isset($this->OTLdata[$checkpos])) {
  349. $this->OTLdata[$checkpos]['GPOSinfo']['kashida'] = 7; // Put after marks on next character
  350. }
  351. }
  352. // Priority 3 Taa Marbutah (0629) FE94; Haa (062D) FEA2; Dal (062F) FEAA
  353. // Final form
  354. // Connecting to previous character
  355. // Position: Before the character
  356. else if ($this->OTLdata[$i]['uni'] == 0xFE94 || $this->OTLdata[$i]['uni'] == 0xFEA2 || $this->OTLdata[$i]['uni'] == 0xFEAA) {
  357. $this->OTLdata[$i]['GPOSinfo']['kashida'] = 6;
  358. }
  359. // Priority 4 Alef (0627) FE8E; Tah (0637) FEC2; Lam (0644) FEDE; Kaf (0643) FEDA; Gaf (06AF) FB93
  360. // Final form
  361. // Connecting to previous character
  362. // Position: Before the character
  363. else if ($this->OTLdata[$i]['uni'] == 0xFE8E || $this->OTLdata[$i]['uni'] == 0xFEC2 || $this->OTLdata[$i]['uni'] == 0xFEDE || $this->OTLdata[$i]['uni'] == 0xFEDA || $this->OTLdata[$i]['uni'] == 0xFB93) {
  364. $this->OTLdata[$i]['GPOSinfo']['kashida'] = 5;
  365. }
  366. // Priority 5 RA (0631) FEAE; Ya (064A) FEF2 FEF4; Alef Maqsurah (0649) FEF0 FBE9
  367. // Final or Medial form
  368. // Connected to preceding medial BAA (0628) = FE92
  369. // Position: Before preceding medial Baa
  370. // Although not mentioned in spec, added Farsi Yeh (06CC) FBFD FBFF; equivalent to 064A or 0649
  371. else if ($this->OTLdata[$i]['uni'] == 0xFEAE || $this->OTLdata[$i]['uni'] == 0xFEF2 || $this->OTLdata[$i]['uni'] == 0xFEF0 || $this->OTLdata[$i]['uni'] == 0xFEF4 || $this->OTLdata[$i]['uni'] == 0xFBE9 || $this->OTLdata[$i]['uni'] == 0xFBFD || $this->OTLdata[$i]['uni'] == 0xFBFF
  372. ) {
  373. $checkpos = $i - 1;
  374. while (isset($this->OTLdata[$checkpos]) && strpos($this->GlyphClassMarks, $this->OTLdata[$checkpos]['hex']) !== false) {
  375. $checkpos--;
  376. }
  377. if (isset($this->OTLdata[$checkpos]) && $this->OTLdata[$checkpos]['uni'] == 0xFE92) {
  378. $this->OTLdata[$checkpos]['GPOSinfo']['kashida'] = 4; // ******* Before preceding BAA
  379. }
  380. }
  381. // Priority 6 WAW (0648) FEEE; Ain (0639) FECA; Qaf (0642) FED6; Fa (0641) FED2
  382. // Final form
  383. // Connecting to previous character
  384. // Position: Before the character
  385. else if ($this->OTLdata[$i]['uni'] == 0xFEEE || $this->OTLdata[$i]['uni'] == 0xFECA || $this->OTLdata[$i]['uni'] == 0xFED6 || $this->OTLdata[$i]['uni'] == 0xFED2) {
  386. $this->OTLdata[$i]['GPOSinfo']['kashida'] = 3;
  387. }
  388. // Priority 7 Other connecting characters
  389. // Final form
  390. // Connecting to previous character
  391. // Position: Before the character
  392. /* This isn't in the spec, but using MS WORD as a basis, give a lower priority to the 3 characters already checked
  393. in (5) above. Test case:
  394. &#x62e;&#x652;&#x631;&#x64e;&#x649;&#x670;
  395. &#x641;&#x64e;&#x62a;&#x64f;&#x630;&#x64e;&#x643;&#x651;&#x650;&#x631;
  396. */
  397. if (!isset($this->OTLdata[$i]['GPOSinfo']['kashida'])) {
  398. if (strpos($this->GSUBdata[$this->GSUBfont]['finals'], $this->OTLdata[$i]['hex']) !== false) { // ANY OTHER FINAL FORM
  399. $this->OTLdata[$i]['GPOSinfo']['kashida'] = 2;
  400. } else if (strpos('0FEAE 0FEF0 0FEF2', $this->OTLdata[$i]['hex']) !== false) { // not already included in 5 above
  401. $this->OTLdata[$i]['GPOSinfo']['kashida'] = 1;
  402. }
  403. }
  404. }
  405. //-----------------------------------------------------------------------------------
  406. // d. Apply Presentation Forms GSUB Lookups (+ any discretionary) - Apply one at a time in Feature order
  407. //-----------------------------------------------------------------------------------
  408. $tags = 'rlig calt liga clig mset';
  409. $omittags = 'locl ccmp nukt akhn rphf rkrf pref blwf abvf half pstf cfar vatu cjct init medi fina isol med2 fin2 fin3 ljmo vjmo tjmo';
  410. $usetags = $tags;
  411. if (!empty($this->mpdf->OTLtags)) {
  412. $usetags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, false);
  413. }
  414. $ts = explode(' ', $usetags);
  415. foreach ($ts AS $ut) { // - Apply one at a time in Feature order
  416. $this->_applyGSUBrules($ut, $GSUBscriptTag, $GSUBlangsys);
  417. }
  418. //-----------------------------------------------------------------------------------
  419. // e. NOT IN SPEC
  420. // If space precedes a mark -> substitute a &nbsp; before the Mark, to prevent line breaking Test:
  421. //-----------------------------------------------------------------------------------
  422. for ($ptr = 1; $ptr < count($this->OTLdata); $ptr++) {
  423. if ($this->OTLdata[$ptr]['general_category'] == UCDN::UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK && $this->OTLdata[$ptr - 1]['uni'] == 32) {
  424. $this->OTLdata[$ptr - 1]['uni'] = 0xa0;
  425. $this->OTLdata[$ptr - 1]['hex'] = '000A0';
  426. }
  427. }
  428. }
  429. // 5(I). GSUB - Shaper - INDIC and SINHALA and KHMER
  430. //===================================
  431. else if ($this->shaper == 'I' || $this->shaper == 'K' || $this->shaper == 'S') {
  432. $this->restrictToSyllable = true;
  433. //-----------------------------------------------------------------------------------
  434. // a. First decompose/compose split mattras
  435. // (normalize) ??????? Nukta/Halant order etc ??????????????????????????????????????????????????????????????????????????
  436. //-----------------------------------------------------------------------------------
  437. for ($ptr = 0; $ptr < count($this->OTLdata); $ptr++) {
  438. $char = $this->OTLdata[$ptr]['uni'];
  439. $sub = INDIC::decompose_indic($char);
  440. if ($sub) {
  441. $newinfo = array();
  442. for ($i = 0; $i < count($sub); $i++) {
  443. $newinfo[$i] = array();
  444. $ucd_record = UCDN::get_ucd_record($sub[$i]);
  445. $newinfo[$i]['general_category'] = $ucd_record[0];
  446. $newinfo[$i]['bidi_type'] = $ucd_record[2];
  447. $charasstr = $this->unicode_hex($sub[$i]);
  448. if (strpos($this->GlyphClassMarks, $charasstr) !== false) {
  449. $newinfo[$i]['group'] = 'M';
  450. } else {
  451. $newinfo[$i]['group'] = 'C';
  452. }
  453. $newinfo[$i]['uni'] = $sub[$i];
  454. $newinfo[$i]['hex'] = $charasstr;
  455. }
  456. array_splice($this->OTLdata, $ptr, 1, $newinfo);
  457. $ptr += count($sub) - 1;
  458. }
  459. /* Only Composition-exclusion exceptions that we want to recompose. */
  460. if ($this->shaper == 'I') {
  461. if ($char == 0x09AF && isset($this->OTLdata[$ptr + 1]) && $this->OTLdata[$ptr + 1]['uni'] == 0x09BC) {
  462. $sub = 0x09DF;
  463. $newinfo = array();
  464. $newinfo[0] = array();
  465. $ucd_record = UCDN::get_ucd_record($sub);
  466. $newinfo[0]['general_category'] = $ucd_record[0];
  467. $newinfo[0]['bidi_type'] = $ucd_record[2];
  468. $newinfo[0]['group'] = 'C';
  469. $newinfo[0]['uni'] = $sub;
  470. $newinfo[0]['hex'] = $this->unicode_hex($sub);
  471. array_splice($this->OTLdata, $ptr, 2, $newinfo);
  472. }
  473. }
  474. }
  475. //-----------------------------------------------------------------------------------
  476. // b. Analyse characters - group as syllables/clusters (Indic); invalid diacritics; add dotted circle
  477. //-----------------------------------------------------------------------------------
  478. $indic_category_string = '';
  479. foreach ($this->OTLdata AS $eid => $c) {
  480. INDIC::set_indic_properties($this->OTLdata[$eid], $scriptblock); // sets ['indic_category'] and ['indic_position']
  481. //$c['general_category']
  482. //$c['combining_class']
  483. //$c['uni'] = $char;
  484. $indic_category_string .= INDIC::$indic_category_char[$this->OTLdata[$eid]['indic_category']];
  485. }
  486. $broken_syllables = false;
  487. if ($this->shaper == 'I') {
  488. INDIC::set_syllables($this->OTLdata, $indic_category_string, $broken_syllables);
  489. } else if ($this->shaper == 'S') {
  490. INDIC::set_syllables_sinhala($this->OTLdata, $indic_category_string, $broken_syllables);
  491. } else if ($this->shaper == 'K') {
  492. INDIC::set_syllables_khmer($this->OTLdata, $indic_category_string, $broken_syllables);
  493. }
  494. $indic_category_string = '';
  495. //-----------------------------------------------------------------------------------
  496. // c. Initial Re-ordering (Indic / Khmer / Sinhala)
  497. //-----------------------------------------------------------------------------------
  498. // Find base consonant
  499. // Decompose/compose and reorder Matras
  500. // Reorder marks to canonical order
  501. $indic_config = INDIC::$indic_configs[$scriptblock];
  502. $dottedcircle = false;
  503. if ($broken_syllables) {
  504. if ($this->mpdf->_charDefined($this->mpdf->fonts[$this->fontkey]['cw'], 0x25CC)) {
  505. $dottedcircle = array();
  506. $ucd_record = UCDN::get_ucd_record(0x25CC);
  507. $dottedcircle[0]['general_category'] = $ucd_record[0];
  508. $dottedcircle[0]['bidi_type'] = $ucd_record[2];
  509. $dottedcircle[0]['group'] = 'C';
  510. $dottedcircle[0]['uni'] = 0x25CC;
  511. $dottedcircle[0]['indic_category'] = INDIC::OT_DOTTEDCIRCLE;
  512. $dottedcircle[0]['indic_position'] = INDIC::POS_BASE_C;
  513. $dottedcircle[0]['hex'] = '025CC'; // TEMPORARY *****
  514. }
  515. }
  516. INDIC::initial_reordering($this->OTLdata, $this->GSUBdata[$this->GSUBfont], $broken_syllables, $indic_config, $scriptblock, $is_old_spec, $dottedcircle);
  517. //-----------------------------------------------------------------------------------
  518. // d. Apply initial and basic shaping forms GSUB Lookups (one at a time)
  519. //-----------------------------------------------------------------------------------
  520. if ($this->shaper == 'I' || $this->shaper == 'S') {
  521. $tags = 'locl ccmp nukt akhn rphf rkrf pref blwf half pstf vatu cjct';
  522. } else if ($this->shaper == 'K') {
  523. $tags = 'locl ccmp pref blwf abvf pstf cfar';
  524. }
  525. $this->_applyGSUBrulesIndic($tags, $GSUBscriptTag, $GSUBlangsys, $is_old_spec);
  526. //-----------------------------------------------------------------------------------
  527. // e. Final Re-ordering (Indic / Khmer / Sinhala)
  528. //-----------------------------------------------------------------------------------
  529. // Reorder matras
  530. // Reorder reph
  531. // Reorder pre-base reordering consonants:
  532. INDIC::final_reordering($this->OTLdata, $this->GSUBdata[$this->GSUBfont], $indic_config, $scriptblock, $is_old_spec);
  533. //-----------------------------------------------------------------------------------
  534. // f. Apply 'init' feature to first syllable in word (indicated by ['mask']) INDIC::FLAG(INDIC::INIT);
  535. //-----------------------------------------------------------------------------------
  536. if ($this->shaper == 'I' || $this->shaper == 'S') {
  537. $tags = 'init';
  538. $this->_applyGSUBrulesIndic($tags, $GSUBscriptTag, $GSUBlangsys, $is_old_spec);
  539. }
  540. //-----------------------------------------------------------------------------------
  541. // g. Apply Presentation Forms GSUB Lookups (+ any discretionary)
  542. //-----------------------------------------------------------------------------------
  543. $tags = 'pres abvs blws psts haln rlig calt liga clig mset';
  544. $omittags = 'locl ccmp nukt akhn rphf rkrf pref blwf abvf half pstf cfar vatu cjct init medi fina isol med2 fin2 fin3 ljmo vjmo tjmo';
  545. $usetags = $tags;
  546. if (!empty($this->mpdf->OTLtags)) {
  547. $usetags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, false);
  548. }
  549. if ($this->shaper == 'K') { // Features are applied one at a time, working through each codepoint
  550. $this->_applyGSUBrulesSingly($usetags, $GSUBscriptTag, $GSUBlangsys);
  551. } else {
  552. $this->_applyGSUBrules($usetags, $GSUBscriptTag, $GSUBlangsys);
  553. }
  554. $this->restrictToSyllable = false;
  555. }
  556. // 5(M). GSUB - Shaper - MYANMAR (ONLY mym2)
  557. //==============================
  558. // NB Old style 'mymr' is left to go through the default shaper
  559. else if ($this->shaper == 'M') {
  560. $this->restrictToSyllable = true;
  561. //-----------------------------------------------------------------------------------
  562. // a. Analyse characters - group as syllables/clusters (Myanmar); invalid diacritics; add dotted circle
  563. //-----------------------------------------------------------------------------------
  564. $myanmar_category_string = '';
  565. foreach ($this->OTLdata AS $eid => $c) {
  566. MYANMAR::set_myanmar_properties($this->OTLdata[$eid]); // sets ['myanmar_category'] and ['myanmar_position']
  567. $myanmar_category_string .= MYANMAR::$myanmar_category_char[$this->OTLdata[$eid]['myanmar_category']];
  568. }
  569. $broken_syllables = false;
  570. MYANMAR::set_syllables($this->OTLdata, $myanmar_category_string, $broken_syllables);
  571. $myanmar_category_string = '';
  572. //-----------------------------------------------------------------------------------
  573. // b. Re-ordering (Myanmar mym2)
  574. //-----------------------------------------------------------------------------------
  575. $dottedcircle = false;
  576. if ($broken_syllables) {
  577. if ($this->mpdf->_charDefined($this->mpdf->fonts[$this->fontkey]['cw'], 0x25CC)) {
  578. $dottedcircle = array();
  579. $ucd_record = UCDN::get_ucd_record(0x25CC);
  580. $dottedcircle[0]['general_category'] = $ucd_record[0];
  581. $dottedcircle[0]['bidi_type'] = $ucd_record[2];
  582. $dottedcircle[0]['group'] = 'C';
  583. $dottedcircle[0]['uni'] = 0x25CC;
  584. $dottedcircle[0]['myanmar_category'] = MYANMAR::OT_DOTTEDCIRCLE;
  585. $dottedcircle[0]['myanmar_position'] = MYANMAR::POS_BASE_C;
  586. $dottedcircle[0]['hex'] = '025CC';
  587. }
  588. }
  589. MYANMAR::reordering($this->OTLdata, $this->GSUBdata[$this->GSUBfont], $broken_syllables, $dottedcircle);
  590. //-----------------------------------------------------------------------------------
  591. // c. Apply initial and basic shaping forms GSUB Lookups (one at a time)
  592. //-----------------------------------------------------------------------------------
  593. $tags = 'locl ccmp rphf pref blwf pstf';
  594. $this->_applyGSUBrulesMyanmar($tags, $GSUBscriptTag, $GSUBlangsys);
  595. //-----------------------------------------------------------------------------------
  596. // d. Apply Presentation Forms GSUB Lookups (+ any discretionary)
  597. //-----------------------------------------------------------------------------------
  598. $tags = 'pres abvs blws psts haln rlig calt liga clig mset';
  599. $omittags = 'locl ccmp nukt akhn rphf rkrf pref blwf abvf half pstf cfar vatu cjct init medi fina isol med2 fin2 fin3 ljmo vjmo tjmo';
  600. $usetags = $tags;
  601. if (!empty($this->mpdf->OTLtags)) {
  602. $usetags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, false);
  603. }
  604. $this->_applyGSUBrules($usetags, $GSUBscriptTag, $GSUBlangsys);
  605. $this->restrictToSyllable = false;
  606. }
  607. // 5(E). GSUB - Shaper - SEA South East Asian (New Tai Lue, Cham, Tai Tam)
  608. //==============================
  609. else if ($this->shaper == 'E') {
  610. /* HarfBuzz says: If the designer designed the font for the 'DFLT' script,
  611. * use the default shaper. Otherwise, use the SEA shaper.
  612. * Note that for some simple scripts, there may not be *any*
  613. * GSUB/GPOS needed, so there may be no scripts found! */
  614. $this->restrictToSyllable = true;
  615. //-----------------------------------------------------------------------------------
  616. // a. Analyse characters - group as syllables/clusters (Indic); invalid diacritics; add dotted circle
  617. //-----------------------------------------------------------------------------------
  618. $sea_category_string = '';
  619. foreach ($this->OTLdata AS $eid => $c) {
  620. SEA::set_sea_properties($this->OTLdata[$eid], $scriptblock); // sets ['sea_category'] and ['sea_position']
  621. //$c['general_category']
  622. //$c['combining_class']
  623. //$c['uni'] = $char;
  624. $sea_category_string .= SEA::$sea_category_char[$this->OTLdata[$eid]['sea_category']];
  625. }
  626. $broken_syllables = false;
  627. SEA::set_syllables($this->OTLdata, $sea_category_string, $broken_syllables);
  628. $sea_category_string = '';
  629. //-----------------------------------------------------------------------------------
  630. // b. Apply locl and ccmp shaping forms - before initial re-ordering; GSUB Lookups (one at a time)
  631. //-----------------------------------------------------------------------------------
  632. $tags = 'locl ccmp';
  633. $this->_applyGSUBrulesSingly($tags, $GSUBscriptTag, $GSUBlangsys);
  634. //-----------------------------------------------------------------------------------
  635. // c. Initial Re-ordering
  636. //-----------------------------------------------------------------------------------
  637. // Find base consonant
  638. // Decompose/compose and reorder Matras
  639. // Reorder marks to canonical order
  640. $dottedcircle = false;
  641. if ($broken_syllables) {
  642. if ($this->mpdf->_charDefined($this->mpdf->fonts[$this->fontkey]['cw'], 0x25CC)) {
  643. $dottedcircle = array();
  644. $ucd_record = UCDN::get_ucd_record(0x25CC);
  645. $dottedcircle[0]['general_category'] = $ucd_record[0];
  646. $dottedcircle[0]['bidi_type'] = $ucd_record[2];
  647. $dottedcircle[0]['group'] = 'C';
  648. $dottedcircle[0]['uni'] = 0x25CC;
  649. $dottedcircle[0]['sea_category'] = SEA::OT_GB;
  650. $dottedcircle[0]['sea_position'] = SEA::POS_BASE_C;
  651. $dottedcircle[0]['hex'] = '025CC'; // TEMPORARY *****
  652. }
  653. }
  654. SEA::initial_reordering($this->OTLdata, $this->GSUBdata[$this->GSUBfont], $broken_syllables, $scriptblock, $dottedcircle);
  655. //-----------------------------------------------------------------------------------
  656. // d. Apply basic shaping forms GSUB Lookups (one at a time)
  657. //-----------------------------------------------------------------------------------
  658. $tags = 'pref abvf blwf pstf';
  659. $this->_applyGSUBrulesSingly($tags, $GSUBscriptTag, $GSUBlangsys);
  660. //-----------------------------------------------------------------------------------
  661. // e. Final Re-ordering
  662. //-----------------------------------------------------------------------------------
  663. SEA::final_reordering($this->OTLdata, $this->GSUBdata[$this->GSUBfont], $scriptblock);
  664. //-----------------------------------------------------------------------------------
  665. // f. Apply Presentation Forms GSUB Lookups (+ any discretionary)
  666. //-----------------------------------------------------------------------------------
  667. $tags = 'pres abvs blws psts';
  668. $omittags = 'locl ccmp nukt akhn rphf rkrf pref blwf abvf half pstf cfar vatu cjct init medi fina isol med2 fin2 fin3 ljmo vjmo tjmo';
  669. $usetags = $tags;
  670. if (!empty($this->mpdf->OTLtags)) {
  671. $usetags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, false);
  672. }
  673. $this->_applyGSUBrules($usetags, $GSUBscriptTag, $GSUBlangsys);
  674. $this->restrictToSyllable = false;
  675. }
  676. // 5(D). GSUB - Shaper - DEFAULT (including THAI and LAO and MYANMAR v1 [mymr] and TIBETAN)
  677. //==============================
  678. else { // DEFAULT
  679. //-----------------------------------------------------------------------------------
  680. // a. First decompose/compose in Thai / Lao - Tibetan
  681. //-----------------------------------------------------------------------------------
  682. // Decomposition for THAI or LAO
  683. /* This function implements the shaping logic documented here:
  684. *
  685. * http://linux.thai.net/~thep/th-otf/shaping.html
  686. *
  687. * The first shaping rule listed there is needed even if the font has Thai
  688. * OpenType tables.
  689. *
  690. *
  691. * The following is NOT specified in the MS OT Thai spec, however, it seems
  692. * to be what Uniscribe and other engines implement. According to Eric Muller:
  693. *
  694. * When you have a SARA AM, decompose it in NIKHAHIT + SARA AA, *and* move the
  695. * NIKHAHIT backwards over any tone mark (0E48-0E4B).
  696. *
  697. * <0E14, 0E4B, 0E33> -> <0E14, 0E4D, 0E4B, 0E32>
  698. *
  699. * This reordering is legit only when the NIKHAHIT comes from a SARA AM, not
  700. * when it's there to start with. The string <0E14, 0E4B, 0E4D> is probably
  701. * not what a user wanted, but the rendering is nevertheless nikhahit above
  702. * chattawa.
  703. *
  704. * Same for Lao.
  705. *
  706. * Thai Lao
  707. * SARA AM: U+0E33 U+0EB3
  708. * SARA AA: U+0E32 U+0EB2
  709. * Nikhahit: U+0E4D U+0ECD
  710. *
  711. * Testing shows that Uniscribe reorder the following marks:
  712. * Thai: <0E31,0E34..0E37,0E47..0E4E>
  713. * Lao: <0EB1,0EB4..0EB7,0EC7..0ECE>
  714. *
  715. * Lao versions are the same as Thai + 0x80.
  716. */
  717. if ($this->shaper == 'T' || $this->shaper == 'L') {
  718. for ($ptr = 0; $ptr < count($this->OTLdata); $ptr++) {
  719. $char = $this->OTLdata[$ptr]['uni'];
  720. if (($char & ~0x0080) == 0x0E33) { // if SARA_AM (U+0E33 or U+0EB3)
  721. $NIKHAHIT = $char + 0x1A;
  722. $SARA_AA = $char - 1;
  723. $sub = array($SARA_AA, $NIKHAHIT);
  724. $newinfo = array();
  725. $ucd_record = UCDN::get_ucd_record($sub[0]);
  726. $newinfo[0]['general_category'] = $ucd_record[0];
  727. $newinfo[0]['bidi_type'] = $ucd_record[2];
  728. $charasstr = $this->unicode_hex($sub[0]);
  729. if (strpos($this->GlyphClassMarks, $charasstr) !== false) {
  730. $newinfo[0]['group'] = 'M';
  731. } else {
  732. $newinfo[0]['group'] = 'C';
  733. }
  734. $newinfo[0]['uni'] = $sub[0];
  735. $newinfo[0]['hex'] = $charasstr;
  736. $this->OTLdata[$ptr] = $newinfo[0]; // Substitute SARA_AM => SARA_AA
  737. $ntones = 0; // number of (preceding) tone marks
  738. // IS_TONE_MARK ((x) & ~0x0080, 0x0E34 - 0x0E37, 0x0E47 - 0x0E4E, 0x0E31)
  739. while (isset($this->OTLdata[$ptr - 1 - $ntones]) && (
  740. ($this->OTLdata[$ptr - 1 - $ntones]['uni'] & ~0x0080) == 0x0E31 ||
  741. (($this->OTLdata[$ptr - 1 - $ntones]['uni'] & ~0x0080) >= 0x0E34 &&
  742. ($this->OTLdata[$ptr - 1 - $ntones]['uni'] & ~0x0080) <= 0x0E37) ||
  743. (($this->OTLdata[$ptr - 1 - $ntones]['uni'] & ~0x0080) >= 0x0E47 &&
  744. ($this->OTLdata[$ptr - 1 - $ntones]['uni'] & ~0x0080) <= 0x0E4E)
  745. )
  746. ) {
  747. $ntones++;
  748. }
  749. $newinfo = array();
  750. $ucd_record = UCDN::get_ucd_record($sub[1]);
  751. $newinfo[0]['general_category'] = $ucd_record[0];
  752. $newinfo[0]['bidi_type'] = $ucd_record[2];
  753. $charasstr = $this->unicode_hex($sub[1]);
  754. if (strpos($this->GlyphClassMarks, $charasstr) !== false) {
  755. $newinfo[0]['group'] = 'M';
  756. } else {
  757. $newinfo[0]['group'] = 'C';
  758. }
  759. $newinfo[0]['uni'] = $sub[1];
  760. $newinfo[0]['hex'] = $charasstr;
  761. // Insert NIKAHIT
  762. array_splice($this->OTLdata, $ptr - $ntones, 0, $newinfo);
  763. $ptr++;
  764. }
  765. }
  766. }
  767. if ($scriptblock == UCDN::SCRIPT_TIBETAN) {
  768. // =========================
  769. // Reordering TIBETAN
  770. // =========================
  771. // Tibetan does not need to need a shaper generally, as long as characters are presented in the correct order
  772. // so we will do one minor change here:
  773. // From ICU: If the present character is a number, and the next character is a pre-number combining mark
  774. // then the two characters are reordered
  775. // From MS OTL spec the following are Digit modifiers (Md): 0F18–0F19, 0F3E–0F3F
  776. // Digits: 0F20–0F33
  777. // On testing only 0x0F3F (pre-based mark) seems to need re-ordering
  778. for ($ptr = 0; $ptr < count($this->OTLdata) - 1; $ptr++) {
  779. if (INDIC::in_range($this->OTLdata[$ptr]['uni'], 0x0F20, 0x0F33) && $this->OTLdata[$ptr + 1]['uni'] == 0x0F3F) {
  780. $tmp = $this->OTLdata[$ptr + 1];
  781. $this->OTLdata[$ptr + 1] = $this->OTLdata[$ptr];
  782. $this->OTLdata[$ptr] = $tmp;
  783. }
  784. }
  785. // =========================
  786. // Decomposition for TIBETAN
  787. // =========================
  788. /* Recommended, but does not seem to change anything...
  789. for($ptr=0; $ptr<count($this->OTLdata); $ptr++) {
  790. $char = $this->OTLdata[$ptr]['uni'];
  791. $sub = INDIC::decompose_indic($char);
  792. if ($sub) {
  793. $newinfo = array();
  794. for($i=0;$i<count($sub);$i++) {
  795. $newinfo[$i] = array();
  796. $ucd_record = UCDN::get_ucd_record($sub[$i]);
  797. $newinfo[$i]['general_category'] = $ucd_record[0];
  798. $newinfo[$i]['bidi_type'] = $ucd_record[2];
  799. $charasstr = $this->unicode_hex($sub[$i]);
  800. if (strpos($this->GlyphClassMarks, $charasstr)!==false) { $newinfo[$i]['group'] = 'M'; }
  801. else { $newinfo[$i]['group'] = 'C'; }
  802. $newinfo[$i]['uni'] = $sub[$i];
  803. $newinfo[$i]['hex'] = $charasstr;
  804. }
  805. array_splice($this->OTLdata, $ptr, 1, $newinfo);
  806. $ptr += count($sub)-1;
  807. }
  808. }
  809. */
  810. }
  811. //-----------------------------------------------------------------------------------
  812. // b. Apply all GSUB Lookups (in order specified in lookup list)
  813. //-----------------------------------------------------------------------------------
  814. $tags = 'locl ccmp pref blwf abvf pstf pres abvs blws psts haln rlig calt liga clig mset RQD';
  815. // pref blwf abvf pstf required for Tibetan
  816. // " RQD" is a non-standard tag in Garuda font - presumably intended to be used by default ? "ReQuireD"
  817. // Being a 3 letter tag is non-standard, and does not allow it to be set by font-feature-settings
  818. /* ?Add these until shapers witten?
  819. Hangul: ljmo vjmo tjmo
  820. */
  821. $omittags = '';
  822. $useGSUBtags = $tags;
  823. if (!empty($this->mpdf->OTLtags)) {
  824. $useGSUBtags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, false);
  825. }
  826. // APPLY GSUB rules (as long as not Latin + SmallCaps - but not OTL smcp)
  827. if (!(($this->mpdf->textvar & FC_SMALLCAPS) && $scriptblock == UCDN::SCRIPT_LATIN && strpos($useGSUBtags, 'smcp') === false)) {
  828. $this->_applyGSUBrules($useGSUBtags, $GSUBscriptTag, $GSUBlangsys);
  829. }
  830. }
  831. }
  832. // Shapers - KHMER & THAI & LAO - Replace Word boundary marker with U+200B
  833. // Also TIBETAN (no shaper)
  834. //=======================================================
  835. if (($this->shaper == "K" || $this->shaper == "T" || $this->shaper == "L") || $scriptblock == UCDN::SCRIPT_TIBETAN) {
  836. // Set up properties to insert a U+200B character
  837. $newinfo = array();
  838. //$newinfo[0] = array('general_category' => 1, 'bidi_type' => 14, 'group' => 'S', 'uni' => 0x200B, 'hex' => '0200B');
  839. $newinfo[0] = array(
  840. 'general_category' => UCDN::UNICODE_GENERAL_CATEGORY_FORMAT,
  841. 'bidi_type' => UCDN::BIDI_CLASS_BN,
  842. 'group' => 'S', 'uni' => 0x200B, 'hex' => '0200B');
  843. // Then insert U+200B at (after) all word end boundaries
  844. for ($i = count($this->OTLdata) - 1; $i > 0; $i--) {
  845. // Make sure after GSUB that wordend has not been moved - check next char is not in the same syllable
  846. if (isset($this->OTLdata[$i]['wordend']) && $this->OTLdata[$i]['wordend'] &&
  847. isset($this->OTLdata[$i + 1]['uni']) && (!isset($this->OTLdata[$i + 1]['syllable']) || !isset($this->OTLdata[$i + 1]['syllable']) || $this->OTLdata[$i + 1]['syllable'] != $this->OTLdata[$i]['syllable'])) {
  848. array_splice($this->OTLdata, $i + 1, 0, $newinfo);
  849. $this->_updateLigatureMarks($i, 1);
  850. } else if ($this->OTLdata[$i]['uni'] == 0x2e) { // Word end if Full-stop.
  851. array_splice($this->OTLdata, $i + 1, 0, $newinfo);
  852. $this->_updateLigatureMarks($i, 1);
  853. }
  854. }
  855. }
  856. // Shapers - INDIC & ARABIC & KHMER & SINHALA & MYANMAR - Remove ZWJ and ZWNJ
  857. //=======================================================
  858. if ($this->shaper == 'I' || $this->shaper == 'S' || $this->shaper == 'A' || $this->shaper == 'K' || $this->shaper == 'M') {
  859. // Remove ZWJ and ZWNJ
  860. for ($i = 0; $i < count($this->OTLdata); $i++) {
  861. if ($this->OTLdata[$i]['uni'] == 8204 || $this->OTLdata[$i]['uni'] == 8205) {
  862. array_splice($this->OTLdata, $i, 1);
  863. $this->_updateLigatureMarks($i, -1);
  864. }
  865. }
  866. }
  867. //print_r($this->OTLdata); echo '<br />';
  868. //print_r($this->assocMarks); echo '<br />';
  869. //print_r($this->assocLigs); exit;
  870. ////////////////////////////////////////////////////////////////
  871. ////////////////////////////////////////////////////////////////
  872. ////////// GPOS /////////////////////////////////
  873. ////////////////////////////////////////////////////////////////
  874. ////////////////////////////////////////////////////////////////
  875. if (($useOTL & 0xFF) && $GPOSscriptTag && $GPOSlangsys && $GPOSFeatures) {
  876. $this->Entry = array();
  877. $this->Exit = array();
  878. // 6. Load GPOS data, Coverage & Lookups
  879. //=================================================================
  880. if (!isset($this->GPOSdata[$this->fontkey])) {
  881. include(_MPDF_TTFONTDATAPATH . $this->mpdf->CurrentFont['fontkey'] . '.GPOSdata.php');
  882. $this->LuCoverage = $this->GPOSdata[$this->fontkey]['LuCoverage'] = $LuCoverage;
  883. } else {
  884. $this->LuCoverage = $this->GPOSdata[$this->fontkey]['LuCoverage'];
  885. }
  886. $this->GPOSLookups = $this->mpdf->CurrentFont['GPOSLookups'];
  887. // 7. Select Feature tags to use (incl optional)
  888. //==============================
  889. $tags = 'abvm blwm mark mkmk curs cpsp dist requ'; // Default set
  890. /* 'requ' is not listed in the Microsoft registry of Feature tags
  891. Found in Arial Unicode MS, it repositions the baseline for punctuation in Kannada script */
  892. // ZZZ96
  893. // Set kern to be included by default in non-Latin script (? just when shapers used)
  894. // Kern is used in some fonts to reposition marks etc. and is essential for correct display
  895. //if ($this->shaper) {$tags .= ' kern'; }
  896. if ($scriptblock != UCDN::SCRIPT_LATIN) {
  897. $tags .= ' kern';
  898. }
  899. $omittags = '';
  900. $usetags = $tags;
  901. if (!empty($this->mpdf->OTLtags)) {
  902. $usetags = $this->_applyTagSettings($tags, $GPOSFeatures, $omittags, false);
  903. }
  904. // 8. Get GPOS LookupList from Feature tags
  905. //==============================
  906. $LookupList = array();
  907. foreach ($GPOSFeatures AS $tag => $arr) {
  908. if (strpos($usetags, $tag) !== false) {
  909. foreach ($arr AS $lu) {
  910. $LookupList[$lu] = $tag;
  911. }
  912. }
  913. }
  914. ksort($LookupList);
  915. // 9. Apply GPOS Lookups (in order specified in lookup list but selecting from specified tags)
  916. //==============================
  917. // APPLY THE GPOS RULES (as long as not Latin + SmallCaps - but not OTL smcp)
  918. if (!(($this->mpdf->textvar & FC_SMALLCAPS) && $scriptblock == UCDN::SCRIPT_LATIN && strpos($useGSUBtags, 'smcp') === false)) {
  919. $this->_applyGPOSrules($LookupList, $is_old_spec);
  920. // (sets: $this->OTLdata[n]['GPOSinfo'] XPlacement YPlacement XAdvance Entry Exit )
  921. }
  922. // 10. Process cursive text
  923. //==============================
  924. if (count($this->Entry) || count($this->Exit)) {
  925. // RTL
  926. $incurs = false;
  927. for ($i = (count($this->OTLdata) - 1); $i >= 0; $i--) {
  928. if (isset($this->Entry[$i]) && isset($this->Entry[$i]['Y']) && $this->Entry[$i]['dir'] == 'RTL') {
  929. $nextbase = $i - 1; // Set as next base ignoring marks (next base reading RTL in logical oder
  930. while (isset($this->OTLdata[$nextbase]['hex']) && strpos($this->GlyphClassMarks, $this->OTLdata[$nextbase]['hex']) !== false) {
  931. $nextbase--;
  932. }
  933. if (isset($this->Exit[$nextbase]) && isset($this->Exit[$nextbase]['Y'])) {
  934. $diff = $this->Entry[$i]['Y'] - $this->Exit[$nextbase]['Y'];
  935. if ($incurs === false) {
  936. $incurs = $diff;
  937. } else {
  938. $incurs += $diff;
  939. }
  940. for ($j = ($i - 1); $j >= $nextbase; $j--) {
  941. if (isset($this->OTLdata[$j]['GPOSinfo']['YPlacement'])) {
  942. $this->OTLdata[$j]['GPOSinfo']['YPlacement'] += $incurs;
  943. } else {
  944. $this->OTLdata[$j]['GPOSinfo']['YPlacement'] = $incurs;
  945. }
  946. }
  947. if (isset($this->Exit[$i]['X']) && isset($this->Entry[$nextbase]['X'])) {
  948. $adj = -($this->Entry[$i]['X'] - $this->Exit[$nextbase]['X']);
  949. // If XAdvance is aplied - in order for PDF to position the Advance correctly need to place it on:
  950. // in RTL - the current glyph or the last of any associated marks
  951. if (isset($this->OTLdata[$nextbase + 1]['GPOSinfo']['XAdvance'])) {
  952. $this->OTLdata[$nextbase + 1]['GPOSinfo']['XAdvance'] += $adj;
  953. } else {
  954. $this->OTLdata[$nextbase + 1]['GPOSinfo']['XAdvance'] = $adj;
  955. }
  956. }
  957. } else {
  958. $incurs = false;
  959. }
  960. } else if (strpos($this->GlyphClassMarks, $this->OTLdata[$i]['hex']) !== false) {
  961. continue;
  962. } // ignore Marks
  963. else {
  964. $incurs = false;
  965. }
  966. }
  967. // LTR
  968. $incurs = false;
  969. for ($i = 0; $i < count($this->OTLdata); $i++) {
  970. if (isset($this->Exit[$i]) && isset($this->Exit[$i]['Y']) && $this->Exit[$i]['dir'] == 'LTR') {
  971. $nextbase = $i + 1; // Set as next base ignoring marks
  972. while (strpos($this->GlyphClassMarks, $this->OTLdata[$nextbase]['hex']) !== false) {
  973. $nextbase++;
  974. }
  975. if (isset($this->Entry[$nextbase]) && isset($this->Entry[$nextbase]['Y'])) {
  976. $diff = $this->Exit[$i]['Y'] - $this->Entry[$nextbase]['Y'];
  977. if ($incurs === false) {
  978. $incurs = $diff;
  979. } else {
  980. $incurs += $diff;
  981. }
  982. for ($j = ($i + 1); $j <= $nextbase; $j++) {
  983. if (isset($this->OTLdata[$j]['GPOSinfo']['YPlacement'])) {
  984. $this->OTLdata[$j]['GPOSinfo']['YPlacement'] += $incurs;
  985. } else {
  986. $this->OTLdata[$j]['GPOSinfo']['YPlacement'] = $incurs;
  987. }
  988. }
  989. if (isset($this->Exit[$i]['X']) && isset($this->Entry[$nextbase]['X'])) {
  990. $adj = -($this->Exit[$i]['X'] - $this->Entry[$nextbase]['X']);
  991. // If XAdvance is aplied - in order for PDF to position the Advance correctly need to place it on:
  992. // in LTR - the next glyph, ignoring marks
  993. if (isset($this->OTLdata[$nextbase]['GPOSinfo']['XAdvance'])) {
  994. $this->OTLdata[$nextbase]['GPOSinfo']['XAdvance'] += $adj;
  995. } else {
  996. $this->OTLdata[$nextbase]['GPOSinfo']['XAdvance'] = $adj;
  997. }
  998. }
  999. } else {
  1000. $incurs = false;
  1001. }
  1002. } else if (strpos($this->GlyphClassMarks, $this->OTLdata[$i]['hex']) !== false) {
  1003. continue;
  1004. } // ignore Marks
  1005. else {
  1006. $incurs = false;
  1007. }
  1008. }
  1009. }
  1010. } // end GPOS
  1011. if ($this->debugOTL) {
  1012. $this->_dumpproc('END', '-', '-', '-', '-', 0, '-', 0);
  1013. exit;
  1014. }
  1015. $this->schOTLdata[$sch] = $this->OTLdata;
  1016. $this->OTLdata = array();
  1017. } // END foreach subchunk
  1018. // 11. Re-assemble and return text string
  1019. //==============================
  1020. $newGPOSinfo = array();
  1021. $newOTLdata = array();
  1022. $newchar_data = array();
  1023. $newgroup = '';
  1024. $e = '';
  1025. $ectr = 0;
  1026. for ($sch = 0; $sch <= $subchunk; $sch++) {
  1027. for ($i = 0; $i < count($this->schOTLdata[$sch]); $i++) {
  1028. if (isset($this->schOTLdata[$sch][$i]['GPOSinfo'])) {
  1029. $newGPOSinfo[$ectr] = $this->schOTLdata[$sch][$i]['GPOSinfo'];
  1030. }
  1031. $newchar_data[$ectr] = array('bidi_class' => $this->schOTLdata[$sch][$i]['bidi_type'], 'uni' => $this->schOTLdata[$sch][$i]['uni']);
  1032. $newgroup .= $this->schOTLdata[$sch][$i]['group'];
  1033. $e.=code2utf($this->schOTLdata[$sch][$i]['uni']);
  1034. if (isset($this->mpdf->CurrentFont['subset'])) {
  1035. $this->mpdf->CurrentFont['subset'][$this->schOTLdata[$sch][$i]['uni']] = $this->schOTLdata[$sch][$i]['uni'];
  1036. }
  1037. $ectr++;
  1038. }
  1039. }
  1040. $this->OTLdata['GPOSinfo'] = $newGPOSinfo;
  1041. $this->OTLdata['char_data'] = $newchar_data;
  1042. $this->OTLdata['group'] = $newgroup;
  1043. // This leaves OTLdata::GPOSinfo, ::bidi_type, & ::group
  1044. return $e;
  1045. }
  1046. function _applyTagSettings($tags, $Features, $omittags = '', $onlytags = false)
  1047. {
  1048. if (empty($this->mpdf->OTLtags['Plus']) && empty($this->mpdf->OTLtags['Minus']) && empty($this->mpdf->OTLtags['FFPlus']) && empty($this->mpdf->OTLtags['FFMinus'])) {
  1049. return $tags;
  1050. }
  1051. // Use $tags as starting point
  1052. $usetags = $tags;
  1053. // Only set / unset tags which are in the font
  1054. // Ignore tags which are in $omittags
  1055. // If $onlytags, then just unset tags which are already in the Tag list
  1056. $fp = $fm = $ffp = $ffm = '';
  1057. // Font features to enable - set by font-variant-xx
  1058. if (isset($this->mpdf->OTLtags['Plus']))
  1059. $fp = $this->mpdf->OTLtags['Plus'];
  1060. preg_match_all('/([a-zA-Z0-9]{4})/', $fp, $m);
  1061. for ($i = 0; $i < count($m[0]); $i++) {
  1062. $t = $m[1][$i];
  1063. // Is it a valid tag?
  1064. if (isset($Features[$t]) && strpos($omittags, $t) === false && (!$onlytags || strpos($tags, $t) !== false )) {
  1065. $usetags .= ' ' . $t;
  1066. }
  1067. }
  1068. // Font features to disable - set by font-variant-xx
  1069. if (isset($this->mpdf->OTLtags['Minus']))
  1070. $fm = $this->mpdf->OTLtags['Minus'];
  1071. preg_match_all('/([a-zA-Z0-9]{4})/', $fm, $m);
  1072. for ($i = 0; $i < count($m[0]); $i++) {
  1073. $t = $m[1][$i];
  1074. // Is it a valid tag?
  1075. if (isset($Features[$t]) && strpos($omittags, $t) === false && (!$onlytags || strpos($tags, $t) !== false )) {
  1076. $usetags = str_replace($t, '', $usetags);
  1077. }
  1078. }
  1079. // Font features to enable - set by font-feature-settings
  1080. if (isset($this->mpdf->OTLtags['FFPlus']))
  1081. $ffp = $this->mpdf->OTLtags['FFPlus']; // Font Features - may include integer: salt4
  1082. preg_match_all('/([a-zA-Z0-9]{4})([\d+]*)/', $ffp, $m);
  1083. for ($i = 0; $i < count($m[0]); $i++) {
  1084. $t = $m[1][$i];
  1085. // Is it a valid tag?
  1086. if (isset($Features[$t]) && strpos($omittags, $t) === false && (!$onlytags || strpos($tags, $t) !== false )) {
  1087. $usetags .= ' ' . $m[0][$i]; // - may include integer: salt4
  1088. }
  1089. }
  1090. // Font features to disable - set by font-feature-settings
  1091. if (isset($this->mpdf->OTLtags['FFMinus']))
  1092. $ffm = $this->mpdf->OTLtags['FFMinus'];
  1093. preg_match_all('/([a-zA-Z0-9]{4})/', $ffm, $m);
  1094. for ($i = 0; $i < count($m[0]); $i++) {
  1095. $t = $m[1][$i];
  1096. // Is it a valid tag?
  1097. if (isset($Features[$t]) && strpos($omittags, $t) === false && (!$onlytags || strpos($tags, $t) !== false )) {
  1098. $usetags = str_replace($t, '', $usetags);
  1099. }
  1100. }
  1101. return $usetags;
  1102. }
  1103. function _applyGSUBrules($usetags, $scriptTag, $langsys)
  1104. {
  1105. // Features from all Tags are applied together, in Lookup List order.
  1106. // For Indic - should be applied one syllable at a time
  1107. // - Implemented in functions checkContextMatch and checkContextMatchMultiple by failing to match if outside scope of current 'syllable'
  1108. // if $this->restrictToSyllable is true
  1109. $GSUBFeatures = $this->mpdf->CurrentFont['GSUBFeatures'][$scriptTag][$langsys];
  1110. $LookupList = array();
  1111. foreach ($GSUBFeatures AS $tag => $arr) {
  1112. if (strpos($usetags, $tag) !== false) {
  1113. foreach ($arr AS $lu) {
  1114. $LookupList[$lu] = $tag;
  1115. }
  1116. }
  1117. }
  1118. ksort($LookupList);
  1119. foreach ($LookupList AS $lu => $tag) {
  1120. $Type = $this->GSUBLookups[$lu]['Type'];
  1121. $Flag = $this->GSUBLookups[$lu]['Flag'];
  1122. $MarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet'];
  1123. $tagInt = 1;
  1124. if (preg_match('/' . $tag . '([0-9]{1,2})/', $usetags, $m)) {
  1125. $tagInt = $m[1];
  1126. }
  1127. $ptr = 0;
  1128. // Test each glyph sequentially
  1129. while ($ptr < (count($this->OTLdata))) { // whilst there is another glyph ..0064
  1130. $currGlyph = $this->OTLdata[$ptr]['hex'];
  1131. $currGID = $this->OTLdata[$ptr]['uni'];
  1132. $shift = 1;
  1133. foreach ($this->GSUBLookups[$lu]['Subtables'] AS $c => $subtable_offset) {
  1134. // NB Coverage only looks at glyphs for position 1 (esp. 7.3 and 8.3)
  1135. if (isset($this->GSLuCoverage[$lu][$c][$currGID])) {
  1136. // Get rules from font GSUB subtable
  1137. $shift = $this->_applyGSUBsubtable($lu, $c, $ptr, $currGlyph, $currGID, ($subtable_offset - $this->GSUB_offset), $Type, $Flag, $MarkFilteringSet, $this->GSLuCoverage[$lu][$c], 0, $tag, 0, $tagInt);
  1138. if ($shift) {
  1139. break;
  1140. }
  1141. }
  1142. }
  1143. if ($shift == 0) {
  1144. $shift = 1;
  1145. }
  1146. $ptr += $shift;
  1147. }
  1148. }
  1149. }
  1150. function _applyGSUBrulesSingly($usetags, $scriptTag, $langsys)
  1151. {
  1152. // Features are applied one at a time, working through each codepoint
  1153. $GSUBFeatures = $this->mpdf->CurrentFont['GSUBFeatures'][$scriptTag][$langsys];
  1154. $tags = explode(' ', $usetags);
  1155. foreach ($tags AS $usetag) {
  1156. $LookupList = array();
  1157. foreach ($GSUBFeatures AS $tag => $arr) {
  1158. if (strpos($usetags, $tag) !== false) {
  1159. foreach ($arr AS $lu) {
  1160. $LookupList[$lu] = $tag;
  1161. }
  1162. }
  1163. }
  1164. ksort($LookupList);
  1165. $ptr = 0;
  1166. // Test each glyph sequentially
  1167. while ($ptr < (count($this->OTLdata))) { // whilst there is another glyph ..0064
  1168. $currGlyph = $this->OTLdata[$ptr]['hex'];
  1169. $currGID = $this->OTLdata[$ptr]['uni'];
  1170. $shift = 1;
  1171. foreach ($LookupList AS $lu => $tag) {
  1172. $Type = $this->GSUBLookups[$lu]['Type'];
  1173. $Flag = $this->GSUBLookups[$lu]['Flag'];
  1174. $MarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet'];
  1175. $tagInt = 1;
  1176. if (preg_match('/' . $tag . '([0-9]{1,2})/', $usetags, $m)) {
  1177. $tagInt = $m[1];
  1178. }
  1179. foreach ($this->GSUBLookups[$lu]['Subtables'] AS $c => $subtable_offset) {
  1180. // NB Coverage only looks at glyphs for position 1 (esp. 7.3 and 8.3)
  1181. if (isset($this->GSLuCoverage[$lu][$c][$currGID])) {
  1182. // Get rules from font GSUB subtable
  1183. $shift = $this->_applyGSUBsubtable($lu, $c, $ptr, $currGlyph, $currGID, ($subtable_offset - $this->GSUB_offset), $Type, $Flag, $MarkFilteringSet, $this->GSLuCoverage[$lu][$c], 0, $tag, 0, $tagInt);
  1184. if ($shift) {
  1185. break 2;
  1186. }
  1187. }
  1188. }
  1189. }
  1190. if ($shift == 0) {
  1191. $shift = 1;
  1192. }
  1193. $ptr += $shift;
  1194. }
  1195. }
  1196. }
  1197. function _applyGSUBrulesMyanmar($usetags, $scriptTag, $langsys)
  1198. {
  1199. // $usetags = locl ccmp rphf pref blwf pstf';
  1200. // applied to all characters
  1201. $GSUBFeatures = $this->mpdf->CurrentFont['GSUBFeatures'][$scriptTag][$langsys];
  1202. // ALL should be applied one syllable at a time
  1203. // Implemented in functions checkContextMatch and checkContextMatchMultiple by failing to match if outside scope of current 'syllable'
  1204. $tags = explode(' ', $usetags);
  1205. foreach ($tags AS $usetag) {
  1206. $LookupList = array();
  1207. foreach ($GSUBFeatures AS $tag => $arr) {
  1208. if ($tag == $usetag) {
  1209. foreach ($arr AS $lu) {
  1210. $LookupList[$lu] = $tag;
  1211. }
  1212. }
  1213. }
  1214. ksort($LookupList);
  1215. foreach ($LookupList AS $lu => $tag) {
  1216. $Type = $this->GSUBLookups[$lu]['Type'];
  1217. $Flag = $this->GSUBLookups[$lu]['Flag'];
  1218. $MarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet'];
  1219. $tagInt = 1;
  1220. if (preg_match('/' . $tag . '([0-9]{1,2})/', $usetags, $m)) {
  1221. $tagInt = $m[1];
  1222. }
  1223. $ptr = 0;
  1224. // Test each glyph sequentially
  1225. while ($ptr < (count($this->OTLdata))) { // whilst there is another glyph ..0064
  1226. $currGlyph = $this->OTLdata[$ptr]['hex'];
  1227. $currGID = $this->OTLdata[$ptr]['uni'];
  1228. $shift = 1;
  1229. foreach ($this->GSUBLookups[$lu]['Subtables'] AS $c => $subtable_offset) {
  1230. // NB Coverage only looks at glyphs for position 1 (esp. 7.3 and 8.3)
  1231. if (isset($this->GSLuCoverage[$lu][$c][$currGID])) {
  1232. // Get rules from font GSUB subtable
  1233. $shift = $this->_applyGSUBsubtable($lu, $c, $ptr, $currGlyph, $currGID, ($subtable_offset - $this->GSUB_offset), $Type, $Flag, $MarkFilteringSet, $this->GSLuCoverage[$lu][$c], 0, $usetag, 0, $tagInt);
  1234. if ($shift) {
  1235. break;
  1236. }
  1237. }
  1238. }
  1239. if ($shift == 0) {
  1240. $shift = 1;
  1241. }
  1242. $ptr += $shift;
  1243. }
  1244. }
  1245. }
  1246. }
  1247. function _applyGSUBrulesIndic($usetags, $scriptTag, $langsys, $is_old_spec)
  1248. {
  1249. // $usetags = 'locl ccmp nukt akhn rphf rkrf pref blwf half pstf vatu cjct'; then later - init
  1250. // rphf, pref, blwf, half, abvf, pstf, and init are only applied where ['mask'] indicates: INDIC::FLAG(INDIC::RPHF);
  1251. // The rest are applied to all characters
  1252. $GSUBFeatures = $this->mpdf->CurrentFont['GSUBFeatures'][$scriptTag][$langsys];
  1253. // ALL should be applied one syllable at a time
  1254. // Implemented in functions checkContextMatch and checkContextMatchMultiple by failing to match if outside scope of current 'syllable'
  1255. $tags = explode(' ', $usetags);
  1256. foreach ($tags AS $usetag) {
  1257. $LookupList = array();
  1258. foreach ($GSUBFeatures AS $tag => $arr) {
  1259. if ($tag == $usetag) {
  1260. foreach ($arr AS $lu) {
  1261. $LookupList[$lu] = $tag;
  1262. }
  1263. }
  1264. }
  1265. ksort($LookupList);
  1266. foreach ($LookupList AS $lu => $tag) {
  1267. $Type = $this->GSUBLookups[$lu]['Type'];
  1268. $Flag = $this->GSUBLookups[$lu]['Flag'];
  1269. $MarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet'];
  1270. $tagInt = 1;
  1271. if (preg_match('/' . $tag . '([0-9]{1,2})/', $usetags, $m)) {
  1272. $tagInt = $m[1];
  1273. }
  1274. $ptr = 0;
  1275. // Test each glyph sequentially
  1276. while ($ptr < (count($this->OTLdata))) { // whilst there is another glyph ..0064
  1277. $currGlyph = $this->OTLdata[$ptr]['hex'];
  1278. $currGID = $this->OTLdata[$ptr]['uni'];
  1279. $shift = 1;
  1280. foreach ($this->GSUBLookups[$lu]['Subtables'] AS $c => $subtable_offset) {
  1281. // NB Coverage only looks at glyphs for position 1 (esp. 7.3 and 8.3)
  1282. if (isset($this->GSLuCoverage[$lu][$c][$currGID])) {
  1283. if (strpos('rphf pref blwf half pstf cfar init', $usetag) !== false) { // only apply when mask indicates
  1284. $mask = 0;
  1285. switch ($usetag) {
  1286. case 'rphf': $mask = (1 << (INDIC::RPHF));
  1287. break;
  1288. case 'pref': $mask = (1 << (INDIC::PREF));
  1289. break;
  1290. case 'blwf': $mask = (1 << (INDIC::BLWF));
  1291. break;
  1292. case 'half': $mask = (1 << (INDIC::HALF));
  1293. break;
  1294. case 'pstf': $mask = (1 << (INDIC::PSTF));
  1295. break;
  1296. case 'cfar': $mask = (1 << (INDIC::CFAR));
  1297. break;
  1298. case 'init': $mask = (1 << (INDIC::INIT));
  1299. break;
  1300. }
  1301. if (!($this->OTLdata[$ptr]['mask'] & $mask)) {
  1302. continue;
  1303. }
  1304. }
  1305. // Get rules from font GSUB subtable
  1306. $shift = $this->_applyGSUBsubtable($lu, $c, $ptr, $currGlyph, $currGID, ($subtable_offset - $this->GSUB_offset), $Type, $Flag, $MarkFilteringSet, $this->GSLuCoverage[$lu][$c], 0, $usetag, $is_old_spec, $tagInt);
  1307. if ($shift) {
  1308. break;
  1309. }
  1310. }
  1311. // Special case for Indic ZZZ99S
  1312. // Check to substitute Halant-Consonant in PREF, BLWF or PSTF
  1313. // i.e. new spec but GSUB tables have Consonant-Halant in Lookups e.g. FreeSerif, which
  1314. // incorrectly just moved old spec tables to new spec. Uniscribe seems to cope with this
  1315. // See also ttffontsuni.php
  1316. // First check if current glyph is a Halant/Virama
  1317. else if (_OTL_OLD_SPEC_COMPAT_1 && $Type == 4 && !$is_old_spec && strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', $currGlyph) !== false) {
  1318. // only apply when 'pref blwf pstf' tags, and when mask indicates
  1319. if (strpos('pref blwf pstf', $usetag) !== false) {
  1320. $mask = 0;
  1321. switch ($usetag) {
  1322. case 'pref': $mask = (1 << (INDIC::PREF));
  1323. break;
  1324. case 'blwf': $mask = (1 << (INDIC::BLWF));
  1325. break;
  1326. case 'pstf': $mask = (1 << (INDIC::PSTF));
  1327. break;
  1328. }
  1329. if (!($this->OTLdata[$ptr]['mask'] & $mask)) {
  1330. continue;
  1331. }
  1332. $nextGlyph = $this->OTLdata[$ptr + 1]['hex'];
  1333. $nextGID = $this->OTLdata[$ptr + 1]['uni'];
  1334. if (isset($this->GSLuCoverage[$lu][$c][$nextGID])) {
  1335. // Get rules from font GSUB subtable
  1336. $shift = $this->_applyGSUBsubtableSpecial($lu, $c, $ptr, $currGlyph, $currGID, $nextGlyph, $nextGID, ($subtable_offset - $this->GSUB_offset), $Type, $this->GSLuCoverage[$lu][$c]);
  1337. if ($shift) {
  1338. break;
  1339. }
  1340. }
  1341. }
  1342. }
  1343. }
  1344. if ($shift == 0) {
  1345. $shift = 1;
  1346. }
  1347. $ptr += $shift;
  1348. }
  1349. }
  1350. }
  1351. }
  1352. function _applyGSUBsubtableSpecial($lookupID, $subtable, $ptr, $currGlyph, $currGID, $nextGlyph, $nextGID, $subtable_offset, $Type, $LuCoverage)
  1353. {
  1354. // Special case for Indic
  1355. // Check to substitute Halant-Consonant in PREF, BLWF or PSTF
  1356. // i.e. new spec but GSUB tables have Consonant-Halant in Lookups e.g. FreeSerif, which
  1357. // incorrectly just moved old spec tables to new spec. Uniscribe seems to cope with this
  1358. // See also ttffontsuni.php
  1359. $this->seek($subtable_offset);
  1360. $SubstFormat = $this->read_ushort();
  1361. // Subtable contains Consonant - Halant
  1362. // Text string contains Halant ($CurrGlyph) - Consonant ($nextGlyph)
  1363. // Halant has already been matched, and already checked that $nextGID is in Coverage table
  1364. ////////////////////////////////////////////////////////////////////////////////
  1365. // Only does: LookupType 4: Ligature Substitution Subtable : n to 1
  1366. ////////////////////////////////////////////////////////////////////////////////
  1367. $Coverage = $subtable_offset + $this->read_ushort();
  1368. $NextGlyphPos = $LuCoverage[$nextGID];
  1369. $LigSetCount = $this->read_short();
  1370. $this->skip($NextGlyphPos * 2);
  1371. $LigSet = $subtable_offset + $this->read_short();
  1372. $this->seek($LigSet);
  1373. $LigCount = $this->read_short();
  1374. // LigatureSet i.e. all starting with the same Glyph $nextGlyph [Consonant]
  1375. $LigatureOffset = array();
  1376. for ($g = 0; $g < $LigCount; $g++) {
  1377. $LigatureOffset[$g] = $LigSet + $this->read_ushort();
  1378. }
  1379. for ($g = 0; $g < $LigCount; $g++) {
  1380. // Ligature tables
  1381. $this->seek($LigatureOffset[$g]);
  1382. $LigGlyph = $this->read_ushort();
  1383. $substitute = $this->glyphToChar($LigGlyph);
  1384. $CompCount = $this->read_ushort();
  1385. if ($CompCount != 2) {
  1386. return 0;
  1387. } // Only expecting to work with 2:1 (and no ignore characters in between)
  1388. $gid = $this->read_ushort();
  1389. $checkGlyph = $this->glyphToChar($gid); // Other component/input Glyphs starting at position 2 (arrayindex 1)
  1390. if ($currGID == $checkGlyph) {
  1391. $match = true;
  1392. } else {
  1393. $match = false;
  1394. break;
  1395. }
  1396. $GlyphPos = array();
  1397. $GlyphPos[] = $ptr;
  1398. $GlyphPos[] = $ptr + 1;
  1399. if ($match) {
  1400. $shift = $this->GSUBsubstitute($ptr, $substitute, 4, $GlyphPos); // GlyphPos contains positions to set null
  1401. if ($shift)
  1402. return 1;
  1403. }
  1404. }
  1405. return 0;
  1406. }
  1407. function _applyGSUBsubtable($lookupID, $subtable, $ptr, $currGlyph, $currGID, $subtable_offset, $Type, $Flag, $MarkFilteringSet, $LuCoverage, $level = 0, $currentTag, $is_old_spec, $tagInt)
  1408. {
  1409. $ignore = $this->_getGCOMignoreString($Flag, $MarkFilteringSet);
  1410. // Lets start
  1411. $this->seek($subtable_offset);
  1412. $SubstFormat = $this->read_ushort();
  1413. ////////////////////////////////////////////////////////////////////////////////
  1414. // LookupType 1: Single Substitution Subtable : 1 to 1
  1415. ////////////////////////////////////////////////////////////////////////////////
  1416. if ($Type == 1) {
  1417. // Flag = Ignore
  1418. if ($this->_checkGCOMignore($Flag, $currGlyph, $MarkFilteringSet)) {
  1419. return 0;
  1420. }
  1421. $CoverageOffset = $subtable_offset + $this->read_ushort();
  1422. $GlyphPos = $LuCoverage[$currGID];
  1423. //===========
  1424. // Format 1:
  1425. //===========
  1426. if ($SubstFormat == 1) { // Calculated output glyph indices
  1427. $DeltaGlyphID = $this->read_short();
  1428. $this->seek($CoverageOffset);
  1429. $glyphs = $this->_getCoverageGID();
  1430. $GlyphID = $glyphs[$GlyphPos] + $DeltaGlyphID;
  1431. }
  1432. //===========
  1433. // Format 2:
  1434. //===========
  1435. else if ($SubstFormat == 2) { // Specified output glyph indices
  1436. $GlyphCount = $this->read_ushort();
  1437. $this->skip($GlyphPos * 2);
  1438. $GlyphID = $this->read_ushort();
  1439. }
  1440. $substitute = $this->glyphToChar($GlyphID);
  1441. $shift = $this->GSUBsubstitute($ptr, $substitute, $Type);
  1442. if ($this->debugOTL && $shift) {
  1443. $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level);
  1444. }
  1445. if ($shift)
  1446. return 1;
  1447. return 0;
  1448. }
  1449. ////////////////////////////////////////////////////////////////////////////////
  1450. // LookupType 2: Multiple Substitution Subtable : 1 to n
  1451. ////////////////////////////////////////////////////////////////////////////////
  1452. else if ($Type == 2) {
  1453. // Flag = Ignore
  1454. if ($this->_checkGCOMignore($Flag, $currGlyph, $MarkFilteringSet)) {
  1455. return 0;
  1456. }
  1457. $Coverage = $subtable_offset + $this->read_ushort();
  1458. $GlyphPos = $LuCoverage[$currGID];
  1459. $this->skip(2);
  1460. $this->skip($GlyphPos * 2);
  1461. $Sequences = $subtable_offset + $this->read_short();
  1462. $this->seek($Sequences);
  1463. $GlyphCount = $this->read_short();
  1464. $SubstituteGlyphs = array();
  1465. for ($g = 0; $g < $GlyphCount; $g++) {
  1466. $sgid = $this->read_ushort();
  1467. $SubstituteGlyphs[] = $this->glyphToChar($sgid);
  1468. }
  1469. $shift = $this->GSUBsubstitute($ptr, $SubstituteGlyphs, $Type);
  1470. if ($this->debugOTL && $shift) {
  1471. $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level);
  1472. }
  1473. if ($shift)
  1474. return $shift;
  1475. return 0;
  1476. }
  1477. ////////////////////////////////////////////////////////////////////////////////
  1478. // LookupType 3: Alternate Forms : 1 to 1(n)
  1479. ////////////////////////////////////////////////////////////////////////////////
  1480. else if ($Type == 3) {
  1481. // Flag = Ignore
  1482. if ($this->_checkGCOMignore($Flag, $currGlyph, $MarkFilteringSet)) {
  1483. return 0;
  1484. }
  1485. $Coverage = $subtable_offset + $this->read_ushort();
  1486. $AlternateSetCount = $this->read_short();
  1487. ///////////////////////////////////////////////////////////////////////////////!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  1488. // Need to set alternate IF set by CSS3 font-feature for a tag
  1489. // i.e. if this is 'salt' alternate may be set to 2
  1490. // default value will be $alt=1 ( === index of 0 in list of alternates)
  1491. $alt = 1; // $alt=1 points to Alternative[0]
  1492. if ($tagInt > 1) {
  1493. $alt = $tagInt;
  1494. }
  1495. ///////////////////////////////////////////////////////////////////////////////!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  1496. if ($alt == 0) {
  1497. return 0;
  1498. } // If specified alternate not present, cancel [ or could default $alt = 1 ?]
  1499. $GlyphPos = $LuCoverage[$currGID];
  1500. $this->skip($GlyphPos * 2);
  1501. $AlternateSets = $subtable_offset + $this->read_short();
  1502. $this->seek($AlternateSets);
  1503. $AlternateGlyphCount = $this->read_short();
  1504. if ($alt > $AlternateGlyphCount) {
  1505. return 0;
  1506. } // If specified alternate not present, cancel [ or could default $alt = 1 ?]
  1507. $this->skip(($alt - 1) * 2);
  1508. $GlyphID = $this->read_ushort();
  1509. $substitute = $this->glyphToChar($GlyphID);
  1510. $shift = $this->GSUBsubstitute($ptr, $substitute, $Type);
  1511. if ($this->debugOTL && $shift) {
  1512. $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level);
  1513. }
  1514. if ($shift)
  1515. return 1;
  1516. return 0;
  1517. }
  1518. ////////////////////////////////////////////////////////////////////////////////
  1519. // LookupType 4: Ligature Substitution Subtable : n to 1
  1520. ////////////////////////////////////////////////////////////////////////////////
  1521. else if ($Type == 4) {
  1522. // Flag = Ignore
  1523. if ($this->_checkGCOMignore($Flag, $currGlyph, $MarkFilteringSet)) {
  1524. return 0;
  1525. }
  1526. $Coverage = $subtable_offset + $this->read_ushort();
  1527. $FirstGlyphPos = $LuCoverage[$currGID];
  1528. $LigSetCount = $this->read_short();
  1529. $this->skip($FirstGlyphPos * 2);
  1530. $LigSet = $subtable_offset + $this->read_short();
  1531. $this->seek($LigSet);
  1532. $LigCount = $this->read_short();
  1533. // LigatureSet i.e. all starting with the same first Glyph $currGlyph
  1534. $LigatureOffset = array();
  1535. for ($g = 0; $g < $LigCount; $g++) {
  1536. $LigatureOffset[$g] = $LigSet + $this->read_ushort();
  1537. }
  1538. for ($g = 0; $g < $LigCount; $g++) {
  1539. // Ligature tables
  1540. $this->seek($LigatureOffset[$g]);
  1541. $LigGlyph = $this->read_ushort(); // Output Ligature GlyphID
  1542. $substitute = $this->glyphToChar($LigGlyph);
  1543. $CompCount = $this->read_ushort();
  1544. $spos = $ptr;
  1545. $match = true;
  1546. $GlyphPos = array();
  1547. $GlyphPos[] = $spos;
  1548. for ($l = 1; $l < $CompCount; $l++) {
  1549. $gid = $this->read_ushort();
  1550. $checkGlyph = $this->glyphToChar($gid); // Other component/input Glyphs starting at position 2 (arrayindex 1)
  1551. $spos++;
  1552. //while $this->OTLdata[$spos]['uni'] is an "ignore" => spos++
  1553. while (isset($this->OTLdata[$spos]) && strpos($ignore, $this->OTLdata[$spos]['hex']) !== false) {
  1554. $spos++;
  1555. }
  1556. if (isset($this->OTLdata[$spos]) && $this->OTLdata[$spos]['uni'] == $checkGlyph) {
  1557. $GlyphPos[] = $spos;
  1558. } else {
  1559. $match = false;
  1560. break;
  1561. }
  1562. }
  1563. if ($match) {
  1564. $shift = $this->GSUBsubstitute($ptr, $substitute, $Type, $GlyphPos); // GlyphPos contains positions to set null
  1565. if ($this->debugOTL && $shift) {
  1566. $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level);
  1567. }
  1568. if ($shift)
  1569. return ($spos - $ptr + 1 - ($CompCount - 1));
  1570. }
  1571. }
  1572. return 0;
  1573. }
  1574. ////////////////////////////////////////////////////////////////////////////////
  1575. // LookupType 5: Contextual Substitution Subtable
  1576. ////////////////////////////////////////////////////////////////////////////////
  1577. else if ($Type == 5) {
  1578. //===========
  1579. // Format 1: Simple Context Glyph Substitution
  1580. //===========
  1581. if ($SubstFormat == 1) {
  1582. $CoverageTableOffset = $subtable_offset + $this->read_ushort();
  1583. $SubRuleSetCount = $this->read_ushort();
  1584. $SubRuleSetOffset = array();
  1585. for ($b = 0; $b < $SubRuleSetCount; $b++) {
  1586. $offset = $this->read_ushort();
  1587. if ($offset == 0x0000) {
  1588. $SubRuleSetOffset[] = $offset;
  1589. } else {
  1590. $SubRuleSetOffset[] = $subtable_offset + $offset;
  1591. }
  1592. }
  1593. // SubRuleSet tables: All contexts beginning with the same glyph
  1594. // Select the SubRuleSet required using the position of the glyph in the coverage table
  1595. $GlyphPos = $LuCoverage[$currGID];
  1596. if ($SubRuleSetOffset[$GlyphPos] > 0) {
  1597. $this->seek($SubRuleSetOffset[$GlyphPos]);
  1598. $SubRuleCnt = $this->read_ushort();
  1599. $SubRule = array();
  1600. for ($b = 0; $b < $SubRuleCnt; $b++) {
  1601. $SubRule[$b] = $SubRuleSetOffset[$GlyphPos] + $this->read_ushort();
  1602. }
  1603. for ($b = 0; $b < $SubRuleCnt; $b++) { // EACH RULE
  1604. $this->seek($SubRule[$b]);
  1605. $InputGlyphCount = $this->read_ushort();
  1606. $SubstCount = $this->read_ushort();
  1607. $Backtrack = array();
  1608. $Lookahead = array();
  1609. $Input = array();
  1610. $Input[0] = $this->OTLdata[$ptr]['uni'];
  1611. for ($r = 1; $r < $InputGlyphCount; $r++) {
  1612. $gid = $this->read_ushort();
  1613. $Input[$r] = $this->glyphToChar($gid);
  1614. }
  1615. $matched = $this->checkContextMatch($Input, $Backtrack, $Lookahead, $ignore, $ptr);
  1616. if ($matched) {
  1617. if ($this->debugOTL) {
  1618. $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level);
  1619. }
  1620. for ($p = 0; $p < $SubstCount; $p++) { // EACH LOOKUP
  1621. $SequenceIndex[$p] = $this->read_ushort();
  1622. $LookupListIndex[$p] = $this->read_ushort();
  1623. }
  1624. for ($p = 0; $p < $SubstCount; $p++) {
  1625. // Apply $LookupListIndex at $SequenceIndex
  1626. if ($SequenceIndex[$p] >= $InputGlyphCount) {
  1627. continue;
  1628. }
  1629. $lu = $LookupListIndex[$p];
  1630. $luType = $this->GSUBLookups[$lu]['Type'];
  1631. $luFlag = $this->GSUBLookups[$lu]['Flag'];
  1632. $luMarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet'];
  1633. $luptr = $matched[$SequenceIndex[$p]];
  1634. $lucurrGlyph = $this->OTLdata[$luptr]['hex'];
  1635. $lucurrGID = $this->OTLdata[$luptr]['uni'];
  1636. foreach ($this->GSUBLookups[$lu]['Subtables'] AS $luc => $lusubtable_offset) {
  1637. $shift = $this->_applyGSUBsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GSUB_offset), $luType, $luFlag, $luMarkFilteringSet, $this->GSLuCoverage[$lu][$luc], 1, $currentTag, $is_old_spec, $tagInt);
  1638. if ($shift) {
  1639. break;
  1640. }
  1641. }
  1642. }
  1643. if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) {
  1644. return $shift;
  1645. } /* OTL_FIX_3 */
  1646. else
  1647. return $InputGlyphCount; // should be + matched ignores in Input Sequence
  1648. }
  1649. }
  1650. }
  1651. return 0;
  1652. }
  1653. //===========
  1654. // Format 2:
  1655. //===========
  1656. // Format 2: Class-based Context Glyph Substitution
  1657. else if ($SubstFormat == 2) {
  1658. $CoverageTableOffset = $subtable_offset + $this->read_ushort();
  1659. $InputClassDefOffset = $subtable_offset + $this->read_ushort();
  1660. $SubClassSetCnt = $this->read_ushort();
  1661. $SubClassSetOffset = array();
  1662. for ($b = 0; $b < $SubClassSetCnt; $b++) {
  1663. $offset = $this->read_ushort();
  1664. if ($offset == 0x0000) {
  1665. $SubClassSetOffset[] = $offset;
  1666. } else {
  1667. $SubClassSetOffset[] = $subtable_offset + $offset;
  1668. }
  1669. }
  1670. $InputClasses = $this->_getClasses($InputClassDefOffset);
  1671. for ($s = 0; $s < $SubClassSetCnt; $s++) { // $SubClassSet is ordered by input class-may be NULL
  1672. // Select $SubClassSet if currGlyph is in First Input Class
  1673. if ($SubClassSetOffset[$s] > 0 && isset($InputClasses[$s][$currGID])) {
  1674. $this->seek($SubClassSetOffset[$s]);
  1675. $SubClassRuleCnt = $this->read_ushort();
  1676. $SubClassRule = array();
  1677. for ($b = 0; $b < $SubClassRuleCnt; $b++) {
  1678. $SubClassRule[$b] = $SubClassSetOffset[$s] + $this->read_ushort();
  1679. }
  1680. for ($b = 0; $b < $SubClassRuleCnt; $b++) { // EACH RULE
  1681. $this->seek($SubClassRule[$b]);
  1682. $InputGlyphCount = $this->read_ushort();
  1683. $SubstCount = $this->read_ushort();
  1684. $Input = array();
  1685. for ($r = 1; $r < $InputGlyphCount; $r++) {
  1686. $Input[$r] = $this->read_ushort();
  1687. }
  1688. $inputClass = $s;
  1689. $inputGlyphs = array();
  1690. $inputGlyphs[0] = $InputClasses[$inputClass];
  1691. if ($InputGlyphCount > 1) {
  1692. // NB starts at 1
  1693. for ($gcl = 1; $gcl < $InputGlyphCount; $gcl++) {
  1694. $classindex = $Input[$gcl];
  1695. if (isset($InputClasses[$classindex])) {
  1696. $inputGlyphs[$gcl] = $InputClasses[$classindex];
  1697. } else {
  1698. $inputGlyphs[$gcl] = '';
  1699. }
  1700. }
  1701. }
  1702. // Class 0 contains all the glyphs NOT in the other classes
  1703. $class0excl = array();
  1704. for ($gc = 1; $gc <= count($InputClasses); $gc++) {
  1705. if (is_array($InputClasses[$gc]))
  1706. $class0excl = $class0excl + $InputClasses[$gc];
  1707. }
  1708. $backtrackGlyphs = array();
  1709. $lookaheadGlyphs = array();
  1710. $matched = $this->checkContextMatchMultipleUni($inputGlyphs, $backtrackGlyphs, $lookaheadGlyphs, $ignore, $ptr, $class0excl);
  1711. if ($matched) {
  1712. if ($this->debugOTL) {
  1713. $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level);
  1714. }
  1715. for ($p = 0; $p < $SubstCount; $p++) { // EACH LOOKUP
  1716. $SequenceIndex[$p] = $this->read_ushort();
  1717. $LookupListIndex[$p] = $this->read_ushort();
  1718. }
  1719. for ($p = 0; $p < $SubstCount; $p++) {
  1720. // Apply $LookupListIndex at $SequenceIndex
  1721. if ($SequenceIndex[$p] >= $InputGlyphCount) {
  1722. continue;
  1723. }
  1724. $lu = $LookupListIndex[$p];
  1725. $luType = $this->GSUBLookups[$lu]['Type'];
  1726. $luFlag = $this->GSUBLookups[$lu]['Flag'];
  1727. $luMarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet'];
  1728. $luptr = $matched[$SequenceIndex[$p]];
  1729. $lucurrGlyph = $this->OTLdata[$luptr]['hex'];
  1730. $lucurrGID = $this->OTLdata[$luptr]['uni'];
  1731. foreach ($this->GSUBLookups[$lu]['Subtables'] AS $luc => $lusubtable_offset) {
  1732. $shift = $this->_applyGSUBsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GSUB_offset), $luType, $luFlag, $luMarkFilteringSet, $this->GSLuCoverage[$lu][$luc], 1, $currentTag, $is_old_spec, $tagInt);
  1733. if ($shift) {
  1734. break;
  1735. }
  1736. }
  1737. }
  1738. if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) {
  1739. return $shift;
  1740. } /* OTL_FIX_3 */
  1741. else
  1742. return $InputGlyphCount; // should be + matched ignores in Input Sequence
  1743. }
  1744. }
  1745. }
  1746. }
  1747. return 0;
  1748. }
  1749. //===========
  1750. // Format 3:
  1751. //===========
  1752. // Format 3: Coverage-based Context Glyph Substitution
  1753. else if ($SubstFormat == 3) {
  1754. throw new MpdfException("GSUB Lookup Type " . $Type . " Format " . $SubstFormat . " not TESTED YET.");
  1755. }
  1756. }
  1757. ////////////////////////////////////////////////////////////////////////////////
  1758. // LookupType 6: Chaining Contextual Substitution Subtable
  1759. ////////////////////////////////////////////////////////////////////////////////
  1760. else if ($Type == 6) {
  1761. //===========
  1762. // Format 1:
  1763. //===========
  1764. // Format 1: Simple Chaining Context Glyph Substitution
  1765. if ($SubstFormat == 1) {
  1766. $Coverage = $subtable_offset + $this->read_ushort();
  1767. $GlyphPos = $LuCoverage[$currGID];
  1768. $ChainSubRuleSetCount = $this->read_ushort();
  1769. // All of the ChainSubRule tables defining contexts that begin with the same first glyph are grouped together and defined in a ChainSubRuleSet table
  1770. $this->skip($GlyphPos * 2);
  1771. $ChainSubRuleSet = $subtable_offset + $this->read_ushort();
  1772. $this->seek($ChainSubRuleSet);
  1773. $ChainSubRuleCount = $this->read_ushort();
  1774. for ($s = 0; $s < $ChainSubRuleCount; $s++) {
  1775. $ChainSubRule[$s] = $ChainSubRuleSet + $this->read_ushort();
  1776. }
  1777. for ($s = 0; $s < $ChainSubRuleCount; $s++) {
  1778. $this->seek($ChainSubRule[$s]);
  1779. $BacktrackGlyphCount = $this->read_ushort();
  1780. $Backtrack = array();
  1781. for ($b = 0; $b < $BacktrackGlyphCount; $b++) {
  1782. $gid = $this->read_ushort();
  1783. $Backtrack[] = $this->glyphToChar($gid);
  1784. }
  1785. $Input = array();
  1786. $Input[0] = $this->OTLdata[$ptr]['uni'];
  1787. $InputGlyphCount = $this->read_ushort();
  1788. for ($b = 1; $b < $InputGlyphCount; $b++) {
  1789. $gid = $this->read_ushort();
  1790. $Input[$b] = $this->glyphToChar($gid);
  1791. }
  1792. $LookaheadGlyphCount = $this->read_ushort();
  1793. $Lookahead = array();
  1794. for ($b = 0; $b < $LookaheadGlyphCount; $b++) {
  1795. $gid = $this->read_ushort();
  1796. $Lookahead[] = $this->glyphToChar($gid);
  1797. }
  1798. $matched = $this->checkContextMatch($Input, $Backtrack, $Lookahead, $ignore, $ptr);
  1799. if ($matched) {
  1800. if ($this->debugOTL) {
  1801. $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level);
  1802. }
  1803. $SubstCount = $this->read_ushort();
  1804. for ($p = 0; $p < $SubstCount; $p++) {
  1805. // SubstLookupRecord
  1806. $SubstLookupRecord[$p]['SequenceIndex'] = $this->read_ushort();
  1807. $SubstLookupRecord[$p]['LookupListIndex'] = $this->read_ushort();
  1808. }
  1809. for ($p = 0; $p < $SubstCount; $p++) {
  1810. // Apply $SubstLookupRecord[$p]['LookupListIndex'] at $SubstLookupRecord[$p]['SequenceIndex']
  1811. if ($SubstLookupRecord[$p]['SequenceIndex'] >= $InputGlyphCount) {
  1812. continue;
  1813. }
  1814. $lu = $SubstLookupRecord[$p]['LookupListIndex'];
  1815. $luType = $this->GSUBLookups[$lu]['Type'];
  1816. $luFlag = $this->GSUBLookups[$lu]['Flag'];
  1817. $luMarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet'];
  1818. $luptr = $matched[$SubstLookupRecord[$p]['SequenceIndex']];
  1819. $lucurrGlyph = $this->OTLdata[$luptr]['hex'];
  1820. $lucurrGID = $this->OTLdata[$luptr]['uni'];
  1821. foreach ($this->GSUBLookups[$lu]['Subtables'] AS $luc => $lusubtable_offset) {
  1822. $shift = $this->_applyGSUBsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GSUB_offset), $luType, $luFlag, $luMarkFilteringSet, $this->GSLuCoverage[$lu][$luc], 1, $currentTag, $is_old_spec, $tagInt);
  1823. if ($shift) {
  1824. break;
  1825. }
  1826. }
  1827. }
  1828. if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) {
  1829. return $shift;
  1830. } /* OTL_FIX_3 */
  1831. else
  1832. return $InputGlyphCount; // should be + matched ignores in Input Sequence
  1833. }
  1834. }
  1835. return 0;
  1836. }
  1837. //===========
  1838. // Format 2:
  1839. //===========
  1840. // Format 2: Class-based Chaining Context Glyph Substitution p257
  1841. else if ($SubstFormat == 2) {
  1842. // NB Format 2 specifies fixed class assignments (identical for each position in the backtrack, input, or lookahead sequence) and exclusive classes (a glyph cannot be in more than one class at a time)
  1843. $CoverageTableOffset = $subtable_offset + $this->read_ushort();
  1844. $BacktrackClassDefOffset = $subtable_offset + $this->read_ushort();
  1845. $InputClassDefOffset = $subtable_offset + $this->read_ushort();
  1846. $LookaheadClassDefOffset = $subtable_offset + $this->read_ushort();
  1847. $ChainSubClassSetCnt = $this->read_ushort();
  1848. $ChainSubClassSetOffset = array();
  1849. for ($b = 0; $b < $ChainSubClassSetCnt; $b++) {
  1850. $offset = $this->read_ushort();
  1851. if ($offset == 0x0000) {
  1852. $ChainSubClassSetOffset[] = $offset;
  1853. } else {
  1854. $ChainSubClassSetOffset[] = $subtable_offset + $offset;
  1855. }
  1856. }
  1857. $BacktrackClasses = $this->_getClasses($BacktrackClassDefOffset);
  1858. $InputClasses = $this->_getClasses($InputClassDefOffset);
  1859. $LookaheadClasses = $this->_getClasses($LookaheadClassDefOffset);
  1860. for ($s = 0; $s < $ChainSubClassSetCnt; $s++) { // $ChainSubClassSet is ordered by input class-may be NULL
  1861. // Select $ChainSubClassSet if currGlyph is in First Input Class
  1862. if ($ChainSubClassSetOffset[$s] > 0 && isset($InputClasses[$s][$currGID])) {
  1863. $this->seek($ChainSubClassSetOffset[$s]);
  1864. $ChainSubClassRuleCnt = $this->read_ushort();
  1865. $ChainSubClassRule = array();
  1866. for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) {
  1867. $ChainSubClassRule[$b] = $ChainSubClassSetOffset[$s] + $this->read_ushort();
  1868. }
  1869. for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) { // EACH RULE
  1870. $this->seek($ChainSubClassRule[$b]);
  1871. $BacktrackGlyphCount = $this->read_ushort();
  1872. for ($r = 0; $r < $BacktrackGlyphCount; $r++) {
  1873. $Backtrack[$r] = $this->read_ushort();
  1874. }
  1875. $InputGlyphCount = $this->read_ushort();
  1876. for ($r = 1; $r < $InputGlyphCount; $r++) {
  1877. $Input[$r] = $this->read_ushort();
  1878. }
  1879. $LookaheadGlyphCount = $this->read_ushort();
  1880. for ($r = 0; $r < $LookaheadGlyphCount; $r++) {
  1881. $Lookahead[$r] = $this->read_ushort();
  1882. }
  1883. // These contain classes of glyphs as arrays
  1884. // $InputClasses[(class)] e.g. 0x02E6,0x02E7,0x02E8
  1885. // $LookaheadClasses[(class)]
  1886. // $BacktrackClasses[(class)]
  1887. // These contain arrays of classIndexes
  1888. // [Backtrack] [Lookahead] and [Input] (Input is from the second position only)
  1889. $inputClass = $s; //???
  1890. $inputGlyphs = array();
  1891. $inputGlyphs[0] = $InputClasses[$inputClass];
  1892. if ($InputGlyphCount > 1) {
  1893. // NB starts at 1
  1894. for ($gcl = 1; $gcl < $InputGlyphCount; $gcl++) {
  1895. $classindex = $Input[$gcl];
  1896. if (isset($InputClasses[$classindex])) {
  1897. $inputGlyphs[$gcl] = $InputClasses[$classindex];
  1898. } else {
  1899. $inputGlyphs[$gcl] = '';
  1900. }
  1901. }
  1902. }
  1903. // Class 0 contains all the glyphs NOT in the other classes
  1904. $class0excl = array();
  1905. for ($gc = 1; $gc <= count($InputClasses); $gc++) {
  1906. if (isset($InputClasses[$gc]))
  1907. $class0excl = $class0excl + $InputClasses[$gc];
  1908. }
  1909. if ($BacktrackGlyphCount) {
  1910. for ($gcl = 0; $gcl < $BacktrackGlyphCount; $gcl++) {
  1911. $classindex = $Backtrack[$gcl];
  1912. if (isset($BacktrackClasses[$classindex])) {
  1913. $backtrackGlyphs[$gcl] = $BacktrackClasses[$classindex];
  1914. } else {
  1915. $backtrackGlyphs[$gcl] = '';
  1916. }
  1917. }
  1918. } else {
  1919. $backtrackGlyphs = array();
  1920. }
  1921. // Class 0 contains all the glyphs NOT in the other classes
  1922. $bclass0excl = array();
  1923. for ($gc = 1; $gc <= count($BacktrackClasses); $gc++) {
  1924. if (isset($BacktrackClasses[$gc]))
  1925. $bclass0excl = $bclass0excl + $BacktrackClasses[$gc];
  1926. }
  1927. if ($LookaheadGlyphCount) {
  1928. for ($gcl = 0; $gcl < $LookaheadGlyphCount; $gcl++) {
  1929. $classindex = $Lookahead[$gcl];
  1930. if (isset($LookaheadClasses[$classindex])) {
  1931. $lookaheadGlyphs[$gcl] = $LookaheadClasses[$classindex];
  1932. } else {
  1933. $lookaheadGlyphs[$gcl] = '';
  1934. }
  1935. }
  1936. } else {
  1937. $lookaheadGlyphs = array();
  1938. }
  1939. // Class 0 contains all the glyphs NOT in the other classes
  1940. $lclass0excl = array();
  1941. for ($gc = 1; $gc <= count($LookaheadClasses); $gc++) {
  1942. if (isset($LookaheadClasses[$gc]))
  1943. $lclass0excl = $lclass0excl + $LookaheadClasses[$gc];
  1944. }
  1945. $matched = $this->checkContextMatchMultipleUni($inputGlyphs, $backtrackGlyphs, $lookaheadGlyphs, $ignore, $ptr, $class0excl, $bclass0excl, $lclass0excl);
  1946. if ($matched) {
  1947. if ($this->debugOTL) {
  1948. $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level);
  1949. }
  1950. $SubstCount = $this->read_ushort();
  1951. for ($p = 0; $p < $SubstCount; $p++) { // EACH LOOKUP
  1952. $SequenceIndex[$p] = $this->read_ushort();
  1953. $LookupListIndex[$p] = $this->read_ushort();
  1954. }
  1955. for ($p = 0; $p < $SubstCount; $p++) {
  1956. // Apply $LookupListIndex at $SequenceIndex
  1957. if ($SequenceIndex[$p] >= $InputGlyphCount) {
  1958. continue;
  1959. }
  1960. $lu = $LookupListIndex[$p];
  1961. $luType = $this->GSUBLookups[$lu]['Type'];
  1962. $luFlag = $this->GSUBLookups[$lu]['Flag'];
  1963. $luMarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet'];
  1964. $luptr = $matched[$SequenceIndex[$p]];
  1965. $lucurrGlyph = $this->OTLdata[$luptr]['hex'];
  1966. $lucurrGID = $this->OTLdata[$luptr]['uni'];
  1967. foreach ($this->GSUBLookups[$lu]['Subtables'] AS $luc => $lusubtable_offset) {
  1968. $shift = $this->_applyGSUBsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GSUB_offset), $luType, $luFlag, $luMarkFilteringSet, $this->GSLuCoverage[$lu][$luc], 1, $currentTag, $is_old_spec, $tagInt);
  1969. if ($shift) {
  1970. break;
  1971. }
  1972. }
  1973. }
  1974. if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) {
  1975. return $shift;
  1976. } /* OTL_FIX_3 */
  1977. else
  1978. return $InputGlyphCount; // should be + matched ignores in Input Sequence
  1979. }
  1980. }
  1981. }
  1982. }
  1983. return 0;
  1984. }
  1985. //===========
  1986. // Format 3:
  1987. //===========
  1988. // Format 3: Coverage-based Chaining Context Glyph Substitution p259
  1989. else if ($SubstFormat == 3) {
  1990. $BacktrackGlyphCount = $this->read_ushort();
  1991. for ($b = 0; $b < $BacktrackGlyphCount; $b++) {
  1992. $CoverageBacktrackOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order
  1993. }
  1994. $InputGlyphCount = $this->read_ushort();
  1995. for ($b = 0; $b < $InputGlyphCount; $b++) {
  1996. $CoverageInputOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order
  1997. }
  1998. $LookaheadGlyphCount = $this->read_ushort();
  1999. for ($b = 0; $b < $LookaheadGlyphCount; $b++) {
  2000. $CoverageLookaheadOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order
  2001. }
  2002. $SubstCount = $this->read_ushort();
  2003. $save_pos = $this->_pos; // Save the point just after PosCount
  2004. $CoverageBacktrackGlyphs = array();
  2005. for ($b = 0; $b < $BacktrackGlyphCount; $b++) {
  2006. $this->seek($CoverageBacktrackOffset[$b]);
  2007. $glyphs = $this->_getCoverage();
  2008. $CoverageBacktrackGlyphs[$b] = implode("|", $glyphs);
  2009. }
  2010. $CoverageInputGlyphs = array();
  2011. for ($b = 0; $b < $InputGlyphCount; $b++) {
  2012. $this->seek($CoverageInputOffset[$b]);
  2013. $glyphs = $this->_getCoverage();
  2014. $CoverageInputGlyphs[$b] = implode("|", $glyphs);
  2015. }
  2016. $CoverageLookaheadGlyphs = array();
  2017. for ($b = 0; $b < $LookaheadGlyphCount; $b++) {
  2018. $this->seek($CoverageLookaheadOffset[$b]);
  2019. $glyphs = $this->_getCoverage();
  2020. $CoverageLookaheadGlyphs[$b] = implode("|", $glyphs);
  2021. }
  2022. $matched = $this->checkContextMatchMultiple($CoverageInputGlyphs, $CoverageBacktrackGlyphs, $CoverageLookaheadGlyphs, $ignore, $ptr);
  2023. if ($matched) {
  2024. if ($this->debugOTL) {
  2025. $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level);
  2026. }
  2027. $this->seek($save_pos); // Return to just after PosCount
  2028. for ($p = 0; $p < $SubstCount; $p++) {
  2029. // SubstLookupRecord
  2030. $SubstLookupRecord[$p]['SequenceIndex'] = $this->read_ushort();
  2031. $SubstLookupRecord[$p]['LookupListIndex'] = $this->read_ushort();
  2032. }
  2033. for ($p = 0; $p < $SubstCount; $p++) {
  2034. // Apply $SubstLookupRecord[$p]['LookupListIndex'] at $SubstLookupRecord[$p]['SequenceIndex']
  2035. if ($SubstLookupRecord[$p]['SequenceIndex'] >= $InputGlyphCount) {
  2036. continue;
  2037. }
  2038. $lu = $SubstLookupRecord[$p]['LookupListIndex'];
  2039. $luType = $this->GSUBLookups[$lu]['Type'];
  2040. $luFlag = $this->GSUBLookups[$lu]['Flag'];
  2041. $luMarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet'];
  2042. $luptr = $matched[$SubstLookupRecord[$p]['SequenceIndex']];
  2043. $lucurrGlyph = $this->OTLdata[$luptr]['hex'];
  2044. $lucurrGID = $this->OTLdata[$luptr]['uni'];
  2045. foreach ($this->GSUBLookups[$lu]['Subtables'] AS $luc => $lusubtable_offset) {
  2046. $shift = $this->_applyGSUBsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GSUB_offset), $luType, $luFlag, $luMarkFilteringSet, $this->GSLuCoverage[$lu][$luc], 1, $currentTag, $is_old_spec, $tagInt);
  2047. if ($shift) {
  2048. break;
  2049. }
  2050. }
  2051. }
  2052. if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) {
  2053. return (isset($shift) ? $shift : 0);
  2054. } /* OTL_FIX_3 */
  2055. else
  2056. return $InputGlyphCount; // should be + matched ignores in Input Sequence
  2057. }
  2058. return 0;
  2059. }
  2060. }
  2061. else {
  2062. throw new MpdfException("GSUB Lookup Type " . $Type . " not supported.");
  2063. }
  2064. }
  2065. function _updateLigatureMarks($pos, $n)
  2066. {
  2067. if ($n > 0) {
  2068. // Update position of Ligatures and associated Marks
  2069. // Foreach lig/assocMarks
  2070. // Any position lpos or mpos > $pos + count($substitute)
  2071. // $this->assocMarks = array(); // assocMarks[$pos mpos] => array(compID, ligPos)
  2072. // $this->assocLigs = array(); // Ligatures[$pos lpos] => nc
  2073. for ($p = count($this->OTLdata) - 1; $p >= ($pos + $n); $p--) {
  2074. if (isset($this->assocLigs[$p])) {
  2075. $tmp = $this->assocLigs[$p];
  2076. unset($this->assocLigs[$p]);
  2077. $this->assocLigs[($p + $n)] = $tmp;
  2078. }
  2079. }
  2080. for ($p = count($this->OTLdata) - 1; $p >= 0; $p--) {
  2081. if (isset($this->assocMarks[$p])) {
  2082. if ($this->assocMarks[$p]['ligPos'] >= ($pos + $n)) {
  2083. $this->assocMarks[$p]['ligPos'] += $n;
  2084. }
  2085. if ($p >= ($pos + $n)) {
  2086. $tmp = $this->assocMarks[$p];
  2087. unset($this->assocMarks[$p]);
  2088. $this->assocMarks[($p + $n)] = $tmp;
  2089. }
  2090. }
  2091. }
  2092. } else if ($n < 1) { // glyphs removed
  2093. $nrem = -$n;
  2094. // Update position of pre-existing Ligatures and associated Marks
  2095. for ($p = ($pos + 1); $p < count($this->OTLdata); $p++) {
  2096. if (isset($this->assocLigs[$p])) {
  2097. $tmp = $this->assocLigs[$p];
  2098. unset($this->assocLigs[$p]);
  2099. $this->assocLigs[($p - $nrem)] = $tmp;
  2100. }
  2101. }
  2102. for ($p = 0; $p < count($this->OTLdata); $p++) {
  2103. if (isset($this->assocMarks[$p])) {
  2104. if ($this->assocMarks[$p]['ligPos'] >= ($pos)) {
  2105. $this->assocMarks[$p]['ligPos'] -= $nrem;
  2106. }
  2107. if ($p > $pos) {
  2108. $tmp = $this->assocMarks[$p];
  2109. unset($this->assocMarks[$p]);
  2110. $this->assocMarks[($p - $nrem)] = $tmp;
  2111. }
  2112. }
  2113. }
  2114. }
  2115. }
  2116. function GSUBsubstitute($pos, $substitute, $Type, $GlyphPos = NULL)
  2117. {
  2118. // LookupType 1: Simple Substitution Subtable : 1 to 1
  2119. // LookupType 3: Alternate Forms : 1 to 1(n)
  2120. if ($Type == 1 || $Type == 3) {
  2121. $this->OTLdata[$pos]['uni'] = $substitute;
  2122. $this->OTLdata[$pos]['hex'] = $this->unicode_hex($substitute);
  2123. return 1;
  2124. }
  2125. // LookupType 2: Multiple Substitution Subtable : 1 to n
  2126. else if ($Type == 2) {
  2127. for ($i = 0; $i < count($substitute); $i++) {
  2128. $uni = $substitute[$i];
  2129. $newOTLdata[$i] = array();
  2130. $newOTLdata[$i]['uni'] = $uni;
  2131. $newOTLdata[$i]['hex'] = $this->unicode_hex($uni);
  2132. // Get types of new inserted chars - or replicate type of char being replaced
  2133. // $bt = UCDN::get_bidi_class($uni);
  2134. // if (!$bt) {
  2135. $bt = $this->OTLdata[$pos]['bidi_type'];
  2136. // }
  2137. if (strpos($this->GlyphClassMarks, $newOTLdata[$i]['hex']) !== false) {
  2138. $gp = 'M';
  2139. } else if ($uni == 32) {
  2140. $gp = 'S';
  2141. } else {
  2142. $gp = 'C';
  2143. }
  2144. // Need to update matra_type ??? of new glyphs inserted ???????????????????????????????????????
  2145. $newOTLdata[$i]['bidi_type'] = $bt;
  2146. $newOTLdata[$i]['group'] = $gp;
  2147. // Need to update details of new glyphs inserted
  2148. $newOTLdata[$i]['general_category'] = $this->OTLdata[$pos]['general_category'];
  2149. if ($this->shaper == 'I' || $this->shaper == 'K' || $this->shaper == 'S') {
  2150. $newOTLdata[$i]['indic_category'] = $this->OTLdata[$pos]['indic_category'];
  2151. $newOTLdata[$i]['indic_position'] = $this->OTLdata[$pos]['indic_position'];
  2152. } else if ($this->shaper == 'M') {
  2153. $newOTLdata[$i]['myanmar_category'] = $this->OTLdata[$pos]['myanmar_category'];
  2154. $newOTLdata[$i]['myanmar_position'] = $this->OTLdata[$pos]['myanmar_position'];
  2155. }
  2156. if (isset($this->OTLdata[$pos]['mask'])) {
  2157. $newOTLdata[$i]['mask'] = $this->OTLdata[$pos]['mask'];
  2158. }
  2159. if (isset($this->OTLdata[$pos]['syllable'])) {
  2160. $newOTLdata[$i]['syllable'] = $this->OTLdata[$pos]['syllable'];
  2161. }
  2162. }
  2163. if ($this->shaper == 'K' || $this->shaper == 'T' || $this->shaper == 'L') {
  2164. if ($this->OTLdata[$pos]['wordend']) {
  2165. $newOTLdata[count($substitute) - 1]['wordend'] = true;
  2166. }
  2167. }
  2168. array_splice($this->OTLdata, $pos, 1, $newOTLdata); // Replace 1 with n
  2169. // Update position of Ligatures and associated Marks
  2170. // count($substitute)-1 is the number of glyphs added
  2171. $nadd = count($substitute) - 1;
  2172. $this->_updateLigatureMarks($pos, $nadd);
  2173. return count($substitute);
  2174. }
  2175. // LookupType 4: Ligature Substitution Subtable : n to 1
  2176. else if ($Type == 4) {
  2177. // Create Ligatures and associated Marks
  2178. $firstGlyph = $this->OTLdata[$pos]['hex'];
  2179. // If all components of the ligature are marks (and in the same syllable), we call this a mark ligature.
  2180. $contains_marks = false;
  2181. $contains_nonmarks = false;
  2182. if (isset($this->OTLdata[$pos]['syllable'])) {
  2183. $current_syllable = $this->OTLdata[$pos]['syllable'];
  2184. } else {
  2185. $current_syllable = 0;
  2186. }
  2187. for ($i = 0; $i < count($GlyphPos); $i++) {
  2188. // If subsequent components are not Marks as well - don't ligate
  2189. $unistr = $this->OTLdata[$GlyphPos[$i]]['hex'];
  2190. if ($this->restrictToSyllable && isset($this->OTLdata[$GlyphPos[$i]]['syllable']) && $this->OTLdata[$GlyphPos[$i]]['syllable'] != $current_syllable) {
  2191. return 0;
  2192. }
  2193. if (strpos($this->GlyphClassMarks, $unistr) !== false) {
  2194. $contains_marks = true;
  2195. } else {
  2196. $contains_nonmarks = true;
  2197. }
  2198. }
  2199. if ($contains_marks && !$contains_nonmarks) {
  2200. // Mark Ligature (all components are Marks)
  2201. $firstMarkAssoc = '';
  2202. if (isset($this->assocMarks[$pos])) {
  2203. $firstMarkAssoc = $this->assocMarks[$pos];
  2204. }
  2205. // If all components of the ligature are marks, we call this a mark ligature.
  2206. for ($i = 1; $i < count($GlyphPos); $i++) {
  2207. // If subsequent components are not Marks as well - don't ligate
  2208. // $unistr = $this->OTLdata[$GlyphPos[$i]]['hex'];
  2209. // if (strpos($this->GlyphClassMarks, $unistr )===false) { return; }
  2210. $nextMarkAssoc = '';
  2211. if (isset($this->assocMarks[$GlyphPos[$i]])) {
  2212. $nextMarkAssoc = $this->assocMarks[$GlyphPos[$i]];
  2213. }
  2214. // If first component was attached to a previous ligature component,
  2215. // all subsequent components should be attached to the same ligature
  2216. // component, otherwise we shouldn't ligate them.
  2217. // If first component was NOT attached to a previous ligature component,
  2218. // all subsequent components should also NOT be attached to any ligature component,
  2219. if ($firstMarkAssoc != $nextMarkAssoc) {
  2220. // unless they are attached to the first component itself!
  2221. // if (!is_array($nextMarkAssoc) || $nextMarkAssoc['ligPos']!= $pos) { return; }
  2222. // Update/Edit - In test with myanmartext font
  2223. // &#x1004;&#x103a;&#x1039;&#x1000;&#x1039;&#x1000;&#x103b;&#x103c;&#x103d;&#x1031;&#x102d;
  2224. // => Lookup 17 E003 E066B E05A 102D
  2225. // E003 and 102D should form a mark ligature, but 102D is already associated with (non-mark) ligature E05A
  2226. // So instead of disallowing the mark ligature to form, just dissociate...
  2227. if (!is_array($nextMarkAssoc) || $nextMarkAssoc['ligPos'] != $pos) {
  2228. unset($this->assocMarks[$GlyphPos[$i]]);
  2229. }
  2230. }
  2231. }
  2232. /*
  2233. * - If it *is* a mark ligature, we don't allocate a new ligature id, and leave
  2234. * the ligature to keep its old ligature id. This will allow it to attach to
  2235. * a base ligature in GPOS. Eg. if the sequence is: LAM,LAM,SHADDA,FATHA,HEH,
  2236. * and LAM,LAM,HEH form a ligature, they will leave SHADDA and FATHA wit a
  2237. * ligature id and component value of 2. Then if SHADDA,FATHA form a ligature
  2238. * later, we don't want them to lose their ligature id/component, otherwise
  2239. * GPOS will fail to correctly position the mark ligature on top of the
  2240. * LAM,LAM,HEH ligature.
  2241. */
  2242. // So if is_array($firstMarkAssoc) - the new (Mark) ligature should keep this association
  2243. $lastPos = $GlyphPos[(count($GlyphPos) - 1)];
  2244. } else {
  2245. /*
  2246. * - Ligatures cannot be formed across glyphs attached to different components
  2247. * of previous ligatures. Eg. the sequence is LAM,SHADDA,LAM,FATHA,HEH, and
  2248. * LAM,LAM,HEH form a ligature, leaving SHADDA,FATHA next to eachother.
  2249. * However, it would be wrong to ligate that SHADDA,FATHA sequence.
  2250. * There is an exception to this: If a ligature tries ligating with marks that
  2251. * belong to it itself, go ahead, assuming that the font designer knows what
  2252. * they are doing (otherwise it can break Indic stuff when a matra wants to
  2253. * ligate with a conjunct...)
  2254. */
  2255. /*
  2256. * - If a ligature is formed of components that some of which are also ligatures
  2257. * themselves, and those ligature components had marks attached to *their*
  2258. * components, we have to attach the marks to the new ligature component
  2259. * positions! Now *that*'s tricky! And these marks may be following the
  2260. * last component of the whole sequence, so we should loop forward looking
  2261. * for them and update them.
  2262. *
  2263. * Eg. the sequence is LAM,LAM,SHADDA,FATHA,HEH, and the font first forms a
  2264. * 'calt' ligature of LAM,HEH, leaving the SHADDA and FATHA with a ligature
  2265. * id and component == 1. Now, during 'liga', the LAM and the LAM-HEH ligature
  2266. * form a LAM-LAM-HEH ligature. We need to reassign the SHADDA and FATHA to
  2267. * the new ligature with a component value of 2.
  2268. *
  2269. * This in fact happened to a font... See:
  2270. * https://bugzilla.gnome.org/show_bug.cgi?id=437633
  2271. */
  2272. $currComp = 0;
  2273. for ($i = 0; $i < count($GlyphPos); $i++) {
  2274. if ($i > 0 && isset($this->assocLigs[$GlyphPos[$i]])) { // One of the other components is already a ligature
  2275. $nc = $this->assocLigs[$GlyphPos[$i]];
  2276. } else {
  2277. $nc = 1;
  2278. }
  2279. // While next char to right is a mark (but not the next matched glyph)
  2280. // ?? + also include a Mark Ligature here
  2281. $ic = 1;
  2282. while ((($i == count($GlyphPos) - 1) || (isset($GlyphPos[$i + 1]) && ($GlyphPos[$i] + $ic) < $GlyphPos[$i + 1])) && isset($this->OTLdata[($GlyphPos[$i] + $ic)]) && strpos($this->GlyphClassMarks, $this->OTLdata[($GlyphPos[$i] + $ic)]['hex']) !== false) {
  2283. $newComp = $currComp;
  2284. if (isset($this->assocMarks[$GlyphPos[$i] + $ic])) { // One of the inbetween Marks is already associated with a Lig
  2285. // OK as long as it is associated with the current Lig
  2286. // if ($this->assocMarks[($GlyphPos[$i]+$ic)]['ligPos'] != ($GlyphPos[$i]+$ic)) { die("Problem #1"); }
  2287. $newComp += $this->assocMarks[($GlyphPos[$i] + $ic)]['compID'];
  2288. }
  2289. $this->assocMarks[($GlyphPos[$i] + $ic)] = array('compID' => $newComp, 'ligPos' => $pos);
  2290. $ic++;
  2291. }
  2292. $currComp += $nc;
  2293. }
  2294. $lastPos = $GlyphPos[(count($GlyphPos) - 1)] + $ic - 1;
  2295. $this->assocLigs[$pos] = $currComp; // Number of components in new Ligature
  2296. }
  2297. // Now remove the unwanted glyphs and associated metadata
  2298. $newOTLdata[0] = array();
  2299. // Get types of new inserted chars - or replicate type of char being replaced
  2300. // $bt = UCDN::get_bidi_class($substitute);
  2301. // if (!$bt) {
  2302. $bt = $this->OTLdata[$pos]['bidi_type'];
  2303. // }
  2304. if (strpos($this->GlyphClassMarks, $this->unicode_hex($substitute)) !== false) {
  2305. $gp = 'M';
  2306. } else if ($substitute == 32) {
  2307. $gp = 'S';
  2308. } else {
  2309. $gp = 'C';
  2310. }
  2311. // Need to update details of new glyphs inserted
  2312. $newOTLdata[0]['general_category'] = $this->OTLdata[$pos]['general_category'];
  2313. $newOTLdata[0]['bidi_type'] = $bt;
  2314. $newOTLdata[0]['group'] = $gp;
  2315. // KASHIDA: If forming a ligature when the last component was identified as a kashida point (final form)
  2316. // If previous/first component of ligature is a medial form, then keep this as a kashida point
  2317. // TEST (Arabic Typesetting) &#x64a;&#x64e;&#x646;&#x62a;&#x64f;&#x645;
  2318. $ka = 0;
  2319. if (isset($this->OTLdata[$GlyphPos[(count($GlyphPos) - 1)]]['GPOSinfo']['kashida'])) {
  2320. $ka = $this->OTLdata[$GlyphPos[(count($GlyphPos) - 1)]]['GPOSinfo']['kashida'];
  2321. }
  2322. if ($ka == 1 && isset($this->OTLdata[$pos]['form']) && $this->OTLdata[$pos]['form'] == 3) {
  2323. $newOTLdata[0]['GPOSinfo']['kashida'] = $ka;
  2324. }
  2325. $newOTLdata[0]['uni'] = $substitute;
  2326. $newOTLdata[0]['hex'] = $this->unicode_hex($substitute);
  2327. if ($this->shaper == 'I' || $this->shaper == 'K' || $this->shaper == 'S') {
  2328. $newOTLdata[0]['indic_category'] = $this->OTLdata[$pos]['indic_category'];
  2329. $newOTLdata[0]['indic_position'] = $this->OTLdata[$pos]['indic_position'];
  2330. } else if ($this->shaper == 'M') {
  2331. $newOTLdata[0]['myanmar_category'] = $this->OTLdata[$pos]['myanmar_category'];
  2332. $newOTLdata[0]['myanmar_position'] = $this->OTLdata[$pos]['myanmar_position'];
  2333. }
  2334. if (isset($this->OTLdata[$pos]['mask'])) {
  2335. $newOTLdata[0]['mask'] = $this->OTLdata[$pos]['mask'];
  2336. }
  2337. if (isset($this->OTLdata[$pos]['syllable'])) {
  2338. $newOTLdata[0]['syllable'] = $this->OTLdata[$pos]['syllable'];
  2339. }
  2340. $newOTLdata[0]['is_ligature'] = true;
  2341. array_splice($this->OTLdata, $pos, 1, $newOTLdata);
  2342. // GlyphPos contains array of arr_pos to set null - not necessarily contiguous
  2343. // +- Remove any assocMarks or assocLigs from the main components (the ones that are deleted)
  2344. for ($i = count($GlyphPos) - 1; $i > 0; $i--) {
  2345. $gpos = $GlyphPos[$i];
  2346. array_splice($this->OTLdata, $gpos, 1);
  2347. unset($this->assocLigs[$gpos]);
  2348. unset($this->assocMarks[$gpos]);
  2349. }
  2350. // $this->assocLigs = array(); // Ligatures[$posarr lpos] => nc
  2351. // $this->assocMarks = array(); // assocMarks[$posarr mpos] => array(compID, ligPos)
  2352. // Update position of pre-existing Ligatures and associated Marks
  2353. // Start after first GlyphPos
  2354. // count($GlyphPos)-1 is the number of glyphs removed from string
  2355. for ($p = ($GlyphPos[0] + 1); $p < (count($this->OTLdata) + count($GlyphPos) - 1); $p++) {
  2356. $nrem = 0; // Number of Glyphs removed at this point in the string
  2357. for ($i = 0; $i < count($GlyphPos); $i++) {
  2358. if ($i > 0 && $p > $GlyphPos[$i]) {
  2359. $nrem++;
  2360. }
  2361. }
  2362. if (isset($this->assocLigs[$p])) {
  2363. $tmp = $this->assocLigs[$p];
  2364. unset($this->assocLigs[$p]);
  2365. $this->assocLigs[($p - $nrem)] = $tmp;
  2366. }
  2367. if (isset($this->assocMarks[$p])) {
  2368. $tmp = $this->assocMarks[$p];
  2369. unset($this->assocMarks[$p]);
  2370. if ($tmp['ligPos'] > $GlyphPos[0]) {
  2371. $tmp['ligPos'] -= $nrem;
  2372. }
  2373. $this->assocMarks[($p - $nrem)] = $tmp;
  2374. }
  2375. }
  2376. return 1;
  2377. } else {
  2378. return 0;
  2379. }
  2380. }
  2381. ////////////////////////////////////////////////////////////////
  2382. ////////////////////////////////////////////////////////////////
  2383. ////////// ARABIC /////////////////////////////////
  2384. ////////////////////////////////////////////////////////////////
  2385. ////////////////////////////////////////////////////////////////
  2386. function arabic_initialise()
  2387. {
  2388. // cf. http://unicode.org/Public/UNIDATA/ArabicShaping.txt
  2389. // http://unicode.org/Public/UNIDATA/extracted/DerivedJoiningType.txt
  2390. // JOIN TO FOLLOWING LETTER IN LOGICAL ORDER (i.e. AS INITIAL/MEDIAL FORM) = Unicode Left-Joining (+ Dual-Joining + Join_Causing 00640)
  2391. $this->arabLeftJoining = array(
  2392. 0x0620 => 1, 0x0626 => 1, 0x0628 => 1, 0x062A => 1, 0x062B => 1, 0x062C => 1, 0x062D => 1, 0x062E => 1,
  2393. 0x0633 => 1, 0x0634 => 1, 0x0635 => 1, 0x0636 => 1, 0x0637 => 1, 0x0638 => 1, 0x0639 => 1, 0x063A => 1,
  2394. 0x063B => 1, 0x063C => 1, 0x063D => 1, 0x063E => 1, 0x063F => 1, 0x0640 => 1, 0x0641 => 1, 0x0642 => 1,
  2395. 0x0643 => 1, 0x0644 => 1, 0x0645 => 1, 0x0646 => 1, 0x0647 => 1, 0x0649 => 1, 0x064A => 1, 0x066E => 1,
  2396. 0x066F => 1, 0x0678 => 1, 0x0679 => 1, 0x067A => 1, 0x067B => 1, 0x067C => 1, 0x067D => 1, 0x067E => 1,
  2397. 0x067F => 1, 0x0680 => 1, 0x0681 => 1, 0x0682 => 1, 0x0683 => 1, 0x0684 => 1, 0x0685 => 1, 0x0686 => 1,
  2398. 0x0687 => 1, 0x069A => 1, 0x069B => 1, 0x069C => 1, 0x069D => 1, 0x069E => 1, 0x069F => 1, 0x06A0 => 1,
  2399. 0x06A1 => 1, 0x06A2 => 1, 0x06A3 => 1, 0x06A4 => 1, 0x06A5 => 1, 0x06A6 => 1, 0x06A7 => 1, 0x06A8 => 1,
  2400. 0x06A9 => 1, 0x06AA => 1, 0x06AB => 1, 0x06AC => 1, 0x06AD => 1, 0x06AE => 1, 0x06AF => 1, 0x06B0 => 1,
  2401. 0x06B1 => 1, 0x06B2 => 1, 0x06B3 => 1, 0x06B4 => 1, 0x06B5 => 1, 0x06B6 => 1, 0x06B7 => 1, 0x06B8 => 1,
  2402. 0x06B9 => 1, 0x06BA => 1, 0x06BB => 1, 0x06BC => 1, 0x06BD => 1, 0x06BE => 1, 0x06BF => 1, 0x06C1 => 1,
  2403. 0x06C2 => 1, 0x06CC => 1, 0x06CE => 1, 0x06D0 => 1, 0x06D1 => 1, 0x06FA => 1, 0x06FB => 1, 0x06FC => 1,
  2404. 0x06FF => 1,
  2405. /* Arabic Supplement */
  2406. 0x0750 => 1, 0x0751 => 1, 0x0752 => 1, 0x0753 => 1, 0x0754 => 1, 0x0755 => 1, 0x0756 => 1, 0x0757 => 1,
  2407. 0x0758 => 1, 0x075C => 1, 0x075D => 1, 0x075E => 1, 0x075F => 1, 0x0760 => 1, 0x0761 => 1, 0x0762 => 1,
  2408. 0x0763 => 1, 0x0764 => 1, 0x0765 => 1, 0x0766 => 1, 0x0767 => 1, 0x0768 => 1, 0x0769 => 1, 0x076A => 1,
  2409. 0x076D => 1, 0x076E => 1, 0x076F => 1, 0x0770 => 1, 0x0772 => 1, 0x0775 => 1, 0x0776 => 1, 0x0777 => 1,
  2410. 0x077A => 1, 0x077B => 1, 0x077C => 1, 0x077D => 1, 0x077E => 1, 0x077F => 1,
  2411. /* Extended Arabic */
  2412. 0x08A0 => 1, 0x08A2 => 1, 0x08A3 => 1, 0x08A4 => 1, 0x08A5 => 1, 0x08A6 => 1, 0x08A7 => 1, 0x08A8 => 1,
  2413. 0x08A9 => 1,
  2414. /* 'syrc' Syriac */
  2415. 0x0712 => 1, 0x0713 => 1, 0x0714 => 1, 0x071A => 1, 0x071B => 1, 0x071C => 1, 0x071D => 1, 0x071F => 1,
  2416. 0x0720 => 1, 0x0721 => 1, 0x0722 => 1, 0x0723 => 1, 0x0724 => 1, 0x0725 => 1, 0x0726 => 1, 0x0727 => 1,
  2417. 0x0729 => 1, 0x072B => 1, 0x072D => 1, 0x072E => 1, 0x074E => 1, 0x074F => 1,
  2418. /* N'Ko */
  2419. 0x07CA => 1, 0x07CB => 1, 0x07CC => 1, 0x07CD => 1, 0x07CE => 1, 0x07CF => 1, 0x07D0 => 1, 0x07D1 => 1,
  2420. 0x07D2 => 1, 0x07D3 => 1, 0x07D4 => 1, 0x07D5 => 1, 0x07D6 => 1, 0x07D7 => 1, 0x07D8 => 1, 0x07D9 => 1,
  2421. 0x07DA => 1, 0x07DB => 1, 0x07DC => 1, 0x07DD => 1, 0x07DE => 1, 0x07DF => 1, 0x07E0 => 1, 0x07E1 => 1,
  2422. 0x07E2 => 1, 0x07E3 => 1, 0x07E4 => 1, 0x07E5 => 1, 0x07E6 => 1, 0x07E7 => 1, 0x07E8 => 1, 0x07E9 => 1,
  2423. 0x07EA => 1, 0x07FA => 1,
  2424. /* Mandaic */
  2425. 0x0841 => 1, 0x0842 => 1, 0x0843 => 1, 0x0844 => 1, 0x0845 => 1, 0x0847 => 1, 0x0848 => 1, 0x084A => 1,
  2426. 0x084B => 1, 0x084C => 1, 0x084D => 1, 0x084E => 1, 0x0850 => 1, 0x0851 => 1, 0x0852 => 1, 0x0853 => 1,
  2427. 0x0855 => 1,
  2428. /* ZWJ U+200D */
  2429. 0x0200D => 1);
  2430. /* JOIN TO PREVIOUS LETTER IN LOGICAL ORDER (i.e. AS FINAL/MEDIAL FORM) = Unicode Right-Joining (+ Dual-Joining + Join_Causing) */
  2431. $this->arabRightJoining = array(
  2432. 0x0620 => 1, 0x0622 => 1, 0x0623 => 1, 0x0624 => 1, 0x0625 => 1, 0x0626 => 1, 0x0627 => 1, 0x0628 => 1,
  2433. 0x0629 => 1, 0x062A => 1, 0x062B => 1, 0x062C => 1, 0x062D => 1, 0x062E => 1, 0x062F => 1, 0x0630 => 1,
  2434. 0x0631 => 1, 0x0632 => 1, 0x0633 => 1, 0x0634 => 1, 0x0635 => 1, 0x0636 => 1, 0x0637 => 1, 0x0638 => 1,
  2435. 0x0639 => 1, 0x063A => 1, 0x063B => 1, 0x063C => 1, 0x063D => 1, 0x063E => 1, 0x063F => 1, 0x0640 => 1,
  2436. 0x0641 => 1, 0x0642 => 1, 0x0643 => 1, 0x0644 => 1, 0x0645 => 1, 0x0646 => 1, 0x0647 => 1, 0x0648 => 1,
  2437. 0x0649 => 1, 0x064A => 1, 0x066E => 1, 0x066F => 1, 0x0671 => 1, 0x0672 => 1, 0x0673 => 1, 0x0675 => 1,
  2438. 0x0676 => 1, 0x0677 => 1, 0x0678 => 1, 0x0679 => 1, 0x067A => 1, 0x067B => 1, 0x067C => 1, 0x067D => 1,
  2439. 0x067E => 1, 0x067F => 1, 0x0680 => 1, 0x0681 => 1, 0x0682 => 1, 0x0683 => 1, 0x0684 => 1, 0x0685 => 1,
  2440. 0x0686 => 1, 0x0687 => 1, 0x0688 => 1, 0x0689 => 1, 0x068A => 1, 0x068B => 1, 0x068C => 1, 0x068D => 1,
  2441. 0x068E => 1, 0x068F => 1, 0x0690 => 1, 0x0691 => 1, 0x0692 => 1, 0x0693 => 1, 0x0694 => 1, 0x0695 => 1,
  2442. 0x0696 => 1, 0x0697 => 1, 0x0698 => 1, 0x0699 => 1, 0x069A => 1, 0x069B => 1, 0x069C => 1, 0x069D => 1,
  2443. 0x069E => 1, 0x069F => 1, 0x06A0 => 1, 0x06A1 => 1, 0x06A2 => 1, 0x06A3 => 1, 0x06A4 => 1, 0x06A5 => 1,
  2444. 0x06A6 => 1, 0x06A7 => 1, 0x06A8 => 1, 0x06A9 => 1, 0x06AA => 1, 0x06AB => 1, 0x06AC => 1, 0x06AD => 1,
  2445. 0x06AE => 1, 0x06AF => 1, 0x06B0 => 1, 0x06B1 => 1, 0x06B2 => 1, 0x06B3 => 1, 0x06B4 => 1, 0x06B5 => 1,
  2446. 0x06B6 => 1, 0x06B7 => 1, 0x06B8 => 1, 0x06B9 => 1, 0x06BA => 1, 0x06BB => 1, 0x06BC => 1, 0x06BD => 1,
  2447. 0x06BE => 1, 0x06BF => 1, 0x06C0 => 1, 0x06C1 => 1, 0x06C2 => 1, 0x06C3 => 1, 0x06C4 => 1, 0x06C5 => 1,
  2448. 0x06C6 => 1, 0x06C7 => 1, 0x06C8 => 1, 0x06C9 => 1, 0x06CA => 1, 0x06CB => 1, 0x06CC => 1, 0x06CD => 1,
  2449. 0x06CE => 1, 0x06CF => 1, 0x06D0 => 1, 0x06D1 => 1, 0x06D2 => 1, 0x06D3 => 1, 0x06D5 => 1, 0x06EE => 1,
  2450. 0x06EF => 1, 0x06FA => 1, 0x06FB => 1, 0x06FC => 1, 0x06FF => 1,
  2451. /* Arabic Supplement */
  2452. 0x0750 => 1, 0x0751 => 1, 0x0752 => 1, 0x0753 => 1, 0x0754 => 1, 0x0755 => 1, 0x0756 => 1, 0x0757 => 1,
  2453. 0x0758 => 1, 0x0759 => 1, 0x075A => 1, 0x075B => 1, 0x075C => 1, 0x075D => 1, 0x075E => 1, 0x075F => 1,
  2454. 0x0760 => 1, 0x0761 => 1, 0x0762 => 1, 0x0763 => 1, 0x0764 => 1, 0x0765 => 1, 0x0766 => 1, 0x0767 => 1,
  2455. 0x0768 => 1, 0x0769 => 1, 0x076A => 1, 0x076B => 1, 0x076C => 1, 0x076D => 1, 0x076E => 1, 0x076F => 1,
  2456. 0x0770 => 1, 0x0771 => 1, 0x0772 => 1, 0x0773 => 1, 0x0774 => 1, 0x0775 => 1, 0x0776 => 1, 0x0777 => 1,
  2457. 0x0778 => 1, 0x0779 => 1, 0x077A => 1, 0x077B => 1, 0x077C => 1, 0x077D => 1, 0x077E => 1, 0x077F => 1,
  2458. /* Extended Arabic */
  2459. 0x08A0 => 1, 0x08A2 => 1, 0x08A3 => 1, 0x08A4 => 1, 0x08A5 => 1, 0x08A6 => 1, 0x08A7 => 1, 0x08A8 => 1,
  2460. 0x08A9 => 1, 0x08AA => 1, 0x08AB => 1, 0x08AC => 1,
  2461. /* 'syrc' Syriac */
  2462. 0x0710 => 1, 0x0712 => 1, 0x0713 => 1, 0x0714 => 1, 0x0715 => 1, 0x0716 => 1, 0x0717 => 1, 0x0718 => 1,
  2463. 0x0719 => 1, 0x071A => 1, 0x071B => 1, 0x071C => 1, 0x071D => 1, 0x071E => 1, 0x071F => 1, 0x0720 => 1,
  2464. 0x0721 => 1, 0x0722 => 1, 0x0723 => 1, 0x0724 => 1, 0x0725 => 1, 0x0726 => 1, 0x0727 => 1, 0x0728 => 1,
  2465. 0x0729 => 1, 0x072A => 1, 0x072B => 1, 0x072C => 1, 0x072D => 1, 0x072E => 1, 0x072F => 1, 0x074D => 1,
  2466. 0x074E => 1, 0x074F,
  2467. /* N'Ko */
  2468. 0x07CA => 1, 0x07CB => 1, 0x07CC => 1, 0x07CD => 1, 0x07CE => 1, 0x07CF => 1, 0x07D0 => 1, 0x07D1 => 1,
  2469. 0x07D2 => 1, 0x07D3 => 1, 0x07D4 => 1, 0x07D5 => 1, 0x07D6 => 1, 0x07D7 => 1, 0x07D8 => 1, 0x07D9 => 1,
  2470. 0x07DA => 1, 0x07DB => 1, 0x07DC => 1, 0x07DD => 1, 0x07DE => 1, 0x07DF => 1, 0x07E0 => 1, 0x07E1 => 1,
  2471. 0x07E2 => 1, 0x07E3 => 1, 0x07E4 => 1, 0x07E5 => 1, 0x07E6 => 1, 0x07E7 => 1, 0x07E8 => 1, 0x07E9 => 1,
  2472. 0x07EA => 1, 0x07FA => 1,
  2473. /* Mandaic */
  2474. 0x0841 => 1, 0x0842 => 1, 0x0843 => 1, 0x0844 => 1, 0x0845 => 1, 0x0847 => 1, 0x0848 => 1, 0x084A => 1,
  2475. 0x084B => 1, 0x084C => 1, 0x084D => 1, 0x084E => 1, 0x0850 => 1, 0x0851 => 1, 0x0852 => 1, 0x0853 => 1,
  2476. 0x0855 => 1,
  2477. 0x0840 => 1, 0x0846 => 1, 0x0849 => 1, 0x084F => 1, 0x0854 => 1, /* Right joining */
  2478. /* ZWJ U+200D */
  2479. 0x0200D => 1);
  2480. /* VOWELS = TRANSPARENT-JOINING = Unicode Transparent-Joining type (not just vowels) */
  2481. $this->arabTransparent = array(
  2482. 0x0610 => 1, 0x0611 => 1, 0x0612 => 1, 0x0613 => 1, 0x0614 => 1, 0x0615 => 1, 0x0616 => 1, 0x0617 => 1,
  2483. 0x0618 => 1, 0x0619 => 1, 0x061A => 1, 0x064B => 1, 0x064C => 1, 0x064D => 1, 0x064E => 1, 0x064F => 1,
  2484. 0x0650 => 1, 0x0651 => 1, 0x0652 => 1, 0x0653 => 1, 0x0654 => 1, 0x0655 => 1, 0x0656 => 1, 0x0657 => 1,
  2485. 0x0658 => 1, 0x0659 => 1, 0x065A => 1, 0x065B => 1, 0x065C => 1, 0x065D => 1, 0x065E => 1, 0x065F => 1,
  2486. 0x0670 => 1, 0x06D6 => 1, 0x06D7 => 1, 0x06D8 => 1, 0x06D9 => 1, 0x06DA => 1, 0x06DB => 1, 0x06DC => 1,
  2487. 0x06DF => 1, 0x06E0 => 1, 0x06E1 => 1, 0x06E2 => 1, 0x06E3 => 1, 0x06E4 => 1, 0x06E7 => 1, 0x06E8 => 1,
  2488. 0x06EA => 1, 0x06EB => 1, 0x06EC => 1, 0x06ED => 1,
  2489. /* Extended Arabic */
  2490. 0x08E4 => 1, 0x08E5 => 1, 0x08E6 => 1, 0x08E7 => 1, 0x08E8 => 1, 0x08E9 => 1, 0x08EA => 1, 0x08EB => 1,
  2491. 0x08EC => 1, 0x08ED => 1, 0x08EE => 1, 0x08EF => 1, 0x08F0 => 1, 0x08F1 => 1, 0x08F2 => 1, 0x08F3 => 1,
  2492. 0x08F4 => 1, 0x08F5 => 1, 0x08F6 => 1, 0x08F7 => 1, 0x08F8 => 1, 0x08F9 => 1, 0x08FA => 1, 0x08FB => 1,
  2493. 0x08FC => 1, 0x08FD => 1, 0x08FE => 1,
  2494. /* Arabic ligatures in presentation form (converted in 'ccmp' in e.g. Arial and Times ? need to add others in this range) */
  2495. 0xFC5E => 1, 0xFC5F => 1, 0xFC60 => 1, 0xFC61 => 1, 0xFC62 => 1,
  2496. /* 'syrc' Syriac */
  2497. 0x070F => 1, 0x0711 => 1, 0x0730 => 1, 0x0731 => 1, 0x0732 => 1, 0x0733 => 1, 0x0734 => 1, 0x0735 => 1,
  2498. 0x0736 => 1, 0x0737 => 1, 0x0738 => 1, 0x0739 => 1, 0x073A => 1, 0x073B => 1, 0x073C => 1, 0x073D => 1,
  2499. 0x073E => 1, 0x073F => 1, 0x0740 => 1, 0x0741 => 1, 0x0742 => 1, 0x0743 => 1, 0x0744 => 1, 0x0745 => 1,
  2500. 0x0746 => 1, 0x0747 => 1, 0x0748 => 1, 0x0749 => 1, 0x074A => 1,
  2501. /* N'Ko */
  2502. 0x07EB => 1, 0x07EC => 1, 0x07ED => 1, 0x07EE => 1, 0x07EF => 1, 0x07F0 => 1, 0x07F1 => 1, 0x07F2 => 1,
  2503. 0x07F3 => 1,
  2504. /* Mandaic */
  2505. 0x0859 => 1, 0x085A => 1, 0x085B => 1,
  2506. );
  2507. }
  2508. function arabic_shaper($usetags, $scriptTag)
  2509. {
  2510. $chars = array();
  2511. for ($i = 0; $i < count($this->OTLdata); $i++) {
  2512. $chars[] = $this->OTLdata[$i]['hex'];
  2513. }
  2514. $crntChar = null;
  2515. $prevChar = null;
  2516. $nextChar = null;
  2517. $output = array();
  2518. $max = count($chars);
  2519. for ($i = $max - 1; $i >= 0; $i--) {
  2520. $crntChar = $chars[$i];
  2521. if ($i > 0) {
  2522. $prevChar = hexdec($chars[$i - 1]);
  2523. } else {
  2524. $prevChar = NULL;
  2525. }
  2526. if ($prevChar && isset($this->arabTransparentJoin[$prevChar]) && isset($chars[$i - 2])) {
  2527. $prevChar = hexdec($chars[$i - 2]);
  2528. if ($prevChar && isset($this->arabTransparentJoin[$prevChar]) && isset($chars[$i - 3])) {
  2529. $prevChar = hexdec($chars[$i - 3]);
  2530. if ($prevChar && isset($this->arabTransparentJoin[$prevChar]) && isset($chars[$i - 4])) {
  2531. $prevChar = hexdec($chars[$i - 4]);
  2532. }
  2533. }
  2534. }
  2535. if ($crntChar && isset($this->arabTransparentJoin[hexdec($crntChar)])) {
  2536. // If next_char = RightJoining && prev_char = LeftJoining:
  2537. if (isset($chars[$i + 1]) && $chars[$i + 1] && isset($this->arabRightJoining[hexdec($chars[$i + 1])]) && $prevChar && isset($this->arabLeftJoining[$prevChar])) {
  2538. $output[] = $this->get_arab_glyphs($crntChar, 1, $chars, $i, $scriptTag, $usetags); // <final> form
  2539. } else {
  2540. $output[] = $this->get_arab_glyphs($crntChar, 0, $chars, $i, $scriptTag, $usetags); // <isolated> form
  2541. }
  2542. continue;
  2543. }
  2544. if (hexdec($crntChar) < 128) {
  2545. $output[] = array($crntChar, 0);
  2546. $nextChar = $crntChar;
  2547. continue;
  2548. }
  2549. // 0=ISOLATED FORM :: 1=FINAL :: 2=INITIAL :: 3=MEDIAL
  2550. $form = 0;
  2551. if ($prevChar && isset($this->arabLeftJoining[$prevChar])) {
  2552. $form++;
  2553. }
  2554. if ($nextChar && isset($this->arabRightJoining[hexdec($nextChar)])) {
  2555. $form += 2;
  2556. }
  2557. $output[] = $this->get_arab_glyphs($crntChar, $form, $chars, $i, $scriptTag, $usetags);
  2558. $nextChar = $crntChar;
  2559. }
  2560. $ra = array_reverse($output);
  2561. for ($i = 0; $i < count($this->OTLdata); $i++) {
  2562. $this->OTLdata[$i]['uni'] = hexdec($ra[$i][0]);
  2563. $this->OTLdata[$i]['hex'] = $ra[$i][0];
  2564. $this->OTLdata[$i]['form'] = $ra[$i][1]; // Actaul form substituted 0=ISOLATED FORM :: 1=FINAL :: 2=INITIAL :: 3=MEDIAL
  2565. }
  2566. }
  2567. function get_arab_glyphs($char, $type, &$chars, $i, $scriptTag, $usetags)
  2568. {
  2569. // Optional Feature settings // doesn't control Syriac at present
  2570. if (($type === 0 && strpos($usetags, 'isol') === false) || ($type === 1 && strpos($usetags, 'fina') === false) || ($type === 2 && strpos($usetags, 'init') === false) || ($type === 3 && strpos($usetags, 'medi') === false)) {
  2571. return array($char, 0);
  2572. }
  2573. // 0=ISOLATED FORM :: 1=FINAL :: 2=INITIAL :: 3=MEDIAL (:: 4=MED2 :: 5=FIN2 :: 6=FIN3)
  2574. $retk = -1;
  2575. // Alaph 00710 in Syriac
  2576. if ($scriptTag == 'syrc' && $char == '00710') {
  2577. // if there is a preceding (base?) character *** should search back to previous base - ignoring vowels and change $n
  2578. // set $n as the position of the last base; for now we'll just do this:
  2579. $n = $i - 1;
  2580. // if the preceding (base) character cannot be joined to
  2581. // not in $this->arabLeftJoining i.e. not a char which can join to the next one
  2582. if (isset($chars[$n]) && isset($this->arabLeftJoining[hexdec($chars[$n])])) {
  2583. // if in the middle of Syriac words
  2584. if (isset($chars[$i + 1]) && preg_match('/[\x{0700}-\x{0745}]/u', code2utf(hexdec($chars[$n]))) && preg_match('/[\x{0700}-\x{0745}]/u', code2utf(hexdec($chars[$i + 1]))) && isset($this->arabGlyphs[$char][4])) {
  2585. $retk = 4;
  2586. }
  2587. // if at the end of Syriac words
  2588. else if (!isset($chars[$i + 1]) || !preg_match('/[\x{0700}-\x{0745}]/u', code2utf(hexdec($chars[$i + 1])))) {
  2589. // if preceding base character IS (00715|00716|0072A)
  2590. if (strpos('0715|0716|072A', $chars[$n]) !== false && isset($this->arabGlyphs[$char][6])) {
  2591. $retk = 6;
  2592. }
  2593. // else if preceding base character is NOT (00715|00716|0072A)
  2594. else if (isset($this->arabGlyphs[$char][5])) {
  2595. $retk = 5;
  2596. }
  2597. }
  2598. }
  2599. if ($retk != -1) {
  2600. return array($this->arabGlyphs[$char][$retk], $retk);
  2601. } else {
  2602. return array($char, 0);
  2603. }
  2604. }
  2605. if (($type > 0 || $type === 0) && isset($this->arabGlyphs[$char][$type])) {
  2606. $retk = $type;
  2607. } else if ($type == 3 && isset($this->arabGlyphs[$char][1])) { // if <medial> not defined, but <final>, return <final>
  2608. $retk = 1;
  2609. } else if ($type == 2 && isset($this->arabGlyphs[$char][0])) { // if <initial> not defined, but <isolated>, return <isolated>
  2610. $retk = 0;
  2611. }
  2612. if ($retk != -1) {
  2613. $match = true;
  2614. // If GSUB includes a Backtrack or Lookahead condition (e.g. font ArabicTypesetting)
  2615. if (isset($this->arabGlyphs[$char]['prel'][$retk]) && $this->arabGlyphs[$char]['prel'][$retk]) {
  2616. $ig = 1;
  2617. foreach ($this->arabGlyphs[$char]['prel'][$retk] AS $k => $v) { // $k starts 0, 1...
  2618. if (!isset($chars[$i - $ig - $k])) {
  2619. $match = false;
  2620. } else if (strpos($v, $chars[$i - $ig - $k]) === false) {
  2621. while (strpos($this->arabGlyphs[$char]['ignore'][$retk], $chars[$i - $ig - $k]) !== false) { // ignore
  2622. $ig++;
  2623. }
  2624. if (!isset($chars[$i - $ig - $k])) {
  2625. $match = false;
  2626. } else if (strpos($v, $chars[$i - $ig - $k]) === false) {
  2627. $match = false;
  2628. }
  2629. }
  2630. }
  2631. }
  2632. if (isset($this->arabGlyphs[$char]['postl'][$retk]) && $this->arabGlyphs[$char]['postl'][$retk]) {
  2633. $ig = 1;
  2634. foreach ($this->arabGlyphs[$char]['postl'][$retk] AS $k => $v) { // $k starts 0, 1...
  2635. if (!isset($chars[$i + $ig + $k])) {
  2636. $match = false;
  2637. } else if (strpos($v, $chars[$i + $ig + $k]) === false) {
  2638. while (strpos($this->arabGlyphs[$char]['ignore'][$retk], $chars[$i + $ig + $k]) !== false) { // ignore
  2639. $ig++;
  2640. }
  2641. if (!isset($chars[$i + $ig + $k])) {
  2642. $match = false;
  2643. } else if (strpos($v, $chars[$i + $ig + $k]) === false) {
  2644. $match = false;
  2645. }
  2646. }
  2647. }
  2648. }
  2649. if ($match) {
  2650. return array($this->arabGlyphs[$char][$retk], $retk);
  2651. } else {
  2652. return array($char, 0);
  2653. }
  2654. } else {
  2655. return array($char, 0);
  2656. }
  2657. }
  2658. ////////////////////////////////////////////////////////////////
  2659. ////////////////////////////////////////////////////////////////
  2660. ///////////////// LINE BREAKING ///////////////////////
  2661. ////////////////////////////////////////////////////////////////
  2662. ////////////////////////////////////////////////////////////////
  2663. ////////////////////////////////////////////////////////////////
  2664. ///////////// TIBETAN LINE BREAKING ///////////////////
  2665. ////////////////////////////////////////////////////////////////
  2666. // Sets $this->OTLdata[$i]['wordend']=true at possible end of word boundaries
  2667. function TibetanlineBreaking()
  2668. {
  2669. for ($ptr = 0; $ptr < count($this->OTLdata); $ptr++) {
  2670. // Break opportunities at U+0F0B Tsheg or U=0F0D
  2671. if (isset($this->OTLdata[$ptr]['uni']) && ($this->OTLdata[$ptr]['uni'] == 0x0F0B || $this->OTLdata[$ptr]['uni'] == 0x0F0D)) {
  2672. if (isset($this->OTLdata[$ptr + 1]['uni']) && ($this->OTLdata[$ptr + 1]['uni'] == 0x0F0D || $this->OTLdata[$ptr + 1]['uni'] == 0xF0E)) {
  2673. continue;
  2674. }
  2675. // Set end of word marker in OTLdata at matchpos
  2676. $this->OTLdata[$ptr]['wordend'] = true;
  2677. }
  2678. }
  2679. }
  2680. ////////////////////////////////////////////////////////////////
  2681. ////////// SOUTH EAST ASIAN LINE BREAKING /////////////
  2682. ////////////////////////////////////////////////////////////////
  2683. // South East Asian Linebreaking (Thai, Khmer and Lao) using dictionary of words
  2684. // Sets $this->OTLdata[$i]['wordend']=true at possible end of word boundaries
  2685. function SEAlineBreaking()
  2686. {
  2687. // Load Line-breaking dictionary
  2688. if (!isset($this->lbdicts[$this->shaper]) && file_exists(_MPDF_PATH . 'includes/linebrdict' . $this->shaper . '.dat')) {
  2689. $this->lbdicts[$this->shaper] = file_get_contents(_MPDF_PATH . 'includes/linebrdict' . $this->shaper . '.dat');
  2690. }
  2691. $dict = &$this->lbdicts[$this->shaper];
  2692. // Find all word boundaries and mark end of word $this->OTLdata[$i]['wordend']=true on last character
  2693. // If Thai, allow for possible suffixes (not in Lao or Khmer)
  2694. // repeater/ellision characters
  2695. // (0x0E2F); // Ellision character THAI_PAIYANNOI 0x0E2F UTF-8 0xE0 0xB8 0xAF
  2696. // (0x0E46); // Repeat character THAI_MAIYAMOK 0x0E46 UTF-8 0xE0 0xB9 0x86
  2697. // (0x0EC6); // Repeat character LAO UTF-8 0xE0 0xBB 0x86
  2698. $rollover = array();
  2699. $ptr = 0;
  2700. while ($ptr < count($this->OTLdata) - 3) {
  2701. if (count($rollover)) {
  2702. $matches = $rollover;
  2703. $rollover = array();
  2704. } else {
  2705. $matches = $this->checkwordmatch($dict, $ptr);
  2706. }
  2707. if (count($matches) == 1) {
  2708. $matchpos = $matches[0];
  2709. // Check for repeaters - if so $matchpos++
  2710. if (isset($this->OTLdata[$matchpos + 1]['uni']) && ($this->OTLdata[$matchpos + 1]['uni'] == 0x0E2F || $this->OTLdata[$matchpos + 1]['uni'] == 0x0E46 || $this->OTLdata[$matchpos + 1]['uni'] == 0x0EC6)) {
  2711. $matchpos++;
  2712. }
  2713. // Set end of word marker in OTLdata at matchpos
  2714. $this->OTLdata[$matchpos]['wordend'] = true;
  2715. $ptr = $matchpos + 1;
  2716. } else if (empty($matches)) {
  2717. $ptr++;
  2718. // Move past any ASCII characters
  2719. while (isset($this->OTLdata[$ptr]['uni']) && ($this->OTLdata[$ptr]['uni'] >> 8) == 0) {
  2720. $ptr++;
  2721. }
  2722. } else { // Multiple matches
  2723. $secondmatch = false;
  2724. for ($m = count($matches) - 1; $m >= 0; $m--) {
  2725. //for ($m=0;$m<count($matches);$m++) {
  2726. $firstmatch = $matches[$m];
  2727. $matches2 = $this->checkwordmatch($dict, $firstmatch + 1);
  2728. if (count($matches2)) {
  2729. // Set end of word marker in OTLdata at matchpos
  2730. $this->OTLdata[$firstmatch]['wordend'] = true;
  2731. $ptr = $firstmatch + 1;
  2732. $rollover = $matches2;
  2733. $secondmatch = true;
  2734. break;
  2735. }
  2736. }
  2737. if (!$secondmatch) {
  2738. // Set end of word marker in OTLdata at end of longest first match
  2739. $this->OTLdata[$matches[count($matches) - 1]]['wordend'] = true;
  2740. $ptr = $matches[count($matches) - 1] + 1;
  2741. // Move past any ASCII characters
  2742. while (isset($this->OTLdata[$ptr]['uni']) && ($this->OTLdata[$ptr]['uni'] >> 8) == 0) {
  2743. $ptr++;
  2744. }
  2745. }
  2746. }
  2747. }
  2748. }
  2749. function checkwordmatch(&$dict, $ptr)
  2750. {
  2751. /*
  2752. define("_DICT_NODE_TYPE_SPLIT", 0x01);
  2753. define("_DICT_NODE_TYPE_LINEAR", 0x02);
  2754. define("_DICT_INTERMEDIATE_MATCH", 0x03);
  2755. define("_DICT_FINAL_MATCH", 0x04);
  2756. Node type: Split.
  2757. Divide at < 98 >= 98
  2758. Offset for >= 98 == 79 (long 4-byte unsigned)
  2759. Node type: Linear match.
  2760. Char = 97
  2761. Intermediate match
  2762. Final match
  2763. */
  2764. $dictptr = 0;
  2765. $ok = true;
  2766. $matches = array();
  2767. while ($ok) {
  2768. $x = ord($dict{$dictptr});
  2769. $c = $this->OTLdata[$ptr]['uni'] & 0xFF;
  2770. if ($x == _DICT_INTERMEDIATE_MATCH) {
  2771. //echo "DICT_INTERMEDIATE_MATCH: ".dechex($c).'<br />';
  2772. // Do not match if next character in text is a Mark
  2773. if (isset($this->OTLdata[$ptr]['uni']) && strpos($this->GlyphClassMarks, $this->OTLdata[$ptr]['hex']) === false) {
  2774. $matches[] = $ptr - 1;
  2775. }
  2776. $dictptr++;
  2777. } else if ($x == _DICT_FINAL_MATCH) {
  2778. //echo "DICT_FINAL_MATCH: ".dechex($c).'<br />';
  2779. // Do not match if next character in text is a Mark
  2780. if (isset($this->OTLdata[$ptr]['uni']) && strpos($this->GlyphClassMarks, $this->OTLdata[$ptr]['hex']) === false) {
  2781. $matches[] = $ptr - 1;
  2782. }
  2783. return $matches;
  2784. } else if ($x == _DICT_NODE_TYPE_LINEAR) {
  2785. //echo "DICT_NODE_TYPE_LINEAR: ".dechex($c).'<br />';
  2786. $dictptr++;
  2787. $m = ord($dict{$dictptr});
  2788. if ($c == $m) {
  2789. $ptr++;
  2790. if ($ptr > count($this->OTLdata) - 1) {
  2791. $next = ord($dict{$dictptr + 1});
  2792. if ($next == _DICT_INTERMEDIATE_MATCH || $next == _DICT_FINAL_MATCH) {
  2793. // Do not match if next character in text is a Mark
  2794. if (isset($this->OTLdata[$ptr]['uni']) && strpos($this->GlyphClassMarks, $this->OTLdata[$ptr]['hex']) === false) {
  2795. $matches[] = $ptr - 1;
  2796. }
  2797. }
  2798. return $matches;
  2799. }
  2800. $dictptr++;
  2801. continue;
  2802. } else {
  2803. //echo "DICT_NODE_TYPE_LINEAR NOT: ".dechex($c).'<br />';
  2804. return $matches;
  2805. }
  2806. } else if ($x == _DICT_NODE_TYPE_SPLIT) {
  2807. //echo "DICT_NODE_TYPE_SPLIT ON ".dechex($d).": ".dechex($c).'<br />';
  2808. $dictptr++;
  2809. $d = ord($dict{$dictptr});
  2810. if ($c < $d) {
  2811. $dictptr += 5;
  2812. } else {
  2813. $dictptr++;
  2814. // Unsigned long 32-bit offset
  2815. $offset = (ord($dict{$dictptr}) * 16777216) + (ord($dict{$dictptr + 1}) << 16) + (ord($dict{$dictptr + 2}) << 8) + ord($dict{$dictptr + 3});
  2816. $dictptr = $offset;
  2817. }
  2818. } else {
  2819. //echo "PROBLEM: ".($x).'<br />';
  2820. $ok = false; // Something has gone wrong
  2821. }
  2822. }
  2823. return $matches;
  2824. }
  2825. ////////////////////////////////////////////////////////////////
  2826. ////////////////////////////////////////////////////////////////
  2827. ////////// GPOS ///////////////////////////////////////
  2828. ////////////////////////////////////////////////////////////////
  2829. ////////////////////////////////////////////////////////////////
  2830. function _applyGPOSrules($LookupList, $is_old_spec = false)
  2831. {
  2832. foreach ($LookupList AS $lu => $tag) {
  2833. $Type = $this->GPOSLookups[$lu]['Type'];
  2834. $Flag = $this->GPOSLookups[$lu]['Flag'];
  2835. $MarkFilteringSet = '';
  2836. if (isset($this->GPOSLookups[$lu]['MarkFilteringSet']))
  2837. $MarkFilteringSet = $this->GPOSLookups[$lu]['MarkFilteringSet'];
  2838. $ptr = 0;
  2839. // Test each glyph sequentially
  2840. while ($ptr < (count($this->OTLdata))) { // whilst there is another glyph ..0064
  2841. $currGlyph = $this->OTLdata[$ptr]['hex'];
  2842. $currGID = $this->OTLdata[$ptr]['uni'];
  2843. $shift = 1;
  2844. foreach ($this->GPOSLookups[$lu]['Subtables'] AS $c => $subtable_offset) {
  2845. // NB Coverage only looks at glyphs for position 1 (esp. 7.3 and 8.3)
  2846. if (isset($this->LuCoverage[$lu][$c][$currGID])) {
  2847. // Get rules from font GPOS subtable
  2848. if (isset($this->OTLdata[$ptr]['bidi_type'])) { // No need to check bidi_type - just a check that it exists
  2849. $shift = $this->_applyGPOSsubtable($lu, $c, $ptr, $currGlyph, $currGID, ($subtable_offset - $this->GPOS_offset + $this->GSUB_length), $Type, $Flag, $MarkFilteringSet, $this->LuCoverage[$lu][$c], $tag, 0, $is_old_spec);
  2850. if ($shift) {
  2851. break;
  2852. }
  2853. }
  2854. }
  2855. }
  2856. if ($shift == 0) {
  2857. $shift = 1;
  2858. }
  2859. $ptr += $shift;
  2860. }
  2861. }
  2862. }
  2863. //////////////////////////////////////////////////////////////////////////////////
  2864. // GPOS Types
  2865. // Lookup Type 1: Single Adjustment Positioning Subtable Adjust position of a single glyph
  2866. // Lookup Type 2: Pair Adjustment Positioning Subtable Adjust position of a pair of glyphs
  2867. // Lookup Type 3: Cursive Attachment Positioning Subtable Attach cursive glyphs
  2868. // Lookup Type 4: MarkToBase Attachment Positioning Subtable Attach a combining mark to a base glyph
  2869. // Lookup Type 5: MarkToLigature Attachment Positioning Subtable Attach a combining mark to a ligature
  2870. // Lookup Type 6: MarkToMark Attachment Positioning Subtable Attach a combining mark to another mark
  2871. // Lookup Type 7: Contextual Positioning Subtables Position one or more glyphs in context
  2872. // Lookup Type 8: Chaining Contextual Positioning Subtable Position one or more glyphs in chained context
  2873. // Lookup Type 9: Extension positioning
  2874. //////////////////////////////////////////////////////////////////////////////////
  2875. function _applyGPOSvaluerecord($basepos, $Value)
  2876. {
  2877. // If current glyph is a mark with a defined width, any XAdvance is considered to REPLACE the character Advance Width
  2878. // Test case <div style="font-family:myanmartext">&#x1004;&#x103a;&#x1039;&#x1000;&#x1039;&#x1000;&#x103b;&#x103c;&#x103d;&#x1031;&#x102d;</div>
  2879. if (strpos($this->GlyphClassMarks, $this->OTLdata[$basepos]['hex']) !== false) {
  2880. $cw = round($this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], $this->OTLdata[$basepos]['uni']) * $this->mpdf->CurrentFont['unitsPerEm'] / 1000); // convert back to font design units
  2881. } else {
  2882. $cw = 0;
  2883. }
  2884. $apos = $this->_getXAdvancePos($basepos);
  2885. if (isset($Value['XAdvance']) && ($Value['XAdvance'] - $cw) != 0) {
  2886. // However DON'T REPLACE the character Advance Width if Advance Width is negative
  2887. // Test case <div style="font-family: dejavusansmono">&#x440;&#x443;&#x301;&#x441;&#x441;&#x43a;&#x438;&#x439;</div>
  2888. if ($Value['XAdvance'] < 0) {
  2889. $cw = 0;
  2890. }
  2891. // For LTR apply XAdvanceL to the last mark following the base = at $apos
  2892. // For RTL apply XAdvanceR to base = at $basepos
  2893. if (isset($this->OTLdata[$apos]['GPOSinfo']['XAdvanceL'])) {
  2894. $this->OTLdata[$apos]['GPOSinfo']['XAdvanceL'] += $Value['XAdvance'] - $cw;
  2895. } else {
  2896. $this->OTLdata[$apos]['GPOSinfo']['XAdvanceL'] = $Value['XAdvance'] - $cw;
  2897. }
  2898. if (isset($this->OTLdata[$basepos]['GPOSinfo']['XAdvanceR'])) {
  2899. $this->OTLdata[$basepos]['GPOSinfo']['XAdvanceR'] += $Value['XAdvance'] - $cw;
  2900. } else {
  2901. $this->OTLdata[$basepos]['GPOSinfo']['XAdvanceR'] = $Value['XAdvance'] - $cw;
  2902. }
  2903. }
  2904. // Any XPlacement (? and Y Placement) apply to base and marks (from basepos to apos)
  2905. for ($a = $basepos; $a <= $apos; $a++) {
  2906. if (isset($Value['XPlacement'])) {
  2907. if (isset($this->OTLdata[$a]['GPOSinfo']['XPlacement'])) {
  2908. $this->OTLdata[$a]['GPOSinfo']['XPlacement'] += $Value['XPlacement'];
  2909. } else {
  2910. $this->OTLdata[$a]['GPOSinfo']['XPlacement'] = $Value['XPlacement'];
  2911. }
  2912. }
  2913. if (isset($Value['YPlacement'])) {
  2914. if (isset($this->OTLdata[$a]['GPOSinfo']['YPlacement'])) {
  2915. $this->OTLdata[$a]['GPOSinfo']['YPlacement'] += $Value['YPlacement'];
  2916. } else {
  2917. $this->OTLdata[$a]['GPOSinfo']['YPlacement'] = $Value['YPlacement'];
  2918. }
  2919. }
  2920. }
  2921. }
  2922. // If XAdvance is aplied to $ptr - in order for PDF to position the Advance correctly need to place it on
  2923. // the last of any Marks which immediately follow the current glyph
  2924. function _getXAdvancePos($pos)
  2925. {
  2926. // NB Not all fonts have all marks specified in GlyphClassMarks
  2927. // If the current glyph is not a base (but a mark) then ignore this, and apply to the current position
  2928. if (strpos($this->GlyphClassMarks, $this->OTLdata[$pos]['hex']) !== false) {
  2929. return $pos;
  2930. }
  2931. while (isset($this->OTLdata[$pos + 1]['hex']) && strpos($this->GlyphClassMarks, $this->OTLdata[$pos + 1]['hex']) !== false) {
  2932. $pos++;
  2933. }
  2934. return $pos;
  2935. }
  2936. function _applyGPOSsubtable($lookupID, $subtable, $ptr, $currGlyph, $currGID, $subtable_offset, $Type, $Flag, $MarkFilteringSet, $LuCoverage, $tag, $level = 0, $is_old_spec)
  2937. {
  2938. if (($Flag & 0x0001) == 1) {
  2939. $dir = 'RTL';
  2940. } // only used for Type 3
  2941. else {
  2942. $dir = 'LTR';
  2943. }
  2944. $ignore = $this->_getGCOMignoreString($Flag, $MarkFilteringSet);
  2945. // Lets start
  2946. $this->seek($subtable_offset);
  2947. $PosFormat = $this->read_ushort();
  2948. ////////////////////////////////////////////////////////////////////////////////
  2949. // LookupType 1: Single adjustment Adjust position of a single glyph (e.g. SmallCaps/Sups/Subs)
  2950. ////////////////////////////////////////////////////////////////////////////////
  2951. if ($Type == 1) {
  2952. //===========
  2953. // Format 1:
  2954. //===========
  2955. if ($PosFormat == 1) {
  2956. $Coverage = $subtable_offset + $this->read_ushort();
  2957. $ValueFormat = $this->read_ushort();
  2958. $Value = $this->_getValueRecord($ValueFormat);
  2959. }
  2960. //===========
  2961. // Format 2:
  2962. //===========
  2963. else if ($PosFormat == 2) {
  2964. $Coverage = $subtable_offset + $this->read_ushort();
  2965. $ValueFormat = $this->read_ushort();
  2966. $ValueCount = $this->read_ushort();
  2967. $GlyphPos = $LuCoverage[$currGID];
  2968. $this->skip($GlyphPos * 2 * $this->count_bits($ValueFormat));
  2969. $Value = $this->_getValueRecord($ValueFormat);
  2970. }
  2971. $this->_applyGPOSvaluerecord($ptr, $Value);
  2972. if ($this->debugOTL) {
  2973. $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level);
  2974. }
  2975. return 1;
  2976. }
  2977. ////////////////////////////////////////////////////////////////////////////////
  2978. // LookupType 2: Pair adjustment Adjust position of a pair of glyphs (Kerning)
  2979. ////////////////////////////////////////////////////////////////////////////////
  2980. else if ($Type == 2) {
  2981. $Coverage = $subtable_offset + $this->read_ushort();
  2982. $ValueFormat1 = $this->read_ushort();
  2983. $ValueFormat2 = $this->read_ushort();
  2984. $sizeOfPair = ( 2 * $this->count_bits($ValueFormat1) ) + ( 2 * $this->count_bits($ValueFormat2) );
  2985. //===========
  2986. // Format 1:
  2987. //===========
  2988. if ($PosFormat == 1) {
  2989. $PairSetCount = $this->read_ushort();
  2990. $PairSetOffset = array();
  2991. for ($p = 0; $p < $PairSetCount; $p++) {
  2992. $PairSetOffset[] = $subtable_offset + $this->read_ushort();
  2993. }
  2994. for ($p = 0; $p < $PairSetCount; $p++) {
  2995. if (isset($LuCoverage[$currGID]) && $LuCoverage[$currGID] == $p) {
  2996. $this->seek($PairSetOffset[$p]);
  2997. //PairSet table
  2998. $PairValueCount = $this->read_ushort();
  2999. for ($pv = 0; $pv < $PairValueCount; $pv++) {
  3000. //PairValueRecord
  3001. $gid = $this->read_ushort();
  3002. $SecondGlyph = $this->glyphToChar($gid);
  3003. $FirstGlyph = $this->OTLdata[$ptr]['uni'];
  3004. $checkpos = $ptr;
  3005. $checkpos++;
  3006. while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) {
  3007. $checkpos++;
  3008. }
  3009. if (isset($this->OTLdata[$checkpos]) && $this->OTLdata[$checkpos]['uni'] == $SecondGlyph) {
  3010. $matchedpos = $checkpos;
  3011. } else {
  3012. $matchedpos = false;
  3013. }
  3014. if ($matchedpos !== false) {
  3015. $Value1 = $this->_getValueRecord($ValueFormat1);
  3016. $Value2 = $this->_getValueRecord($ValueFormat2);
  3017. if ($ValueFormat1) {
  3018. $this->_applyGPOSvaluerecord($ptr, $Value1);
  3019. }
  3020. if ($ValueFormat2) {
  3021. $this->_applyGPOSvaluerecord($matchedpos, $Value2);
  3022. if ($this->debugOTL) {
  3023. $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level);
  3024. }
  3025. return $matchedpos - $ptr + 1;
  3026. }
  3027. if ($this->debugOTL) {
  3028. $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level);
  3029. }
  3030. return $matchedpos - $ptr;
  3031. } else {
  3032. $this->skip($sizeOfPair);
  3033. }
  3034. }
  3035. }
  3036. }
  3037. return 0;
  3038. }
  3039. //===========
  3040. // Format 2:
  3041. //===========
  3042. else if ($PosFormat == 2) {
  3043. $ClassDef1 = $subtable_offset + $this->read_ushort();
  3044. $ClassDef2 = $subtable_offset + $this->read_ushort();
  3045. $Class1Count = $this->read_ushort();
  3046. $Class2Count = $this->read_ushort();
  3047. $sizeOfValueRecords = $Class1Count * $Class2Count * $sizeOfPair;
  3048. //$this->skip($sizeOfValueRecords ); ???? NOT NEEDED
  3049. // NB Class1Count includes Class 0 even though it is not defined by $ClassDef1
  3050. // i.e. Class1Count = 5; Class1 will contain array(indices 1-4);
  3051. $Class1 = $this->_getClassDefinitionTable($ClassDef1);
  3052. $Class2 = $this->_getClassDefinitionTable($ClassDef2);
  3053. $FirstGlyph = $this->OTLdata[$ptr]['uni'];
  3054. $checkpos = $ptr;
  3055. $checkpos++;
  3056. while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) {
  3057. $checkpos++;
  3058. }
  3059. if (isset($this->OTLdata[$checkpos])) {
  3060. $matchedpos = $checkpos;
  3061. } else {
  3062. return 0;
  3063. }
  3064. $SecondGlyph = $this->OTLdata[$matchedpos]['uni'];
  3065. for ($i = 0; $i < $Class1Count; $i++) {
  3066. if (isset($Class1[$i]) && count($Class1[$i])) {
  3067. $FirstClassPos = array_search($FirstGlyph, $Class1[$i]);
  3068. if ($FirstClassPos === false) {
  3069. continue;
  3070. } else {
  3071. for ($j = 0; $j < $Class2Count; $j++) {
  3072. if (isset($Class2[$j]) && count($Class2[$j])) {
  3073. $SecondClassPos = array_search($SecondGlyph, $Class2[$j]);
  3074. if ($SecondClassPos === false) {
  3075. continue;
  3076. }
  3077. // Get ValueRecord[$i][$j]
  3078. $offs = ($i * $Class2Count * $sizeOfPair) + ($j * $sizeOfPair);
  3079. $this->seek($subtable_offset + 16 + $offs);
  3080. $Value1 = $this->_getValueRecord($ValueFormat1);
  3081. $Value2 = $this->_getValueRecord($ValueFormat2);
  3082. if ($ValueFormat1) {
  3083. $this->_applyGPOSvaluerecord($ptr, $Value1);
  3084. }
  3085. if ($ValueFormat2) {
  3086. $this->_applyGPOSvaluerecord($matchedpos, $Value2);
  3087. if ($this->debugOTL) {
  3088. $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level);
  3089. }
  3090. return $matchedpos - $ptr + 1;
  3091. }
  3092. if ($this->debugOTL) {
  3093. $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level);
  3094. }
  3095. return $matchedpos - $ptr;
  3096. }
  3097. }
  3098. }
  3099. }
  3100. }
  3101. return 0;
  3102. }
  3103. }
  3104. ////////////////////////////////////////////////////////////////////////////////
  3105. // LookupType 3: Cursive attachment Attach cursive glyphs
  3106. ////////////////////////////////////////////////////////////////////////////////
  3107. else if ($Type == 3) {
  3108. $this->skip(4);
  3109. // Need default XAdvance for glyph
  3110. $pdfWidth = $this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], hexdec($currGlyph)); // DON'T convert back to design units
  3111. $CPos = $LuCoverage[$currGID];
  3112. $this->skip($CPos * 4);
  3113. $EntryAnchor = $this->read_ushort();
  3114. $ExitAnchor = $this->read_ushort();
  3115. if ($EntryAnchor != 0) {
  3116. $EntryAnchor += $subtable_offset;
  3117. list($x, $y) = $this->_getAnchorTable($EntryAnchor);
  3118. if ($dir == 'RTL') {
  3119. if (round($pdfWidth) == round($x * 1000 / $this->mpdf->CurrentFont['unitsPerEm'])) {
  3120. $x = 0;
  3121. } else {
  3122. $x = $x - ($pdfWidth * $this->mpdf->CurrentFont['unitsPerEm'] / 1000);
  3123. }
  3124. }
  3125. $this->Entry[$ptr] = array('X' => $x, 'Y' => $y, 'dir' => $dir);
  3126. }
  3127. if ($ExitAnchor != 0) {
  3128. $ExitAnchor += $subtable_offset;
  3129. list($x, $y) = $this->_getAnchorTable($ExitAnchor);
  3130. if ($dir == 'LTR') {
  3131. if (round($pdfWidth) == round($x * 1000 / $this->mpdf->CurrentFont['unitsPerEm'])) {
  3132. $x = 0;
  3133. } else {
  3134. $x = $x - ($pdfWidth * $this->mpdf->CurrentFont['unitsPerEm'] / 1000);
  3135. }
  3136. }
  3137. $this->Exit[$ptr] = array('X' => $x, 'Y' => $y, 'dir' => $dir);
  3138. }
  3139. if ($this->debugOTL) {
  3140. $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level);
  3141. }
  3142. return 1;
  3143. }
  3144. ////////////////////////////////////////////////////////////////////////////////
  3145. // LookupType 4: MarkToBase attachment Attach a combining mark to a base glyph
  3146. ////////////////////////////////////////////////////////////////////////////////
  3147. else if ($Type == 4) {
  3148. $MarkCoverage = $subtable_offset + $this->read_ushort();
  3149. //$MarkCoverage is already set in $LuCoverage 00065|00073 etc
  3150. $BaseCoverage = $subtable_offset + $this->read_ushort();
  3151. $ClassCount = $this->read_ushort(); // Number of classes defined for marks = Number of mark glyphs in the MarkCoverage table
  3152. $MarkArray = $subtable_offset + $this->read_ushort(); // Offset to MarkArray table
  3153. $BaseArray = $subtable_offset + $this->read_ushort(); // Offset to BaseArray table
  3154. $this->seek($BaseCoverage);
  3155. $BaseGlyphs = implode('|', $this->_getCoverage());
  3156. $checkpos = $ptr;
  3157. $checkpos--;
  3158. // ZZZ93
  3159. // In Lohit-Kannada font (old-spec), rules specify a Type 4 GPOS to attach below-forms to base glyph
  3160. // the repositioning does not happen in MS Word, and shouldn't happen comparing with other fonts
  3161. // ?Why not
  3162. // This Fix blocks the GPOS rule if the "mark" is not actually classified as a mark in the GlyphClasses of GDEF
  3163. // but only in Indic old-spec.
  3164. // Test cases: &#xca8;&#xccd;&#xca8;&#xcc1; and &#xc95;&#xccd;&#xcb0;&#xccc;
  3165. if ($this->shaper == 'I' && $is_old_spec && strpos($this->GlyphClassMarks, $this->OTLdata[$ptr]['hex']) === false) {
  3166. return;
  3167. }
  3168. // "To identify the base glyph that combines with a mark, the text-processing client must look backward in the glyph string from the mark to the preceding base glyph."
  3169. while (isset($this->OTLdata[$checkpos]) && strpos($this->GlyphClassMarks, $this->OTLdata[$checkpos]['hex']) !== false) {
  3170. $checkpos--;
  3171. }
  3172. if (isset($this->OTLdata[$checkpos]) && strpos($BaseGlyphs, $this->OTLdata[$checkpos]['hex']) !== false) {
  3173. $matchedpos = $checkpos;
  3174. } else {
  3175. $matchedpos = false;
  3176. }
  3177. if ($matchedpos !== false) {
  3178. // Get the relevant MarkRecord
  3179. $MarkPos = $LuCoverage[$currGID];
  3180. $MarkRecord = $this->_getMarkRecord($MarkArray, $MarkPos); // e.g. Array ( [Class] => 0 [AnchorX] => -549 [AnchorY] => 1548 )
  3181. //Mark Class is = $MarkRecord['Class']
  3182. // Get the relevant BaseRecord
  3183. $this->seek($BaseArray);
  3184. $BaseCount = $this->read_ushort();
  3185. $BasePos = strpos($BaseGlyphs, $this->OTLdata[$matchedpos]['hex']) / 6;
  3186. // Move to the BaseRecord we want
  3187. $nSkip = (2 * $BasePos * $ClassCount );
  3188. $this->skip($nSkip);
  3189. // Read BaseRecord we want for appropriate Class
  3190. $nSkip = 2 * $MarkRecord['Class'];
  3191. $this->skip($nSkip);
  3192. $BaseRecordOffset = $BaseArray + $this->read_ushort();
  3193. list($x, $y) = $this->_getAnchorTable($BaseRecordOffset);
  3194. $BaseRecord = array('AnchorX' => $x, 'AnchorY' => $y); // e.g. Array ( [AnchorX] => 660 [AnchorY] => 1556 )
  3195. // Need default XAdvance for Base glyph
  3196. $BaseWidth = $this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], $this->OTLdata[$matchedpos]['uni']) * $this->mpdf->CurrentFont['unitsPerEm'] / 1000; // convert back to font design units
  3197. $this->OTLdata[$ptr]['GPOSinfo']['BaseWidth'] = $BaseWidth;
  3198. // And any intervening (ignored) characters
  3199. if (($ptr - $matchedpos) > 1) {
  3200. for ($i = $matchedpos + 1; $i < $ptr; $i++) {
  3201. $BaseWidthExtra = $this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], $this->OTLdata[$i]['uni']) * $this->mpdf->CurrentFont['unitsPerEm'] / 1000; // convert back to font design units
  3202. $this->OTLdata[$ptr]['GPOSinfo']['BaseWidth'] += $BaseWidthExtra;
  3203. }
  3204. }
  3205. // Align to previous Glyph by attachment - so need to add to previous placement values
  3206. $prevXPlacement = (isset($this->OTLdata[$matchedpos]['GPOSinfo']['XPlacement']) ? $this->OTLdata[$matchedpos]['GPOSinfo']['XPlacement'] : 0);
  3207. $prevYPlacement = (isset($this->OTLdata[$matchedpos]['GPOSinfo']['YPlacement']) ? $this->OTLdata[$matchedpos]['GPOSinfo']['YPlacement'] : 0);
  3208. $this->OTLdata[$ptr]['GPOSinfo']['XPlacement'] = $prevXPlacement + $BaseRecord['AnchorX'] - $MarkRecord['AnchorX'];
  3209. $this->OTLdata[$ptr]['GPOSinfo']['YPlacement'] = $prevYPlacement + $BaseRecord['AnchorY'] - $MarkRecord['AnchorY'];
  3210. if ($this->debugOTL) {
  3211. $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level);
  3212. }
  3213. return 1;
  3214. }
  3215. return 0;
  3216. }
  3217. ////////////////////////////////////////////////////////////////////////////////
  3218. // LookupType 5: MarkToLigature attachment Attach a combining mark to a ligature
  3219. ////////////////////////////////////////////////////////////////////////////////
  3220. else if ($Type == 5) {
  3221. $MarkCoverage = $subtable_offset + $this->read_ushort();
  3222. //$MarkCoverage is already set in $LuCoverage 00065|00073 etc
  3223. $LigatureCoverage = $subtable_offset + $this->read_ushort();
  3224. $ClassCount = $this->read_ushort(); // Number of classes defined for marks = Number of mark glyphs in the MarkCoverage table
  3225. $MarkArray = $subtable_offset + $this->read_ushort(); // Offset to MarkArray table
  3226. $LigatureArray = $subtable_offset + $this->read_ushort(); // Offset to LigatureArray table
  3227. $this->seek($LigatureCoverage);
  3228. $LigatureGlyphs = implode('|', $this->_getCoverage());
  3229. $checkpos = $ptr;
  3230. $checkpos--;
  3231. // "To position a combining mark using a MarkToLigature attachment subtable, the text-processing client must work backward from the mark to the preceding ligature glyph."
  3232. while (isset($this->OTLdata[$checkpos]) && strpos($this->GlyphClassMarks, $this->OTLdata[$checkpos]['hex']) !== false) {
  3233. $checkpos--;
  3234. }
  3235. if (isset($this->OTLdata[$checkpos]) && strpos($LigatureGlyphs, $this->OTLdata[$checkpos]['hex']) !== false) {
  3236. $matchedpos = $checkpos;
  3237. } else {
  3238. $matchedpos = false;
  3239. }
  3240. if ($matchedpos !== false) {
  3241. // Get the relevant MarkRecord
  3242. $MarkPos = $LuCoverage[$currGID];
  3243. $MarkRecord = $this->_getMarkRecord($MarkArray, $MarkPos); // e.g. Array ( [Class] => 0 [AnchorX] => -549 [AnchorY] => 1548 )
  3244. //Mark Class is = $MarkRecord['Class']
  3245. // Get the relevant LigatureRecord
  3246. $this->seek($LigatureArray);
  3247. $LigatureCount = $this->read_ushort();
  3248. $LigaturePos = strpos($LigatureGlyphs, $this->OTLdata[$matchedpos]['hex']) / 6;
  3249. // Move to the LigatureAttach table Record we want
  3250. $nSkip = (2 * $LigaturePos);
  3251. $this->skip($nSkip);
  3252. $LigatureAttachOffset = $LigatureArray + $this->read_ushort();
  3253. $this->seek($LigatureAttachOffset);
  3254. $ComponentCount = $this->read_ushort();
  3255. $offsets = array();
  3256. for ($comp = 0; $comp < $ComponentCount; $comp++) {
  3257. // ComponentRecords
  3258. for ($class = 0; $class < $ClassCount; $class++) {
  3259. $offsets[$comp][$class] = $this->read_ushort();
  3260. }
  3261. }
  3262. // Get the specific component for this mark attachment
  3263. if (isset($this->assocLigs[$matchedpos]) && isset($this->assocMarks[$ptr]['ligPos']) && $this->assocMarks[$ptr]['ligPos'] == $matchedpos) {
  3264. $component = $this->assocMarks[$ptr]['compID'];
  3265. } else {
  3266. $component = $ComponentCount - 1;
  3267. }
  3268. $offset = $offsets[$component][$MarkRecord['Class']];
  3269. if ($offset != 0) {
  3270. $LigatureRecordOffset = $offset + $LigatureAttachOffset;
  3271. list($x, $y) = $this->_getAnchorTable($LigatureRecordOffset);
  3272. $LigatureRecord = array('AnchorX' => $x, 'AnchorY' => $y);
  3273. // Need default XAdvance for Ligature glyph
  3274. $LigatureWidth = $this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], $this->OTLdata[$matchedpos]['uni']) * $this->mpdf->CurrentFont['unitsPerEm'] / 1000; // convert back to font design units
  3275. $this->OTLdata[$ptr]['GPOSinfo']['BaseWidth'] = $LigatureWidth;
  3276. // And any intervening (ignored)characters
  3277. if (($ptr - $matchedpos) > 1) {
  3278. for ($i = $matchedpos + 1; $i < $ptr; $i++) {
  3279. $LigatureWidthExtra = $this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], $this->OTLdata[$i]['uni']) * $this->mpdf->CurrentFont['unitsPerEm'] / 1000; // convert back to font design units
  3280. $this->OTLdata[$ptr]['GPOSinfo']['BaseWidth'] += $LigatureWidthExtra;
  3281. }
  3282. }
  3283. // Align to previous Ligature by attachment - so need to add to previous placement values
  3284. if (isset($this->OTLdata[$matchedpos]['GPOSinfo']['XPlacement']))
  3285. $prevXPlacement = $this->OTLdata[$matchedpos]['GPOSinfo']['XPlacement'];
  3286. else {
  3287. $prevXPlacement = 0;
  3288. }
  3289. if (isset($this->OTLdata[$matchedpos]['GPOSinfo']['YPlacement'])) {
  3290. $prevYPlacement = $this->OTLdata[$matchedpos]['GPOSinfo']['YPlacement'];
  3291. } else {
  3292. $prevYPlacement = 0;
  3293. }
  3294. $this->OTLdata[$ptr]['GPOSinfo']['XPlacement'] = $prevXPlacement + $LigatureRecord['AnchorX'] - $MarkRecord['AnchorX'];
  3295. $this->OTLdata[$ptr]['GPOSinfo']['YPlacement'] = $prevYPlacement + $LigatureRecord['AnchorY'] - $MarkRecord['AnchorY'];
  3296. if ($this->debugOTL) {
  3297. $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level);
  3298. }
  3299. return 1;
  3300. }
  3301. }
  3302. return 0;
  3303. }
  3304. ////////////////////////////////////////////////////////////////////////////////
  3305. // LookupType 6: MarkToMark attachment Attach a combining mark to another mark
  3306. ////////////////////////////////////////////////////////////////////////////////
  3307. else if ($Type == 6) {
  3308. $Mark1Coverage = $subtable_offset + $this->read_ushort(); // Combining Mark
  3309. //$Mark1Coverage is already set in $LuCoverage 0065|0073 etc
  3310. $Mark2Coverage = $subtable_offset + $this->read_ushort(); // Base Mark
  3311. $ClassCount = $this->read_ushort(); // Number of classes defined for marks = No. of Combining mark1 glyphs in the MarkCoverage table
  3312. $Mark1Array = $subtable_offset + $this->read_ushort(); // Offset to MarkArray table
  3313. $Mark2Array = $subtable_offset + $this->read_ushort(); // Offset to Mark2Array table
  3314. $this->seek($Mark2Coverage);
  3315. $Mark2Glyphs = implode('|', $this->_getCoverage());
  3316. $checkpos = $ptr;
  3317. $checkpos--;
  3318. while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) {
  3319. $checkpos--;
  3320. }
  3321. if (isset($this->OTLdata[$checkpos]) && strpos($Mark2Glyphs, $this->OTLdata[$checkpos]['hex']) !== false) {
  3322. $matchedpos = $checkpos;
  3323. } else {
  3324. $matchedpos = false;
  3325. }
  3326. if ($matchedpos !== false) {
  3327. // Get the relevant MarkRecord
  3328. $Mark1Pos = $LuCoverage[$currGID];
  3329. $Mark1Record = $this->_getMarkRecord($Mark1Array, $Mark1Pos); // e.g. Array ( [Class] => 0 [AnchorX] => -549 [AnchorY] => 1548 )
  3330. //Mark Class is = $Mark1Record['Class']
  3331. // Get the relevant Mark2Record
  3332. $this->seek($Mark2Array);
  3333. $Mark2Count = $this->read_ushort();
  3334. $Mark2Pos = strpos($Mark2Glyphs, $this->OTLdata[$matchedpos]['hex']) / 6;
  3335. // Move to the Mark2Record we want
  3336. $nSkip = (2 * $Mark2Pos * $ClassCount );
  3337. $this->skip($nSkip);
  3338. // Read Mark2Record we want for appropriate Class
  3339. $nSkip = 2 * $Mark1Record['Class'];
  3340. $this->skip($nSkip);
  3341. $Mark2RecordOffset = $Mark2Array + $this->read_ushort();
  3342. list($x, $y) = $this->_getAnchorTable($Mark2RecordOffset);
  3343. $Mark2Record = array('AnchorX' => $x, 'AnchorY' => $y); // e.g. Array ( [AnchorX] => 660 [AnchorY] => 1556 )
  3344. // Need default XAdvance for Mark2 glyph
  3345. $Mark2Width = $this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], $this->OTLdata[$matchedpos]['uni']) * $this->mpdf->CurrentFont['unitsPerEm'] / 1000; // convert back to font design units
  3346. // IF combining marks are set on different components of a ligature glyph, do not apply this rule
  3347. // Test: arabictypesetting: &#x625;&#x650;&#x644;&#x64e;&#x649;&#x670;&#x653;
  3348. // Test: arabictypesetting: &#x628;&#x651;&#x64e;&#x64a;&#x652;&#x646;&#x64e;&#x643;&#x64f;&#x645;&#x652;
  3349. $prevLig = -1;
  3350. $thisLig = -1;
  3351. $prevComp = -1;
  3352. $thisComp = -1;
  3353. if (isset($this->assocMarks[$matchedpos])) {
  3354. $prevLig = $this->assocMarks[$matchedpos]['ligPos'];
  3355. $prevComp = $this->assocMarks[$matchedpos]['compID'];
  3356. }
  3357. if (isset($this->assocMarks[$ptr])) {
  3358. $thisLig = $this->assocMarks[$ptr]['ligPos'];
  3359. $thisComp = $this->assocMarks[$ptr]['compID'];
  3360. }
  3361. // However IF Mark2 (first in logical order, i.e. being attached to) is not associated with a base, carry on
  3362. // This happens in Indic when the Mark being attached to e.g. [Halant Ma lig] -> MatraU, [U+0B4D + U+B2E as E0F5]-> U+0B41 become E135
  3363. if (!defined("OMIT_OTL_FIX_1") || OMIT_OTL_FIX_1 != 1) {
  3364. /* OTL_FIX_1 */
  3365. if (isset($this->assocMarks[$matchedpos]) && ($prevLig != $thisLig || $prevComp != $thisComp )) {
  3366. return 0;
  3367. }
  3368. } else {
  3369. /* Original code */
  3370. if ($prevLig != $thisLig || $prevComp != $thisComp) {
  3371. return 0;
  3372. }
  3373. }
  3374. if (!defined("OMIT_OTL_FIX_2") || OMIT_OTL_FIX_2 != 1) {
  3375. /* OTL_FIX_2 */
  3376. if (!isset($this->OTLdata[$matchedpos]['GPOSinfo']['BaseWidth']) || !$this->OTLdata[$matchedpos]['GPOSinfo']['BaseWidth']) {
  3377. $this->OTLdata[$ptr]['GPOSinfo']['BaseWidth'] = $Mark2Width;
  3378. }
  3379. }
  3380. // ZZZ99Q - Test Case font-family: garuda &#xe19;&#xe49;&#xe33;
  3381. if (isset($this->OTLdata[$matchedpos]['GPOSinfo']['BaseWidth']) && $this->OTLdata[$matchedpos]['GPOSinfo']['BaseWidth']) {
  3382. $this->OTLdata[$ptr]['GPOSinfo']['BaseWidth'] = $this->OTLdata[$matchedpos]['GPOSinfo']['BaseWidth'];
  3383. }
  3384. // Align to previous Mark by attachment - so need to add the previous placement values
  3385. $prevXPlacement = (isset($this->OTLdata[$matchedpos]['GPOSinfo']['XPlacement']) ? $this->OTLdata[$matchedpos]['GPOSinfo']['XPlacement'] : 0);
  3386. $prevYPlacement = (isset($this->OTLdata[$matchedpos]['GPOSinfo']['YPlacement']) ? $this->OTLdata[$matchedpos]['GPOSinfo']['YPlacement'] : 0);
  3387. $this->OTLdata[$ptr]['GPOSinfo']['XPlacement'] = $prevXPlacement + $Mark2Record['AnchorX'] - $Mark1Record['AnchorX'];
  3388. $this->OTLdata[$ptr]['GPOSinfo']['YPlacement'] = $prevYPlacement + $Mark2Record['AnchorY'] - $Mark1Record['AnchorY'];
  3389. if ($this->debugOTL) {
  3390. $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level);
  3391. }
  3392. return 1;
  3393. }
  3394. return 0;
  3395. }
  3396. ////////////////////////////////////////////////////////////////////////////////
  3397. // LookupType 7: Context positioning Position one or more glyphs in context
  3398. ////////////////////////////////////////////////////////////////////////////////
  3399. else if ($Type == 7) {
  3400. //===========
  3401. // Format 1:
  3402. //===========
  3403. if ($PosFormat == 1) {
  3404. throw new MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not TESTED YET.");
  3405. }
  3406. //===========
  3407. // Format 2:
  3408. //===========
  3409. else if ($PosFormat == 2) {
  3410. $CoverageTableOffset = $subtable_offset + $this->read_ushort();
  3411. $InputClassDefOffset = $subtable_offset + $this->read_ushort();
  3412. $PosClassSetCnt = $this->read_ushort();
  3413. $PosClassSetOffset = array();
  3414. for ($b = 0; $b < $PosClassSetCnt; $b++) {
  3415. $offset = $this->read_ushort();
  3416. if ($offset == 0x0000) {
  3417. $PosClassSetOffset[] = $offset;
  3418. } else {
  3419. $PosClassSetOffset[] = $subtable_offset + $offset;
  3420. }
  3421. }
  3422. $InputClasses = $this->_getClasses($InputClassDefOffset);
  3423. for ($s = 0; $s < $PosClassSetCnt; $s++) { // $ChainPosClassSet is ordered by input class-may be NULL
  3424. // Select $PosClassSet if currGlyph is in First Input Class
  3425. if ($PosClassSetOffset[$s] > 0 && isset($InputClasses[$s][$currGID])) {
  3426. $this->seek($PosClassSetOffset[$s]);
  3427. $PosClassRuleCnt = $this->read_ushort();
  3428. $PosClassRule = array();
  3429. for ($b = 0; $b < $PosClassRuleCnt; $b++) {
  3430. $PosClassRule[$b] = $PosClassSetOffset[$s] + $this->read_ushort();
  3431. }
  3432. for ($b = 0; $b < $PosClassRuleCnt; $b++) { // EACH RULE
  3433. $this->seek($PosClassRule[$b]);
  3434. $InputGlyphCount = $this->read_ushort();
  3435. $PosCount = $this->read_ushort();
  3436. $Input = array();
  3437. for ($r = 1; $r < $InputGlyphCount; $r++) {
  3438. $Input[$r] = $this->read_ushort();
  3439. }
  3440. $inputClass = $s;
  3441. $inputGlyphs = array();
  3442. $inputGlyphs[0] = $InputClasses[$inputClass];
  3443. if ($InputGlyphCount > 1) {
  3444. // NB starts at 1
  3445. for ($gcl = 1; $gcl < $InputGlyphCount; $gcl++) {
  3446. $classindex = $Input[$gcl];
  3447. if (isset($InputClasses[$classindex])) {
  3448. $inputGlyphs[$gcl] = $InputClasses[$classindex];
  3449. } else {
  3450. $inputGlyphs[$gcl] = '';
  3451. }
  3452. }
  3453. }
  3454. // Class 0 contains all the glyphs NOT in the other classes
  3455. $class0excl = array();
  3456. for ($gc = 1; $gc <= count($InputClasses); $gc++) {
  3457. if (is_array($InputClasses[$gc]))
  3458. $class0excl = $class0excl + $InputClasses[$gc];
  3459. }
  3460. $backtrackGlyphs = array();
  3461. $lookaheadGlyphs = array();
  3462. $matched = $this->checkContextMatchMultipleUni($inputGlyphs, $backtrackGlyphs, $lookaheadGlyphs, $ignore, $ptr, $class0excl);
  3463. if ($matched) {
  3464. for ($p = 0; $p < $PosCount; $p++) { // EACH LOOKUP
  3465. $SequenceIndex[$p] = $this->read_ushort();
  3466. $LookupListIndex[$p] = $this->read_ushort();
  3467. }
  3468. for ($p = 0; $p < $PosCount; $p++) {
  3469. // Apply $LookupListIndex at $SequenceIndex
  3470. if ($SequenceIndex[$p] >= $InputGlyphCount) {
  3471. continue;
  3472. }
  3473. $lu = $LookupListIndex[$p];
  3474. $luType = $this->GPOSLookups[$lu]['Type'];
  3475. $luFlag = $this->GPOSLookups[$lu]['Flag'];
  3476. $luMarkFilteringSet = $this->GPOSLookups[$lu]['MarkFilteringSet'];
  3477. $luptr = $matched[$SequenceIndex[$p]];
  3478. $lucurrGlyph = $this->OTLdata[$luptr]['hex'];
  3479. $lucurrGID = $this->OTLdata[$luptr]['uni'];
  3480. foreach ($this->GPOSLookups[$lu]['Subtables'] AS $luc => $lusubtable_offset) {
  3481. $shift = $this->_applyGPOSsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GPOS_offset + $this->GSUB_length), $luType, $luFlag, $luMarkFilteringSet, $this->LuCoverage[$lu][$luc], $tag, 1, $is_old_spec);
  3482. if ($this->debugOTL && $shift) {
  3483. $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level);
  3484. }
  3485. if ($shift) {
  3486. break;
  3487. }
  3488. }
  3489. }
  3490. if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) {
  3491. return $shift;
  3492. } /* OTL_FIX_3 */
  3493. else
  3494. return $InputGlyphCount; // should be + matched ignores in Input Sequence
  3495. }
  3496. }
  3497. }
  3498. }
  3499. return 0;
  3500. }
  3501. //===========
  3502. // Format 3:
  3503. //===========
  3504. else if ($PosFormat == 3) {
  3505. throw new MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not TESTED YET.");
  3506. } else {
  3507. throw new MpdfException("GPOS Lookup Type " . $Type . ", Format " . $PosFormat . " not supported.");
  3508. }
  3509. }
  3510. ////////////////////////////////////////////////////////////////////////////////
  3511. // LookupType 8: Chained Context positioning Position one or more glyphs in chained context
  3512. ////////////////////////////////////////////////////////////////////////////////
  3513. else if ($Type == 8) {
  3514. //===========
  3515. // Format 1:
  3516. //===========
  3517. if ($PosFormat == 1) {
  3518. throw new MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not TESTED YET.");
  3519. return 0;
  3520. }
  3521. //===========
  3522. // Format 2:
  3523. //===========
  3524. else if ($PosFormat == 2) {
  3525. $CoverageTableOffset = $subtable_offset + $this->read_ushort();
  3526. $BacktrackClassDefOffset = $subtable_offset + $this->read_ushort();
  3527. $InputClassDefOffset = $subtable_offset + $this->read_ushort();
  3528. $LookaheadClassDefOffset = $subtable_offset + $this->read_ushort();
  3529. $ChainPosClassSetCnt = $this->read_ushort();
  3530. $ChainPosClassSetOffset = array();
  3531. for ($b = 0; $b < $ChainPosClassSetCnt; $b++) {
  3532. $offset = $this->read_ushort();
  3533. if ($offset == 0x0000) {
  3534. $ChainPosClassSetOffset[] = $offset;
  3535. } else {
  3536. $ChainPosClassSetOffset[] = $subtable_offset + $offset;
  3537. }
  3538. }
  3539. $BacktrackClasses = $this->_getClasses($BacktrackClassDefOffset);
  3540. $InputClasses = $this->_getClasses($InputClassDefOffset);
  3541. $LookaheadClasses = $this->_getClasses($LookaheadClassDefOffset);
  3542. for ($s = 0; $s < $ChainPosClassSetCnt; $s++) { // $ChainPosClassSet is ordered by input class-may be NULL
  3543. // Select $ChainPosClassSet if currGlyph is in First Input Class
  3544. if ($ChainPosClassSetOffset[$s] > 0 && isset($InputClasses[$s][$currGID])) {
  3545. $this->seek($ChainPosClassSetOffset[$s]);
  3546. $ChainPosClassRuleCnt = $this->read_ushort();
  3547. $ChainPosClassRule = array();
  3548. for ($b = 0; $b < $ChainPosClassRuleCnt; $b++) {
  3549. $ChainPosClassRule[$b] = $ChainPosClassSetOffset[$s] + $this->read_ushort();
  3550. }
  3551. for ($b = 0; $b < $ChainPosClassRuleCnt; $b++) { // EACH RULE
  3552. $this->seek($ChainPosClassRule[$b]);
  3553. $BacktrackGlyphCount = $this->read_ushort();
  3554. $Backtrack = array();
  3555. for ($r = 0; $r < $BacktrackGlyphCount; $r++) {
  3556. $Backtrack[$r] = $this->read_ushort();
  3557. }
  3558. $InputGlyphCount = $this->read_ushort();
  3559. $Input = array();
  3560. for ($r = 1; $r < $InputGlyphCount; $r++) {
  3561. $Input[$r] = $this->read_ushort();
  3562. }
  3563. $LookaheadGlyphCount = $this->read_ushort();
  3564. $Lookahead = array();
  3565. for ($r = 0; $r < $LookaheadGlyphCount; $r++) {
  3566. $Lookahead[$r] = $this->read_ushort();
  3567. }
  3568. $inputClass = $s; //???
  3569. $inputGlyphs = array();
  3570. $inputGlyphs[0] = $InputClasses[$inputClass];
  3571. if ($InputGlyphCount > 1) {
  3572. // NB starts at 1
  3573. for ($gcl = 1; $gcl < $InputGlyphCount; $gcl++) {
  3574. $classindex = $Input[$gcl];
  3575. if (isset($InputClasses[$classindex])) {
  3576. $inputGlyphs[$gcl] = $InputClasses[$classindex];
  3577. } else {
  3578. $inputGlyphs[$gcl] = '';
  3579. }
  3580. }
  3581. }
  3582. // Class 0 contains all the glyphs NOT in the other classes
  3583. $class0excl = array();
  3584. for ($gc = 1; $gc <= count($InputClasses); $gc++) {
  3585. if (isset($InputClasses[$gc]) && is_array($InputClasses[$gc]))
  3586. $class0excl = $class0excl + $InputClasses[$gc];
  3587. }
  3588. if ($BacktrackGlyphCount) {
  3589. $backtrackGlyphs = array();
  3590. for ($gcl = 0; $gcl < $BacktrackGlyphCount; $gcl++) {
  3591. $classindex = $Backtrack[$gcl];
  3592. if (isset($BacktrackClasses[$classindex])) {
  3593. $backtrackGlyphs[$gcl] = $BacktrackClasses[$classindex];
  3594. } else {
  3595. $backtrackGlyphs[$gcl] = '';
  3596. }
  3597. }
  3598. } else {
  3599. $backtrackGlyphs = array();
  3600. }
  3601. // Class 0 contains all the glyphs NOT in the other classes
  3602. $bclass0excl = array();
  3603. for ($gc = 1; $gc <= count($BacktrackClasses); $gc++) {
  3604. if (isset($BacktrackClasses[$gc]) && is_array($BacktrackClasses[$gc]))
  3605. $bclass0excl = $bclass0excl + $BacktrackClasses[$gc];
  3606. }
  3607. if ($LookaheadGlyphCount) {
  3608. $lookaheadGlyphs = array();
  3609. for ($gcl = 0; $gcl < $LookaheadGlyphCount; $gcl++) {
  3610. $classindex = $Lookahead[$gcl];
  3611. if (isset($LookaheadClasses[$classindex])) {
  3612. $lookaheadGlyphs[$gcl] = $LookaheadClasses[$classindex];
  3613. } else {
  3614. $lookaheadGlyphs[$gcl] = '';
  3615. }
  3616. }
  3617. } else {
  3618. $lookaheadGlyphs = array();
  3619. }
  3620. // Class 0 contains all the glyphs NOT in the other classes
  3621. $lclass0excl = array();
  3622. for ($gc = 1; $gc <= count($LookaheadClasses); $gc++) {
  3623. if (isset($LookaheadClasses[$gc]) && is_array($LookaheadClasses[$gc]))
  3624. $lclass0excl = $lclass0excl + $LookaheadClasses[$gc];
  3625. }
  3626. $matched = $this->checkContextMatchMultipleUni($inputGlyphs, $backtrackGlyphs, $lookaheadGlyphs, $ignore, $ptr, $class0excl, $bclass0excl, $lclass0excl);
  3627. if ($matched) {
  3628. $PosCount = $this->read_ushort();
  3629. $SequenceIndex = array();
  3630. $LookupListIndex = array();
  3631. for ($p = 0; $p < $PosCount; $p++) { // EACH LOOKUP
  3632. $SequenceIndex[$p] = $this->read_ushort();
  3633. $LookupListIndex[$p] = $this->read_ushort();
  3634. }
  3635. for ($p = 0; $p < $PosCount; $p++) {
  3636. // Apply $LookupListIndex at $SequenceIndex
  3637. if ($SequenceIndex[$p] >= $InputGlyphCount) {
  3638. continue;
  3639. }
  3640. $lu = $LookupListIndex[$p];
  3641. $luType = $this->GPOSLookups[$lu]['Type'];
  3642. $luFlag = $this->GPOSLookups[$lu]['Flag'];
  3643. $luMarkFilteringSet = $this->GPOSLookups[$lu]['MarkFilteringSet'];
  3644. $luptr = $matched[$SequenceIndex[$p]];
  3645. $lucurrGlyph = $this->OTLdata[$luptr]['hex'];
  3646. $lucurrGID = $this->OTLdata[$luptr]['uni'];
  3647. foreach ($this->GPOSLookups[$lu]['Subtables'] AS $luc => $lusubtable_offset) {
  3648. $shift = $this->_applyGPOSsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GPOS_offset + $this->GSUB_length), $luType, $luFlag, $luMarkFilteringSet, $this->LuCoverage[$lu][$luc], $tag, 1, $is_old_spec);
  3649. if ($this->debugOTL && $shift) {
  3650. $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level);
  3651. }
  3652. if ($shift) {
  3653. break;
  3654. }
  3655. }
  3656. }
  3657. if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) {
  3658. return $shift;
  3659. } /* OTL_FIX_3 */
  3660. else
  3661. return $InputGlyphCount; // should be + matched ignores in Input Sequence
  3662. }
  3663. }
  3664. }
  3665. }
  3666. return 0;
  3667. }
  3668. //===========
  3669. // Format 3:
  3670. //===========
  3671. else if ($PosFormat == 3) {
  3672. $BacktrackGlyphCount = $this->read_ushort();
  3673. for ($b = 0; $b < $BacktrackGlyphCount; $b++) {
  3674. $CoverageBacktrackOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order
  3675. }
  3676. $InputGlyphCount = $this->read_ushort();
  3677. for ($b = 0; $b < $InputGlyphCount; $b++) {
  3678. $CoverageInputOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order
  3679. }
  3680. $LookaheadGlyphCount = $this->read_ushort();
  3681. for ($b = 0; $b < $LookaheadGlyphCount; $b++) {
  3682. $CoverageLookaheadOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order
  3683. }
  3684. $PosCount = $this->read_ushort();
  3685. $save_pos = $this->_pos; // Save the point just after PosCount
  3686. $CoverageBacktrackGlyphs = array();
  3687. for ($b = 0; $b < $BacktrackGlyphCount; $b++) {
  3688. $this->seek($CoverageBacktrackOffset[$b]);
  3689. $glyphs = $this->_getCoverage();
  3690. $CoverageBacktrackGlyphs[$b] = implode("|", $glyphs);
  3691. }
  3692. $CoverageInputGlyphs = array();
  3693. for ($b = 0; $b < $InputGlyphCount; $b++) {
  3694. $this->seek($CoverageInputOffset[$b]);
  3695. $glyphs = $this->_getCoverage();
  3696. $CoverageInputGlyphs[$b] = implode("|", $glyphs);
  3697. }
  3698. $CoverageLookaheadGlyphs = array();
  3699. for ($b = 0; $b < $LookaheadGlyphCount; $b++) {
  3700. $this->seek($CoverageLookaheadOffset[$b]);
  3701. $glyphs = $this->_getCoverage();
  3702. $CoverageLookaheadGlyphs[$b] = implode("|", $glyphs);
  3703. }
  3704. $matched = $this->checkContextMatchMultiple($CoverageInputGlyphs, $CoverageBacktrackGlyphs, $CoverageLookaheadGlyphs, $ignore, $ptr);
  3705. if ($matched) {
  3706. $this->seek($save_pos); // Return to just after PosCount
  3707. for ($p = 0; $p < $PosCount; $p++) {
  3708. // PosLookupRecord
  3709. $PosLookupRecord[$p]['SequenceIndex'] = $this->read_ushort();
  3710. $PosLookupRecord[$p]['LookupListIndex'] = $this->read_ushort();
  3711. }
  3712. for ($p = 0; $p < $PosCount; $p++) {
  3713. // Apply $PosLookupRecord[$p]['LookupListIndex'] at $PosLookupRecord[$p]['SequenceIndex']
  3714. if ($PosLookupRecord[$p]['SequenceIndex'] >= $InputGlyphCount) {
  3715. continue;
  3716. }
  3717. $lu = $PosLookupRecord[$p]['LookupListIndex'];
  3718. $luType = $this->GPOSLookups[$lu]['Type'];
  3719. $luFlag = $this->GPOSLookups[$lu]['Flag'];
  3720. if (isset($this->GPOSLookups[$lu]['MarkFilteringSet'])) {
  3721. $luMarkFilteringSet = $this->GPOSLookups[$lu]['MarkFilteringSet'];
  3722. } else {
  3723. $luMarkFilteringSet = '';
  3724. }
  3725. $luptr = $matched[$PosLookupRecord[$p]['SequenceIndex']];
  3726. $lucurrGlyph = $this->OTLdata[$luptr]['hex'];
  3727. $lucurrGID = $this->OTLdata[$luptr]['uni'];
  3728. foreach ($this->GPOSLookups[$lu]['Subtables'] AS $luc => $lusubtable_offset) {
  3729. $shift = $this->_applyGPOSsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GPOS_offset + $this->GSUB_length), $luType, $luFlag, $luMarkFilteringSet, $this->LuCoverage[$lu][$luc], $tag, 1, $is_old_spec);
  3730. if ($this->debugOTL && $shift) {
  3731. $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level);
  3732. }
  3733. if ($shift) {
  3734. break;
  3735. }
  3736. }
  3737. }
  3738. }
  3739. } else {
  3740. throw new MpdfException("GPOS Lookup Type " . $Type . ", Format " . $PosFormat . " not supported.");
  3741. }
  3742. } else {
  3743. throw new MpdfException("GPOS Lookup Type " . $Type . " not supported.");
  3744. }
  3745. }
  3746. //////////////////////////////////////////////////////////////////////////////////
  3747. //////////////////////////////////////////////////////////////////////////////////
  3748. // GPOS / GSUB / GCOM (common) functions
  3749. //////////////////////////////////////////////////////////////////////////////////
  3750. //////////////////////////////////////////////////////////////////////////////////
  3751. function checkContextMatch($Input, $Backtrack, $Lookahead, $ignore, $ptr)
  3752. {
  3753. // Input etc are single numbers - GSUB Format 6.1
  3754. // Input starts with (1=>xxx)
  3755. // return false if no match, else an array of ptr for matches (0=>0, 1=>3,...)
  3756. $current_syllable = (isset($this->OTLdata[$ptr]['syllable']) ? $this->OTLdata[$ptr]['syllable'] : 0);
  3757. // BACKTRACK
  3758. $checkpos = $ptr;
  3759. for ($i = 0; $i < count($Backtrack); $i++) {
  3760. $checkpos--;
  3761. while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) {
  3762. $checkpos--;
  3763. }
  3764. // If outside scope of current syllable - return no match
  3765. if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) {
  3766. return false;
  3767. } else if (!isset($this->OTLdata[$checkpos]) || $this->OTLdata[$checkpos]['uni'] != $Backtrack[$i]) {
  3768. return false;
  3769. }
  3770. }
  3771. // INPUT
  3772. $matched = array(0 => $ptr);
  3773. $checkpos = $ptr;
  3774. for ($i = 1; $i < count($Input); $i++) {
  3775. $checkpos++;
  3776. while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) {
  3777. $checkpos++;
  3778. }
  3779. // If outside scope of current syllable - return no match
  3780. if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) {
  3781. return false;
  3782. } else if (isset($this->OTLdata[$checkpos]) && $this->OTLdata[$checkpos]['uni'] == $Input[$i]) {
  3783. $matched[] = $checkpos;
  3784. } else {
  3785. return false;
  3786. }
  3787. }
  3788. // LOOKAHEAD
  3789. for ($i = 0; $i < count($Lookahead); $i++) {
  3790. $checkpos++;
  3791. while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) {
  3792. $checkpos++;
  3793. }
  3794. // If outside scope of current syllable - return no match
  3795. if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) {
  3796. return false;
  3797. } else if (!isset($this->OTLdata[$checkpos]) || $this->OTLdata[$checkpos]['uni'] != $Lookahead[$i]) {
  3798. return false;
  3799. }
  3800. }
  3801. return $matched;
  3802. }
  3803. function checkContextMatchMultiple($Input, $Backtrack, $Lookahead, $ignore, $ptr, $class0excl = '', $bclass0excl = '', $lclass0excl = '')
  3804. {
  3805. // Input etc are string/array of glyph strings - GSUB Format 5.2, 5.3, 6.2, 6.3, GPOS Format 7.2, 7.3, 8.2, 8.3
  3806. // Input starts with (1=>xxx)
  3807. // return false if no match, else an array of ptr for matches (0=>0, 1=>3,...)
  3808. // $class0excl is the string of glyphs in all classes except Class 0 (GSUB 5.2, 6.2, GPOS 7.2, 8.2)
  3809. // $bclass0excl & $lclass0excl are the same for lookahead and backtrack (GSUB 6.2, GPOS 8.2)
  3810. $current_syllable = (isset($this->OTLdata[$ptr]['syllable']) ? $this->OTLdata[$ptr]['syllable'] : 0);
  3811. // BACKTRACK
  3812. $checkpos = $ptr;
  3813. for ($i = 0; $i < count($Backtrack); $i++) {
  3814. $checkpos--;
  3815. while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) {
  3816. $checkpos--;
  3817. }
  3818. // If outside scope of current syllable - return no match
  3819. if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) {
  3820. return false;
  3821. }
  3822. // If Class 0 specified, matches anything NOT in $bclass0excl
  3823. else if (!$Backtrack[$i] && isset($this->OTLdata[$checkpos]) && strpos($bclass0excl, $this->OTLdata[$checkpos]['hex']) !== false) {
  3824. return false;
  3825. } else if (!isset($this->OTLdata[$checkpos]) || strpos($Backtrack[$i], $this->OTLdata[$checkpos]['hex']) === false) {
  3826. return false;
  3827. }
  3828. }
  3829. // INPUT
  3830. $matched = array(0 => $ptr);
  3831. $checkpos = $ptr;
  3832. for ($i = 1; $i < count($Input); $i++) { // Start at 1 - already matched the first InputGlyph
  3833. $checkpos++;
  3834. while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) {
  3835. $checkpos++;
  3836. }
  3837. // If outside scope of current syllable - return no match
  3838. if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) {
  3839. return false;
  3840. }
  3841. // If Input Class 0 specified, matches anything NOT in $class0excl
  3842. else if (!$Input[$i] && isset($this->OTLdata[$checkpos]) && strpos($class0excl, $this->OTLdata[$checkpos]['hex']) === false) {
  3843. $matched[] = $checkpos;
  3844. } else if (isset($this->OTLdata[$checkpos]) && strpos($Input[$i], $this->OTLdata[$checkpos]['hex']) !== false) {
  3845. $matched[] = $checkpos;
  3846. } else {
  3847. return false;
  3848. }
  3849. }
  3850. // LOOKAHEAD
  3851. for ($i = 0; $i < count($Lookahead); $i++) {
  3852. $checkpos++;
  3853. while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) {
  3854. $checkpos++;
  3855. }
  3856. // If outside scope of current syllable - return no match
  3857. if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) {
  3858. return false;
  3859. }
  3860. // If Class 0 specified, matches anything NOT in $lclass0excl
  3861. else if (!$Lookahead[$i] && isset($this->OTLdata[$checkpos]) && strpos($lclass0excl, $this->OTLdata[$checkpos]['hex']) !== false) {
  3862. return false;
  3863. } else if (!isset($this->OTLdata[$checkpos]) || strpos($Lookahead[$i], $this->OTLdata[$checkpos]['hex']) === false) {
  3864. return false;
  3865. }
  3866. }
  3867. return $matched;
  3868. }
  3869. function checkContextMatchMultipleUni($Input, $Backtrack, $Lookahead, $ignore, $ptr, $class0excl = array(), $bclass0excl = array(), $lclass0excl = array())
  3870. {
  3871. // Input etc are array of glyphs - GSUB Format 5.2, 5.3, 6.2, 6.3, GPOS Format 7.2, 7.3, 8.2, 8.3
  3872. // Input starts with (1=>xxx)
  3873. // return false if no match, else an array of ptr for matches (0=>0, 1=>3,...)
  3874. // $class0excl is array of glyphs in all classes except Class 0 (GSUB 5.2, 6.2, GPOS 7.2, 8.2)
  3875. // $bclass0excl & $lclass0excl are the same for lookahead and backtrack (GSUB 6.2, GPOS 8.2)
  3876. $current_syllable = (isset($this->OTLdata[$ptr]['syllable']) ? $this->OTLdata[$ptr]['syllable'] : 0);
  3877. // BACKTRACK
  3878. $checkpos = $ptr;
  3879. for ($i = 0; $i < count($Backtrack); $i++) {
  3880. $checkpos--;
  3881. while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) {
  3882. $checkpos--;
  3883. }
  3884. // If outside scope of current syllable - return no match
  3885. if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) {
  3886. return false;
  3887. }
  3888. // If Class 0 specified, matches anything NOT in $bclass0excl
  3889. else if (!$Backtrack[$i] && isset($this->OTLdata[$checkpos]) && isset($bclass0excl[$this->OTLdata[$checkpos]['uni']])) {
  3890. return false;
  3891. } else if (!isset($this->OTLdata[$checkpos]) || !isset($Backtrack[$i][$this->OTLdata[$checkpos]['uni']])) {
  3892. return false;
  3893. }
  3894. }
  3895. // INPUT
  3896. $matched = array(0 => $ptr);
  3897. $checkpos = $ptr;
  3898. for ($i = 1; $i < count($Input); $i++) { // Start at 1 - already matched the first InputGlyph
  3899. $checkpos++;
  3900. while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) {
  3901. $checkpos++;
  3902. }
  3903. // If outside scope of current syllable - return no match
  3904. if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) {
  3905. return false;
  3906. }
  3907. // If Input Class 0 specified, matches anything NOT in $class0excl
  3908. else if (!$Input[$i] && isset($this->OTLdata[$checkpos]) && !isset($class0excl[$this->OTLdata[$checkpos]['uni']])) {
  3909. $matched[] = $checkpos;
  3910. } else if (isset($this->OTLdata[$checkpos]) && isset($Input[$i][$this->OTLdata[$checkpos]['uni']])) {
  3911. $matched[] = $checkpos;
  3912. } else {
  3913. return false;
  3914. }
  3915. }
  3916. // LOOKAHEAD
  3917. for ($i = 0; $i < count($Lookahead); $i++) {
  3918. $checkpos++;
  3919. while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) {
  3920. $checkpos++;
  3921. }
  3922. // If outside scope of current syllable - return no match
  3923. if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) {
  3924. return false;
  3925. }
  3926. // If Class 0 specified, matches anything NOT in $lclass0excl
  3927. else if (!$Lookahead[$i] && isset($this->OTLdata[$checkpos]) && isset($lclass0excl[$this->OTLdata[$checkpos]['uni']])) {
  3928. return false;
  3929. } else if (!isset($this->OTLdata[$checkpos]) || !isset($Lookahead[$i][$this->OTLdata[$checkpos]['uni']])) {
  3930. return false;
  3931. }
  3932. }
  3933. return $matched;
  3934. }
  3935. function _getClassDefinitionTable($offset)
  3936. {
  3937. if (isset($this->LuDataCache[$this->fontkey][$offset])) {
  3938. $GlyphByClass = $this->LuDataCache[$this->fontkey][$offset];
  3939. } else {
  3940. $this->seek($offset);
  3941. $ClassFormat = $this->read_ushort();
  3942. $GlyphClass = array();
  3943. // $GlyphByClass = array(0=>array()); // NB This forces an index[0]
  3944. if ($ClassFormat == 1) {
  3945. $StartGlyph = $this->read_ushort();
  3946. $GlyphCount = $this->read_ushort();
  3947. for ($i = 0; $i < $GlyphCount; $i++) {
  3948. $GlyphClass[$i]['startGlyphID'] = $StartGlyph + $i;
  3949. $GlyphClass[$i]['endGlyphID'] = $StartGlyph + $i;
  3950. $GlyphClass[$i]['class'] = $this->read_ushort();
  3951. for ($g = $GlyphClass[$i]['startGlyphID']; $g <= $GlyphClass[$i]['endGlyphID']; $g++) {
  3952. $GlyphByClass[$GlyphClass[$i]['class']][] = $this->glyphToChar($g);
  3953. }
  3954. }
  3955. } else if ($ClassFormat == 2) {
  3956. $tableCount = $this->read_ushort();
  3957. for ($i = 0; $i < $tableCount; $i++) {
  3958. $GlyphClass[$i]['startGlyphID'] = $this->read_ushort();
  3959. $GlyphClass[$i]['endGlyphID'] = $this->read_ushort();
  3960. $GlyphClass[$i]['class'] = $this->read_ushort();
  3961. for ($g = $GlyphClass[$i]['startGlyphID']; $g <= $GlyphClass[$i]['endGlyphID']; $g++) {
  3962. $GlyphByClass[$GlyphClass[$i]['class']][] = $this->glyphToChar($g);
  3963. }
  3964. }
  3965. }
  3966. ksort($GlyphByClass);
  3967. $this->LuDataCache[$this->fontkey][$offset] = $GlyphByClass;
  3968. }
  3969. return $GlyphByClass;
  3970. }
  3971. function count_bits($n)
  3972. {
  3973. for ($c = 0; $n; $c++) {
  3974. $n &= $n - 1; // clear the least significant bit set
  3975. }
  3976. return $c;
  3977. }
  3978. function _getValueRecord($ValueFormat)
  3979. { // Common ValueRecord for GPOS
  3980. // Only returns 3 possible: $vra['XPlacement'] $vra['YPlacement'] $vra['XAdvance']
  3981. $vra = array();
  3982. // Horizontal adjustment for placement - in design units
  3983. if (($ValueFormat & 0x0001) == 0x0001) {
  3984. $vra['XPlacement'] = $this->read_short();
  3985. }
  3986. // Vertical adjustment for placement - in design units
  3987. if (($ValueFormat & 0x0002) == 0x0002) {
  3988. $vra['YPlacement'] = $this->read_short();
  3989. }
  3990. // Horizontal adjustment for advance - in design units (only used for horizontal writing)
  3991. if (($ValueFormat & 0x0004) == 0x0004) {
  3992. $vra['XAdvance'] = $this->read_short();
  3993. }
  3994. // Vertical adjustment for advance - in design units (only used for vertical writing)
  3995. if (($ValueFormat & 0x0008) == 0x0008) {
  3996. $this->read_short();
  3997. }
  3998. // Offset to Device table for horizontal placement-measured from beginning of PosTable (may be NULL)
  3999. if (($ValueFormat & 0x0010) == 0x0010) {
  4000. $this->read_ushort();
  4001. }
  4002. // Offset to Device table for vertical placement-measured from beginning of PosTable (may be NULL)
  4003. if (($ValueFormat & 0x0020) == 0x0020) {
  4004. $this->read_ushort();
  4005. }
  4006. // Offset to Device table for horizontal advance-measured from beginning of PosTable (may be NULL)
  4007. if (($ValueFormat & 0x0040) == 0x0040) {
  4008. $this->read_ushort();
  4009. }
  4010. // Offset to Device table for vertical advance-measured from beginning of PosTable (may be NULL)
  4011. if (($ValueFormat & 0x0080) == 0x0080) {
  4012. $this->read_ushort();
  4013. }
  4014. return $vra;
  4015. }
  4016. function _getAnchorTable($offset = 0)
  4017. {
  4018. if ($offset) {
  4019. $this->seek($offset);
  4020. }
  4021. $AnchorFormat = $this->read_ushort();
  4022. $XCoordinate = $this->read_short();
  4023. $YCoordinate = $this->read_short();
  4024. // Format 2 specifies additional link to contour point; Format 3 additional Device table
  4025. return array($XCoordinate, $YCoordinate);
  4026. }
  4027. function _getMarkRecord($offset, $MarkPos)
  4028. {
  4029. $this->seek($offset);
  4030. $MarkCount = $this->read_ushort();
  4031. $this->skip($MarkPos * 4);
  4032. $Class = $this->read_ushort();
  4033. $MarkAnchor = $offset + $this->read_ushort(); // = Offset to anchor table
  4034. list($x, $y) = $this->_getAnchorTable($MarkAnchor);
  4035. $MarkRecord = array('Class' => $Class, 'AnchorX' => $x, 'AnchorY' => $y);
  4036. return $MarkRecord;
  4037. }
  4038. function _getGCOMignoreString($flag, $MarkFilteringSet)
  4039. {
  4040. // If ignoreFlag set, combine all ignore glyphs into -> "(?:( 0FBA1| 0FBA2| 0FBA3)*)"
  4041. // else "()"
  4042. // for Input - set on secondary Lookup table if in Context, and set Backtrack and Lookahead on Context Lookup
  4043. $str = "";
  4044. $ignoreflag = 0;
  4045. // Flag & 0xFF?? = MarkAttachmentType
  4046. if ($flag & 0xFF00) {
  4047. // "a lookup must ignore any mark glyphs that are not in the specified mark attachment class"
  4048. // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table
  4049. $MarkAttachmentType = $flag >> 8;
  4050. $ignoreflag = $flag;
  4051. $str = $this->MarkAttachmentType[$MarkAttachmentType];
  4052. }
  4053. // Flag & 0x0010 = UseMarkFilteringSet
  4054. if ($flag & 0x0010) {
  4055. throw new MpdfException("This font [" . $this->fontkey . "] contains MarkGlyphSets - Not tested yet");
  4056. // Change also in ttfontsuni.php
  4057. if ($MarkFilteringSet == '')
  4058. throw new MpdfException("This font [" . $this->fontkey . "] contains MarkGlyphSets - but MarkFilteringSet not set");
  4059. $str = $this->MarkGlyphSets[$MarkFilteringSet];
  4060. }
  4061. // If Ignore Marks set, supercedes any above
  4062. // Flag & 0x0008 = Ignore Marks - (unless already done with MarkAttachmentType)
  4063. if (($flag & 0x0008) == 0x0008 && ($flag & 0xFF00) == 0) {
  4064. $ignoreflag = 8;
  4065. $str = $this->GlyphClassMarks;
  4066. }
  4067. // Flag & 0x0004 = Ignore Ligatures
  4068. if (($flag & 0x0004) == 0x0004) {
  4069. $ignoreflag += 4;
  4070. if ($str) {
  4071. $str .= "|";
  4072. }
  4073. $str .= $this->GlyphClassLigatures;
  4074. }
  4075. // Flag & 0x0002 = Ignore BaseGlyphs
  4076. if (($flag & 0x0002) == 0x0002) {
  4077. $ignoreflag += 2;
  4078. if ($str) {
  4079. $str .= "|";
  4080. }
  4081. $str .= $this->GlyphClassBases;
  4082. }
  4083. if ($str) {
  4084. return "((?:(?:" . $str . "))*)";
  4085. } else
  4086. return "()";
  4087. }
  4088. function _checkGCOMignore($flag, $glyph, $MarkFilteringSet)
  4089. {
  4090. $ignore = false;
  4091. // Flag & 0x0008 = Ignore Marks - (unless already done with MarkAttachmentType)
  4092. if (($flag & 0x0008 && ($flag & 0xFF00) == 0) && strpos($this->GlyphClassMarks, $glyph)) {
  4093. $ignore = true;
  4094. }
  4095. if (($flag & 0x0004) && strpos($this->GlyphClassLigatures, $glyph)) {
  4096. $ignore = true;
  4097. }
  4098. if (($flag & 0x0002) && strpos($this->GlyphClassBases, $glyph)) {
  4099. $ignore = true;
  4100. }
  4101. // Flag & 0xFF?? = MarkAttachmentType
  4102. if ($flag & 0xFF00) {
  4103. // "a lookup must ignore any mark glyphs that are not in the specified mark attachment class"
  4104. // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table
  4105. if (strpos($this->MarkAttachmentType[($flag >> 8)], $glyph)) {
  4106. $ignore = true;
  4107. }
  4108. }
  4109. // Flag & 0x0010 = UseMarkFilteringSet
  4110. if (($flag & 0x0010) && strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) {
  4111. $ignore = true;
  4112. }
  4113. return $ignore;
  4114. }
  4115. ////////////////////////////////////////////////////////////////
  4116. ////////////////////////////////////////////////////////////////
  4117. ////////// BIDI ALGORITHM ////////////////////////
  4118. ////////////////////////////////////////////////////////////////
  4119. ////////////////////////////////////////////////////////////////
  4120. ////////////////////////////////////////////////////////////////
  4121. ////////////////////////////////////////////////////////////////
  4122. // These functions are called from mpdf after GSUB/GPOS has taken place
  4123. // At this stage the bidi-type is in string form
  4124. ////////////////////////////////////////////////////////////////
  4125. ////////////////////////////////////////////////////////////////
  4126. /*
  4127. Bidirectional Character Types
  4128. =============================
  4129. Type Description General Scope
  4130. Strong
  4131. L Left-to-Right LRM, most alphabetic, syllabic, Han ideographs, non-European or non-Arabic digits, ...
  4132. LRE Left-to-Right Embedding LRE
  4133. LRO Left-to-Right Override LRO
  4134. R Right-to-Left RLM, Hebrew alphabet, and related punctuation
  4135. AL Right-to-Left Arabic Arabic, Thaana, and Syriac alphabets, most punctuation specific to those scripts, ...
  4136. RLE Right-to-Left Embedding RLE
  4137. RLO Right-to-Left Override RLO
  4138. Weak
  4139. PDF Pop Directional Format PDF
  4140. EN European Number European digits, Eastern Arabic-Indic digits, ...
  4141. ES European Number Separator Plus sign, minus sign
  4142. ET European Number Terminator Degree sign, currency symbols, ...
  4143. AN Arabic Number Arabic-Indic digits, Arabic decimal and thousands separators, ...
  4144. CS Common Number Separator Colon, comma, full stop (period), No-break space, ...
  4145. NSM Nonspacing Mark Characters marked Mn (Nonspacing_Mark) and Me (Enclosing_Mark) in the Unicode Character Database
  4146. BN Boundary Neutral Default ignorables, non-characters, and control characters, other than those explicitly given other types.
  4147. Neutral
  4148. B Paragraph Separator Paragraph separator, appropriate Newline Functions, higher-level protocol paragraph determination
  4149. S Segment Separator Tab
  4150. WS Whitespace Space, figure space, line separator, form feed, General Punctuation spaces, ...
  4151. ON Other Neutrals All other characters, including OBJECT REPLACEMENT CHARACTER
  4152. */
  4153. function _bidiSort($ta, $str = '', $dir, &$chunkOTLdata, $useGPOS)
  4154. {
  4155. $pel = 0; // paragraph embedding level
  4156. $maxlevel = 0;
  4157. $numchars = count($chunkOTLdata['char_data']);
  4158. // Set the initial paragraph embedding level
  4159. if ($dir == 'rtl') {
  4160. $pel = 1;
  4161. } else {
  4162. $pel = 0;
  4163. }
  4164. // X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral.
  4165. // Current Embedding Level
  4166. $cel = $pel;
  4167. // directional override status (-1 is Neutral)
  4168. $dos = -1;
  4169. $remember = array();
  4170. // Array of characters data
  4171. $chardata = Array();
  4172. // Process each character iteratively, applying rules X2 through X9. Only embedding levels from 0 to 61 are valid in this phase.
  4173. // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
  4174. for ($i = 0; $i < $numchars; ++$i) {
  4175. if ($chunkOTLdata['char_data'][$i]['uni'] == 8235) { // RLE
  4176. // X2. With each RLE, compute the least greater odd embedding level.
  4177. // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
  4178. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  4179. $next_level = $cel + ($cel % 2) + 1;
  4180. if ($next_level < 62) {
  4181. $remember[] = array('num' => 8235, 'cel' => $cel, 'dos' => $dos);
  4182. $cel = $next_level;
  4183. $dos = -1;
  4184. }
  4185. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 8234) { // LRE
  4186. // X3. With each LRE, compute the least greater even embedding level.
  4187. // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
  4188. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  4189. $next_level = $cel + 2 - ($cel % 2);
  4190. if ($next_level < 62) {
  4191. $remember[] = array('num' => 8234, 'cel' => $cel, 'dos' => $dos);
  4192. $cel = $next_level;
  4193. $dos = -1;
  4194. }
  4195. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 8238) { // RLO
  4196. // X4. With each RLO, compute the least greater odd embedding level.
  4197. // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to right-to-left.
  4198. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  4199. $next_level = $cel + ($cel % 2) + 1;
  4200. if ($next_level < 62) {
  4201. $remember[] = array('num' => 8238, 'cel' => $cel, 'dos' => $dos);
  4202. $cel = $next_level;
  4203. $dos = UCDN::BIDI_CLASS_R;
  4204. }
  4205. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 8237) { // LRO
  4206. // X5. With each LRO, compute the least greater even embedding level.
  4207. // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to left-to-right.
  4208. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  4209. $next_level = $cel + 2 - ($cel % 2);
  4210. if ($next_level < 62) {
  4211. $remember[] = array('num' => 8237, 'cel' => $cel, 'dos' => $dos);
  4212. $cel = $next_level;
  4213. $dos = UCDN::BIDI_CLASS_L;
  4214. }
  4215. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 8236) { // PDF
  4216. // X7. With each PDF, determine the matching embedding or override code. If there was a valid matching code, restore (pop) the last remembered (pushed) embedding level and directional override.
  4217. if (count($remember)) {
  4218. $last = count($remember) - 1;
  4219. if (($remember[$last]['num'] == 8235) || ($remember[$last]['num'] == 8234) || ($remember[$last]['num'] == 8238) ||
  4220. ($remember[$last]['num'] == 8237)) {
  4221. $match = array_pop($remember);
  4222. $cel = $match['cel'];
  4223. $dos = $match['dos'];
  4224. }
  4225. }
  4226. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 10) { // NEW LINE
  4227. // Reset to start values
  4228. $cel = $pel;
  4229. $dos = -1;
  4230. $remember = array();
  4231. } else {
  4232. // X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
  4233. // a. Set the level of the current character to the current embedding level.
  4234. // b. When the directional override status is not neutral, reset the current character type to directional override status.
  4235. if ($dos != -1) {
  4236. $chardir = $dos;
  4237. } else {
  4238. $chardir = $chunkOTLdata['char_data'][$i]['bidi_class'];
  4239. }
  4240. // stores string characters and other information
  4241. if (isset($chunkOTLdata['GPOSinfo'][$i])) {
  4242. $gpos = $chunkOTLdata['GPOSinfo'][$i];
  4243. } else
  4244. $gpos = '';
  4245. $chardata[] = array('char' => $chunkOTLdata['char_data'][$i]['uni'], 'level' => $cel, 'type' => $chardir, 'group' => $chunkOTLdata['group']{$i}, 'GPOSinfo' => $gpos);
  4246. }
  4247. }
  4248. $numchars = count($chardata);
  4249. // X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph.
  4250. // Paragraph separators are not included in the embedding.
  4251. // X9. Remove all RLE, LRE, RLO, LRO, and PDF codes.
  4252. // This is effectively done by only saving other codes to chardata
  4253. // X10. Determine the start-of-sequence (sor) and end-of-sequence (eor) types, either L or R, for each isolating run sequence. These depend on the higher of the two levels on either side of the sequence boundary:
  4254. // For sor, compare the level of the first character in the sequence with the level of the character preceding it in the paragraph or if there is none, with the paragraph embedding level.
  4255. // For eor, compare the level of the last character in the sequence with the level of the character following it in the paragraph or if there is none, with the paragraph embedding level.
  4256. // If the higher level is odd, the sor or eor is R; otherwise, it is L.
  4257. $prelevel = $pel;
  4258. $postlevel = $pel;
  4259. $cel = $prelevel; // current embedding level
  4260. for ($i = 0; $i < $numchars; ++$i) {
  4261. $level = $chardata[$i]['level'];
  4262. if ($i == 0) {
  4263. $left = $prelevel;
  4264. } else {
  4265. $left = $chardata[$i - 1]['level'];
  4266. }
  4267. if ($i == ($numchars - 1)) {
  4268. $right = $postlevel;
  4269. } else {
  4270. $right = $chardata[$i + 1]['level'];
  4271. }
  4272. $chardata[$i]['sor'] = max($left, $level) % 2 ? UCDN::BIDI_CLASS_R : UCDN::BIDI_CLASS_L;
  4273. $chardata[$i]['eor'] = max($right, $level) % 2 ? UCDN::BIDI_CLASS_R : UCDN::BIDI_CLASS_L;
  4274. }
  4275. // 3.3.3 Resolving Weak Types
  4276. // Weak types are now resolved one level run at a time. At level run boundaries where the type of the character on the other side of the boundary is required, the type assigned to sor or eor is used.
  4277. // Nonspacing marks are now resolved based on the previous characters.
  4278. // W1. Examine each nonspacing mark (NSM) in the level run, and change the type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor.
  4279. for ($i = 0; $i < $numchars; ++$i) {
  4280. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_NSM) {
  4281. if ($i == 0 || $chardata[$i]['level'] != $chardata[$i - 1]['level']) {
  4282. $chardata[$i]['type'] = $chardata[$i]['sor'];
  4283. } else {
  4284. $chardata[$i]['type'] = $chardata[($i - 1)]['type'];
  4285. }
  4286. }
  4287. }
  4288. // W2. Search backward from each instance of a European number until the first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number.
  4289. $prevlevel = -1;
  4290. $levcount = 0;
  4291. for ($i = 0; $i < $numchars; ++$i) {
  4292. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_EN) {
  4293. $found = false;
  4294. for ($j = $levcount; $j >= 0; $j--) {
  4295. if ($chardata[$j]['type'] == UCDN::BIDI_CLASS_AL) {
  4296. $chardata[$i]['type'] = UCDN::BIDI_CLASS_AN;
  4297. $found = true;
  4298. break;
  4299. } else if (($chardata[$j]['type'] == UCDN::BIDI_CLASS_L) || ($chardata[$j]['type'] == UCDN::BIDI_CLASS_R)) {
  4300. $found = true;
  4301. break;
  4302. }
  4303. }
  4304. }
  4305. if ($chardata[$i]['level'] != $prevlevel) {
  4306. $levcount = 0;
  4307. } else {
  4308. ++$levcount;
  4309. }
  4310. $prevlevel = $chardata[$i]['level'];
  4311. }
  4312. // W3. Change all ALs to R.
  4313. for ($i = 0; $i < $numchars; ++$i) {
  4314. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_AL) {
  4315. $chardata[$i]['type'] = UCDN::BIDI_CLASS_R;
  4316. }
  4317. }
  4318. // W4. A single European separator between two European numbers changes to a European number. A single common separator between two numbers of the same type changes to that type.
  4319. for ($i = 1; $i < $numchars; ++$i) {
  4320. if (($i + 1) < $numchars && $chardata[($i)]['level'] == $chardata[($i + 1)]['level'] && $chardata[($i)]['level'] == $chardata[($i - 1)]['level']) {
  4321. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_ES && $chardata[($i - 1)]['type'] == UCDN::BIDI_CLASS_EN && $chardata[($i + 1)]['type'] == UCDN::BIDI_CLASS_EN) {
  4322. $chardata[$i]['type'] = UCDN::BIDI_CLASS_EN;
  4323. } else if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_CS && $chardata[($i - 1)]['type'] == UCDN::BIDI_CLASS_EN && $chardata[($i + 1)]['type'] == UCDN::BIDI_CLASS_EN) {
  4324. $chardata[$i]['type'] = UCDN::BIDI_CLASS_EN;
  4325. } else if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_CS && $chardata[($i - 1)]['type'] == UCDN::BIDI_CLASS_AN && $chardata[($i + 1)]['type'] == UCDN::BIDI_CLASS_AN) {
  4326. $chardata[$i]['type'] = UCDN::BIDI_CLASS_AN;
  4327. }
  4328. }
  4329. }
  4330. // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
  4331. for ($i = 0; $i < $numchars; ++$i) {
  4332. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_ET) {
  4333. if ($i > 0 && $chardata[($i - 1)]['type'] == UCDN::BIDI_CLASS_EN && $chardata[($i)]['level'] == $chardata[($i - 1)]['level']) {
  4334. $chardata[$i]['type'] = UCDN::BIDI_CLASS_EN;
  4335. } else {
  4336. $j = $i + 1;
  4337. while ($j < $numchars && $chardata[$j]['level'] == $chardata[$i]['level']) {
  4338. if ($chardata[$j]['type'] == UCDN::BIDI_CLASS_EN) {
  4339. $chardata[$i]['type'] = UCDN::BIDI_CLASS_EN;
  4340. break;
  4341. } else if ($chardata[$j]['type'] != UCDN::BIDI_CLASS_ET) {
  4342. break;
  4343. }
  4344. ++$j;
  4345. }
  4346. }
  4347. }
  4348. }
  4349. // W6. Otherwise, separators and terminators change to Other Neutral.
  4350. for ($i = 0; $i < $numchars; ++$i) {
  4351. if (($chardata[$i]['type'] == UCDN::BIDI_CLASS_ET) || ($chardata[$i]['type'] == UCDN::BIDI_CLASS_ES) || ($chardata[$i]['type'] == UCDN::BIDI_CLASS_CS)) {
  4352. $chardata[$i]['type'] = UCDN::BIDI_CLASS_ON;
  4353. }
  4354. }
  4355. //W7. Search backward from each instance of a European number until the first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L.
  4356. for ($i = 0; $i < $numchars; ++$i) {
  4357. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_EN) {
  4358. if ($i == 0) { // Start of Level run
  4359. if ($chardata[$i]['sor'] == UCDN::BIDI_CLASS_L)
  4360. $chardata[$i]['type'] = $chardata[$i]['sor'];
  4361. }
  4362. else {
  4363. for ($j = $i - 1; $j >= 0; $j--) {
  4364. if ($chardata[$j]['level'] != $chardata[$i]['level']) { // Level run boundary
  4365. if ($chardata[$j + 1]['sor'] == UCDN::BIDI_CLASS_L)
  4366. $chardata[$i]['type'] = $chardata[$j + 1]['sor'];
  4367. break;
  4368. }
  4369. else if ($chardata[$j]['type'] == UCDN::BIDI_CLASS_L) {
  4370. $chardata[$i]['type'] = UCDN::BIDI_CLASS_L;
  4371. break;
  4372. } else if ($chardata[$j]['type'] == UCDN::BIDI_CLASS_R) {
  4373. break;
  4374. }
  4375. }
  4376. }
  4377. }
  4378. }
  4379. // N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. European and Arabic numbers act as if they were R in terms of their influence on neutrals. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries.
  4380. for ($i = 0; $i < $numchars; ++$i) {
  4381. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_ON || $chardata[$i]['type'] == UCDN::BIDI_CLASS_WS) {
  4382. $left = -1;
  4383. // LEFT
  4384. if ($i == 0) { // first char
  4385. $left = $chardata[($i)]['sor'];
  4386. } else if ($chardata[($i - 1)]['level'] != $chardata[($i)]['level']) { // run boundary
  4387. $left = $chardata[($i)]['sor'];
  4388. } else if ($chardata[($i - 1)]['type'] == UCDN::BIDI_CLASS_L) {
  4389. $left = UCDN::BIDI_CLASS_L;
  4390. } else if ($chardata[($i - 1)]['type'] == UCDN::BIDI_CLASS_R || $chardata[($i - 1)]['type'] == UCDN::BIDI_CLASS_EN || $chardata[($i - 1)]['type'] == UCDN::BIDI_CLASS_AN) {
  4391. $left = UCDN::BIDI_CLASS_R;
  4392. }
  4393. // RIGHT
  4394. $right = -1;
  4395. $j = $i;
  4396. // move to the right of any following neutrals OR hit a run boundary
  4397. while (($chardata[$j]['type'] == UCDN::BIDI_CLASS_ON || $chardata[$j]['type'] == UCDN::BIDI_CLASS_WS) && $j <= ($numchars - 1)) {
  4398. if ($j == ($numchars - 1)) { // last char
  4399. $right = $chardata[($j)]['eor'];
  4400. break;
  4401. } else if ($chardata[($j + 1)]['level'] != $chardata[($j)]['level']) { // run boundary
  4402. $right = $chardata[($j)]['eor'];
  4403. break;
  4404. } else if ($chardata[($j + 1)]['type'] == UCDN::BIDI_CLASS_L) {
  4405. $right = UCDN::BIDI_CLASS_L;
  4406. break;
  4407. } else if ($chardata[($j + 1)]['type'] == UCDN::BIDI_CLASS_R || $chardata[($j + 1)]['type'] == UCDN::BIDI_CLASS_EN || $chardata[($j + 1)]['type'] == UCDN::BIDI_CLASS_AN) {
  4408. $right = UCDN::BIDI_CLASS_R;
  4409. break;
  4410. }
  4411. $j++;
  4412. }
  4413. if ($left > -1 && $left == $right) {
  4414. $chardata[$i]['orig_type'] = $chardata[$i]['type']; // Need to store the original 'WS' for reference in L1 below
  4415. $chardata[$i]['type'] = $left;
  4416. }
  4417. }
  4418. }
  4419. // N2. Any remaining neutrals take the embedding direction
  4420. for ($i = 0; $i < $numchars; ++$i) {
  4421. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_ON || $chardata[$i]['type'] == UCDN::BIDI_CLASS_WS) {
  4422. $chardata[$i]['type'] = ($chardata[$i]['level'] % 2) ? UCDN::BIDI_CLASS_R : UCDN::BIDI_CLASS_L;
  4423. $chardata[$i]['orig_type'] = $chardata[$i]['type']; // Need to store the original 'WS' for reference in L1 below
  4424. }
  4425. }
  4426. // I1. For all characters with an even (left-to-right) embedding direction, those of type R go up one level and those of type AN or EN go up two levels.
  4427. // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
  4428. for ($i = 0; $i < $numchars; ++$i) {
  4429. $odd = $chardata[$i]['level'] % 2;
  4430. if ($odd) {
  4431. if (($chardata[$i]['type'] == UCDN::BIDI_CLASS_L) || ($chardata[$i]['type'] == UCDN::BIDI_CLASS_AN) || ($chardata[$i]['type'] == UCDN::BIDI_CLASS_EN)) {
  4432. $chardata[$i]['level'] += 1;
  4433. }
  4434. } else {
  4435. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_R) {
  4436. $chardata[$i]['level'] += 1;
  4437. } else if (($chardata[$i]['type'] == UCDN::BIDI_CLASS_AN) || ($chardata[$i]['type'] == UCDN::BIDI_CLASS_EN)) {
  4438. $chardata[$i]['level'] += 2;
  4439. }
  4440. }
  4441. $maxlevel = max($chardata[$i]['level'], $maxlevel);
  4442. }
  4443. // NB
  4444. // Separate into lines at this point************
  4445. //
  4446. // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
  4447. // 1. Segment separators (Tab) 'S',
  4448. // 2. Paragraph separators 'B',
  4449. // 3. Any sequence of whitespace characters 'WS' preceding a segment separator or paragraph separator, and
  4450. // 4. Any sequence of whitespace characters 'WS' at the end of the line.
  4451. // The types of characters used here are the original types, not those modified by the previous phase cf N1 and N2*******
  4452. // Because a Paragraph Separator breaks lines, there will be at most one per line, at the end of that line.
  4453. for ($i = ($numchars - 1); $i > 0; $i--) {
  4454. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_WS || (isset($chardata[$i]['orig_type']) && $chardata[$i]['orig_type'] == UCDN::BIDI_CLASS_WS)) {
  4455. $chardata[$i]['level'] = $pel;
  4456. } else {
  4457. break;
  4458. }
  4459. }
  4460. // L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher.
  4461. for ($j = $maxlevel; $j > 0; $j--) {
  4462. $ordarray = array();
  4463. $revarr = array();
  4464. $onlevel = false;
  4465. for ($i = 0; $i < $numchars; ++$i) {
  4466. if ($chardata[$i]['level'] >= $j) {
  4467. $onlevel = true;
  4468. // L4. A character is depicted by a mirrored glyph if and only if (a) the resolved directionality of that character is R, and (b) the Bidi_Mirrored property value of that character is true.
  4469. if (isset(UCDN::$mirror_pairs[$chardata[$i]['char']]) && $chardata[$i]['type'] == UCDN::BIDI_CLASS_R) {
  4470. $chardata[$i]['char'] = UCDN::$mirror_pairs[$chardata[$i]['char']];
  4471. }
  4472. $revarr[] = $chardata[$i];
  4473. } else {
  4474. if ($onlevel) {
  4475. $revarr = array_reverse($revarr);
  4476. $ordarray = array_merge($ordarray, $revarr);
  4477. $revarr = Array();
  4478. $onlevel = false;
  4479. }
  4480. $ordarray[] = $chardata[$i];
  4481. }
  4482. }
  4483. if ($onlevel) {
  4484. $revarr = array_reverse($revarr);
  4485. $ordarray = array_merge($ordarray, $revarr);
  4486. }
  4487. $chardata = $ordarray;
  4488. }
  4489. $group = '';
  4490. $e = '';
  4491. $GPOS = array();
  4492. $cctr = 0;
  4493. $rtl_content = 0x0;
  4494. foreach ($chardata as $cd) {
  4495. $e.=code2utf($cd['char']);
  4496. $group .= $cd['group'];
  4497. if ($useGPOS && is_array($cd['GPOSinfo'])) {
  4498. $GPOS[$cctr] = $cd['GPOSinfo'];
  4499. $GPOS[$cctr]['wDir'] = ($cd['level'] % 2) ? 'RTL' : 'LTR';
  4500. }
  4501. if ($cd['type'] == UCDN::BIDI_CLASS_L) {
  4502. $rtl_content |= 1;
  4503. } else if ($cd['type'] == UCDN::BIDI_CLASS_R) {
  4504. $rtl_content |= 2;
  4505. }
  4506. $cctr++;
  4507. }
  4508. $chunkOTLdata['group'] = $group;
  4509. if ($useGPOS) {
  4510. $chunkOTLdata['GPOSinfo'] = $GPOS;
  4511. }
  4512. return array($e, $rtl_content);
  4513. }
  4514. // **********************************************************************************************
  4515. // The following versions for BidiSort work on amalgamated chunks to process the whole paragraph
  4516. // Firstly set the level in the OTLdata - called from fn printbuffer() [_bidiPrepare]
  4517. // Secondly re-order - called from fn writeFlowingBlock and FinishFlowingBlock, when already divided into lines. [_bidiReorder]
  4518. // **********************************************************************************************
  4519. function _bidiPrepare(&$para, $dir)
  4520. {
  4521. // Set the initial paragraph embedding level
  4522. $pel = 0; // paragraph embedding level
  4523. if ($dir == 'rtl') {
  4524. $pel = 1;
  4525. }
  4526. // X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral.
  4527. // Current Embedding Level
  4528. $cel = $pel;
  4529. // directional override status (-1 is Neutral)
  4530. $dos = -1;
  4531. $remember = array();
  4532. $controlchars = false;
  4533. $strongrtl = false;
  4534. $diid = 0; // direction isolate ID
  4535. $dictr = 0; // direction isolate counter
  4536. // Process each character iteratively, applying rules X2 through X9. Only embedding levels from 0 to 61 are valid in this phase.
  4537. // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
  4538. $numchunks = count($para);
  4539. for ($nc = 0; $nc < $numchunks; $nc++) {
  4540. $chunkOTLdata = & $para[$nc][18];
  4541. $numchars = count($chunkOTLdata['char_data']);
  4542. for ($i = 0; $i < $numchars; ++$i) {
  4543. if ($chunkOTLdata['char_data'][$i]['uni'] == 8235) { // RLE
  4544. // X2. With each RLE, compute the least greater odd embedding level.
  4545. // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
  4546. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  4547. $next_level = $cel + ($cel % 2) + 1;
  4548. if ($next_level < 62) {
  4549. $remember[] = array('num' => 8235, 'cel' => $cel, 'dos' => $dos);
  4550. $cel = $next_level;
  4551. $dos = -1;
  4552. $controlchars = true;
  4553. }
  4554. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 8234) { // LRE
  4555. // X3. With each LRE, compute the least greater even embedding level.
  4556. // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
  4557. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  4558. $next_level = $cel + 2 - ($cel % 2);
  4559. if ($next_level < 62) {
  4560. $remember[] = array('num' => 8234, 'cel' => $cel, 'dos' => $dos);
  4561. $cel = $next_level;
  4562. $dos = -1;
  4563. $controlchars = true;
  4564. }
  4565. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 8238) { // RLO
  4566. // X4. With each RLO, compute the least greater odd embedding level.
  4567. // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to right-to-left.
  4568. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  4569. $next_level = $cel + ($cel % 2) + 1;
  4570. if ($next_level < 62) {
  4571. $remember[] = array('num' => 8238, 'cel' => $cel, 'dos' => $dos);
  4572. $cel = $next_level;
  4573. $dos = UCDN::BIDI_CLASS_R;
  4574. $controlchars = true;
  4575. }
  4576. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 8237) { // LRO
  4577. // X5. With each LRO, compute the least greater even embedding level.
  4578. // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to left-to-right.
  4579. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  4580. $next_level = $cel + 2 - ($cel % 2);
  4581. if ($next_level < 62) {
  4582. $remember[] = array('num' => 8237, 'cel' => $cel, 'dos' => $dos);
  4583. $cel = $next_level;
  4584. $dos = UCDN::BIDI_CLASS_L;
  4585. $controlchars = true;
  4586. }
  4587. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 8236) { // PDF
  4588. // X7. With each PDF, determine the matching embedding or override code. If there was a valid matching code, restore (pop) the last remembered (pushed) embedding level and directional override.
  4589. if (count($remember)) {
  4590. $last = count($remember) - 1;
  4591. if (($remember[$last]['num'] == 8235) || ($remember[$last]['num'] == 8234) || ($remember[$last]['num'] == 8238) ||
  4592. ($remember[$last]['num'] == 8237)) {
  4593. $match = array_pop($remember);
  4594. $cel = $match['cel'];
  4595. $dos = $match['dos'];
  4596. }
  4597. }
  4598. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 8294 || $chunkOTLdata['char_data'][$i]['uni'] == 8295 ||
  4599. $chunkOTLdata['char_data'][$i]['uni'] == 8296) { // LRI // RLI // FSI
  4600. // X5a. With each RLI:
  4601. // X5b. With each LRI:
  4602. // X5c. With each FSI, apply rules P2 and P3 for First Strong character
  4603. // Set the RLI/LRI/FSI embedding level to the embedding level of the last entry on the directional status stack.
  4604. if ($dos != -1) {
  4605. $chardir = $dos;
  4606. } else {
  4607. $chardir = $chunkOTLdata['char_data'][$i]['bidi_class'];
  4608. }
  4609. $chunkOTLdata['char_data'][$i]['level'] = $cel;
  4610. $chunkOTLdata['char_data'][$i]['type'] = $chardir;
  4611. $chunkOTLdata['char_data'][$i]['diid'] = $diid;
  4612. $fsi = '';
  4613. // X5c. With each FSI, apply rules P2 and P3 within the isolate run for First Strong character
  4614. if ($chunkOTLdata['char_data'][$i]['uni'] == 8296) { // FSI
  4615. $lvl = 0;
  4616. $nc2 = $nc;
  4617. $i2 = $i;
  4618. while (!($nc2 == ($numchunks - 1) && $i2 == ((count($para[$nc2][18]['char_data'])) - 1))) { // while not at end of last chunk
  4619. $i2++;
  4620. if ($i2 >= count($para[$nc2][18]['char_data'])) {
  4621. $nc2++;
  4622. $i2 = 0;
  4623. }
  4624. if ($lvl > 0) {
  4625. continue;
  4626. }
  4627. if ($para[$nc2][18]['char_data'][$i2]['uni'] == 8294 || $para[$nc2][18]['char_data'][$i2]['uni'] == 8295 || $para[$nc2][18]['char_data'][$i2]['uni'] == 8296) {
  4628. $lvl++;
  4629. continue;
  4630. }
  4631. if ($para[$nc2][18]['char_data'][$i2]['uni'] == 8297) {
  4632. $lvl--;
  4633. if ($lvl < 0) {
  4634. break;
  4635. }
  4636. }
  4637. if ($para[$nc2][18]['char_data'][$i2]['bidi_class'] === UCDN::BIDI_CLASS_L || $para[$nc2][18]['char_data'][$i2]['bidi_class'] == UCDN::BIDI_CLASS_AL || $para[$nc2][18]['char_data'][$i2]['bidi_class'] === UCDN::BIDI_CLASS_R) {
  4638. $fsi = $para[$nc2][18]['char_data'][$i2]['bidi_class'];
  4639. break;
  4640. }
  4641. }
  4642. // if fsi not found, fsi is same as paragraph embedding level
  4643. if (!$fsi && $fsi !== 0) {
  4644. if ($pel == 1) {
  4645. $fsi = UCDN::BIDI_CLASS_R;
  4646. } else {
  4647. $fsi = UCDN::BIDI_CLASS_L;
  4648. }
  4649. }
  4650. }
  4651. if ($chunkOTLdata['char_data'][$i]['uni'] == 8294 || $fsi === UCDN::BIDI_CLASS_L) { // LRI or FSI-L
  4652. // Compute the least even embedding level greater than the embedding level of the last entry on the directional status stack.
  4653. $next_level = $cel + 2 - ($cel % 2);
  4654. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 8295 || $fsi == UCDN::BIDI_CLASS_R || $fsi == UCDN::BIDI_CLASS_AL) { // RLI or FSI-R
  4655. // Compute the least odd embedding level greater than the embedding level of the last entry on the directional status stack.
  4656. $next_level = $cel + ($cel % 2) + 1;
  4657. }
  4658. // Increment the isolate count by one, and push an entry consisting of the new embedding level,
  4659. // neutral directional override status, and true directional isolate status onto the directional status stack.
  4660. $remember[] = array('num' => $chunkOTLdata['char_data'][$i]['uni'], 'cel' => $cel, 'dos' => $dos, 'diid' => $diid);
  4661. $cel = $next_level;
  4662. $dos = -1;
  4663. $diid = ++$dictr; // Set new direction isolate ID after incrementing direction isolate counter
  4664. $controlchars = true;
  4665. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 8297) { // PDI
  4666. // X6a. With each PDI, perform the following steps:
  4667. // Pop the last entry from the directional status stack and decrement the isolate count by one.
  4668. while (count($remember)) {
  4669. $last = count($remember) - 1;
  4670. if (($remember[$last]['num'] == 8294) || ($remember[$last]['num'] == 8295) || ($remember[$last]['num'] == 8296)) {
  4671. $match = array_pop($remember);
  4672. $cel = $match['cel'];
  4673. $dos = $match['dos'];
  4674. $diid = $match['diid'];
  4675. break;
  4676. }
  4677. // End/close any open embedding states not explicitly closed during the isolate
  4678. else if (($remember[$last]['num'] == 8235) || ($remember[$last]['num'] == 8234) || ($remember[$last]['num'] == 8238) ||
  4679. ($remember[$last]['num'] == 8237)) {
  4680. $match = array_pop($remember);
  4681. }
  4682. }
  4683. // In all cases, set the PDI’s level to the embedding level of the last entry on the directional status stack left after the steps above.
  4684. // NB The level assigned to an isolate initiator is always the same as that assigned to the matching PDI.
  4685. if ($dos != -1) {
  4686. $chardir = $dos;
  4687. } else {
  4688. $chardir = $chunkOTLdata['char_data'][$i]['bidi_class'];
  4689. }
  4690. $chunkOTLdata['char_data'][$i]['level'] = $cel;
  4691. $chunkOTLdata['char_data'][$i]['type'] = $chardir;
  4692. $chunkOTLdata['char_data'][$i]['diid'] = $diid;
  4693. $controlchars = true;
  4694. } else if ($chunkOTLdata['char_data'][$i]['uni'] == 10) { // NEW LINE
  4695. // Reset to start values
  4696. $cel = $pel;
  4697. $dos = -1;
  4698. $remember = array();
  4699. } else {
  4700. // X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
  4701. // a. Set the level of the current character to the current embedding level.
  4702. // b. When the directional override status is not neutral, reset the current character type to directional override status.
  4703. if ($dos != -1) {
  4704. $chardir = $dos;
  4705. } else {
  4706. $chardir = $chunkOTLdata['char_data'][$i]['bidi_class'];
  4707. if ($chardir == UCDN::BIDI_CLASS_R || $chardir == UCDN::BIDI_CLASS_AL) {
  4708. $strongrtl = true;
  4709. }
  4710. }
  4711. $chunkOTLdata['char_data'][$i]['level'] = $cel;
  4712. $chunkOTLdata['char_data'][$i]['type'] = $chardir;
  4713. $chunkOTLdata['char_data'][$i]['diid'] = $diid;
  4714. }
  4715. }
  4716. // X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph.
  4717. // Paragraph separators are not included in the embedding.
  4718. // X9. Remove all RLE, LRE, RLO, LRO, and PDF codes.
  4719. if ($controlchars) {
  4720. $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xaa");
  4721. $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xab");
  4722. $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xac");
  4723. $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xad");
  4724. $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xae");
  4725. preg_replace("/\x{202a}-\x{202e}/u", '', $para[$nc][0]);
  4726. }
  4727. }
  4728. // Remove any blank chunks made by removing directional codes
  4729. $numchunks = count($para);
  4730. for ($nc = ($numchunks - 1); $nc >= 0; $nc--) {
  4731. if (count($para[$nc][18]['char_data']) == 0) {
  4732. array_splice($para, $nc, 1);
  4733. }
  4734. }
  4735. if ($dir != 'rtl' && !$strongrtl && !$controlchars) {
  4736. return;
  4737. }
  4738. $numchunks = count($para);
  4739. // X10. Determine the start-of-sequence (sor) and end-of-sequence (eor) types, either L or R, for each isolating run sequence. These depend on the higher of the two levels on either side of the sequence boundary:
  4740. // For sor, compare the level of the first character in the sequence with the level of the character preceding it in the paragraph or if there is none, with the paragraph embedding level.
  4741. // For eor, compare the level of the last character in the sequence with the level of the character following it in the paragraph or if there is none, with the paragraph embedding level.
  4742. // If the higher level is odd, the sor or eor is R; otherwise, it is L.
  4743. for ($ir = 0; $ir <= $dictr; $ir++) {
  4744. $prelevel = $pel;
  4745. $postlevel = $pel;
  4746. $firstchar = true;
  4747. for ($nc = 0; $nc < $numchunks; $nc++) {
  4748. $chardata = & $para[$nc][18]['char_data'];
  4749. $numchars = count($chardata);
  4750. for ($i = 0; $i < $numchars; ++$i) {
  4751. if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) {
  4752. continue;
  4753. } // Ignore characters in a different isolate run
  4754. $right = $postlevel;
  4755. $nc2 = $nc;
  4756. $i2 = $i;
  4757. while (!($nc2 == ($numchunks - 1) && $i2 == ((count($para[$nc2][18]['char_data'])) - 1))) { // while not at end of last chunk
  4758. $i2++;
  4759. if ($i2 >= count($para[$nc2][18]['char_data'])) {
  4760. $nc2++;
  4761. $i2 = 0;
  4762. }
  4763. if (isset($para[$nc2][18]['char_data'][$i2]['diid']) && $para[$nc2][18]['char_data'][$i2]['diid'] == $ir) {
  4764. $right = $para[$nc2][18]['char_data'][$i2]['level'];
  4765. break;
  4766. }
  4767. }
  4768. $level = $chardata[$i]['level'];
  4769. if ($firstchar || $level != $prelevel) {
  4770. $chardata[$i]['sor'] = max($prelevel, $level) % 2 ? UCDN::BIDI_CLASS_R : UCDN::BIDI_CLASS_L;
  4771. }
  4772. if (($nc == ($numchunks - 1) && $i == ($numchars - 1)) || $level != $right) {
  4773. $chardata[$i]['eor'] = max($right, $level) % 2 ? UCDN::BIDI_CLASS_R : UCDN::BIDI_CLASS_L;
  4774. }
  4775. $prelevel = $level;
  4776. $firstchar = false;
  4777. }
  4778. }
  4779. }
  4780. // 3.3.3 Resolving Weak Types
  4781. // Weak types are now resolved one level run at a time. At level run boundaries where the type of the character on the other side of the boundary is required, the type assigned to sor or eor is used.
  4782. // Nonspacing marks are now resolved based on the previous characters.
  4783. // W1. Examine each nonspacing mark (NSM) in the level run, and change the type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor.
  4784. for ($ir = 0; $ir <= $dictr; $ir++) {
  4785. $prevtype = 0;
  4786. for ($nc = 0; $nc < $numchunks; $nc++) {
  4787. $chardata = & $para[$nc][18]['char_data'];
  4788. $numchars = count($chardata);
  4789. for ($i = 0; $i < $numchars; ++$i) {
  4790. if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) {
  4791. continue;
  4792. } // Ignore characters in a different isolate run
  4793. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_NSM) {
  4794. if (isset($chardata[$i]['sor'])) {
  4795. $chardata[$i]['type'] = $chardata[$i]['sor'];
  4796. } else {
  4797. $chardata[$i]['type'] = $prevtype;
  4798. }
  4799. }
  4800. $prevtype = $chardata[$i]['type'];
  4801. }
  4802. }
  4803. }
  4804. // W2. Search backward from each instance of a European number until the first strong type (R, L, AL or sor) is found. If an AL is found, change the type of the European number to Arabic number.
  4805. for ($ir = 0; $ir <= $dictr; $ir++) {
  4806. $laststrongtype = -1;
  4807. for ($nc = 0; $nc < $numchunks; $nc++) {
  4808. $chardata = & $para[$nc][18]['char_data'];
  4809. $numchars = count($chardata);
  4810. for ($i = 0; $i < $numchars; ++$i) {
  4811. if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) {
  4812. continue;
  4813. } // Ignore characters in a different isolate run
  4814. if (isset($chardata[$i]['sor'])) {
  4815. $laststrongtype = $chardata[$i]['sor'];
  4816. }
  4817. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_EN && $laststrongtype == UCDN::BIDI_CLASS_AL) {
  4818. $chardata[$i]['type'] = UCDN::BIDI_CLASS_AN;
  4819. }
  4820. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_L || $chardata[$i]['type'] == UCDN::BIDI_CLASS_R || $chardata[$i]['type'] == UCDN::BIDI_CLASS_AL) {
  4821. $laststrongtype = $chardata[$i]['type'];
  4822. }
  4823. }
  4824. }
  4825. }
  4826. // W3. Change all ALs to R.
  4827. for ($nc = 0; $nc < $numchunks; $nc++) {
  4828. $chardata = & $para[$nc][18]['char_data'];
  4829. $numchars = count($chardata);
  4830. for ($i = 0; $i < $numchars; ++$i) {
  4831. if (isset($chardata[$i]['type']) && $chardata[$i]['type'] == UCDN::BIDI_CLASS_AL) {
  4832. $chardata[$i]['type'] = UCDN::BIDI_CLASS_R;
  4833. }
  4834. }
  4835. }
  4836. // W4. A single European separator between two European numbers changes to a European number. A single common separator between two numbers of the same type changes to that type.
  4837. for ($ir = 0; $ir <= $dictr; $ir++) {
  4838. $prevtype = -1;
  4839. $nexttype = -1;
  4840. for ($nc = 0; $nc < $numchunks; $nc++) {
  4841. $chardata = & $para[$nc][18]['char_data'];
  4842. $numchars = count($chardata);
  4843. for ($i = 0; $i < $numchars; ++$i) {
  4844. if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) {
  4845. continue;
  4846. } // Ignore characters in a different isolate run
  4847. // Get next type
  4848. $nexttype = -1;
  4849. $nc2 = $nc;
  4850. $i2 = $i;
  4851. while (!($nc2 == ($numchunks - 1) && $i2 == ((count($para[$nc2][18]['char_data'])) - 1))) { // while not at end of last chunk
  4852. $i2++;
  4853. if ($i2 >= count($para[$nc2][18]['char_data'])) {
  4854. $nc2++;
  4855. $i2 = 0;
  4856. }
  4857. if (isset($para[$nc2][18]['char_data'][$i2]['diid']) && $para[$nc2][18]['char_data'][$i2]['diid'] == $ir) {
  4858. $nexttype = $para[$nc2][18]['char_data'][$i2]['type'];
  4859. break;
  4860. }
  4861. }
  4862. if (!isset($chardata[$i]['sor']) && !isset($chardata[$i]['eor'])) {
  4863. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_ES && $prevtype == UCDN::BIDI_CLASS_EN && $nexttype == UCDN::BIDI_CLASS_EN) {
  4864. $chardata[$i]['type'] = UCDN::BIDI_CLASS_EN;
  4865. } else if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_CS && $prevtype == UCDN::BIDI_CLASS_EN && $nexttype == UCDN::BIDI_CLASS_EN) {
  4866. $chardata[$i]['type'] = UCDN::BIDI_CLASS_EN;
  4867. } else if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_CS && $prevtype == UCDN::BIDI_CLASS_AN && $nexttype == UCDN::BIDI_CLASS_AN) {
  4868. $chardata[$i]['type'] = UCDN::BIDI_CLASS_AN;
  4869. }
  4870. }
  4871. $prevtype = $chardata[$i]['type'];
  4872. }
  4873. }
  4874. }
  4875. // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
  4876. for ($ir = 0; $ir <= $dictr; $ir++) {
  4877. $prevtype = -1;
  4878. $nexttype = -1;
  4879. for ($nc = 0; $nc < $numchunks; $nc++) {
  4880. $chardata = & $para[$nc][18]['char_data'];
  4881. $numchars = count($chardata);
  4882. for ($i = 0; $i < $numchars; ++$i) {
  4883. if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) {
  4884. continue;
  4885. } // Ignore characters in a different isolate run
  4886. if (isset($chardata[$i]['sor'])) {
  4887. $prevtype = $chardata[$i]['sor'];
  4888. }
  4889. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_ET) {
  4890. if ($prevtype == UCDN::BIDI_CLASS_EN) {
  4891. $chardata[$i]['type'] = UCDN::BIDI_CLASS_EN;
  4892. } else if (!isset($chardata[$i]['eor'])) {
  4893. $nexttype = -1;
  4894. $nc2 = $nc;
  4895. $i2 = $i;
  4896. while (!($nc2 == ($numchunks - 1) && $i2 == ((count($para[$nc2][18]['char_data'])) - 1))) { // while not at end of last chunk
  4897. $i2++;
  4898. if ($i2 >= count($para[$nc2][18]['char_data'])) {
  4899. $nc2++;
  4900. $i2 = 0;
  4901. }
  4902. if ($para[$nc2][18]['char_data'][$i2]['diid'] != $ir) {
  4903. continue;
  4904. }
  4905. $nexttype = $para[$nc2][18]['char_data'][$i2]['type'];
  4906. if (isset($para[$nc2][18]['char_data'][$i2]['sor'])) {
  4907. break;
  4908. }
  4909. if ($nexttype == UCDN::BIDI_CLASS_EN) {
  4910. $chardata[$i]['type'] = UCDN::BIDI_CLASS_EN;
  4911. break;
  4912. } else if ($nexttype != UCDN::BIDI_CLASS_ET) {
  4913. break;
  4914. }
  4915. }
  4916. }
  4917. }
  4918. $prevtype = $chardata[$i]['type'];
  4919. }
  4920. }
  4921. }
  4922. // W6. Otherwise, separators and terminators change to Other Neutral.
  4923. for ($nc = 0; $nc < $numchunks; $nc++) {
  4924. $chardata = & $para[$nc][18]['char_data'];
  4925. $numchars = count($chardata);
  4926. for ($i = 0; $i < $numchars; ++$i) {
  4927. if (isset($chardata[$i]['type']) && (($chardata[$i]['type'] == UCDN::BIDI_CLASS_ET) || ($chardata[$i]['type'] == UCDN::BIDI_CLASS_ES) || ($chardata[$i]['type'] == UCDN::BIDI_CLASS_CS))) {
  4928. $chardata[$i]['type'] = UCDN::BIDI_CLASS_ON;
  4929. }
  4930. }
  4931. }
  4932. //W7. Search backward from each instance of a European number until the first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L.
  4933. for ($ir = 0; $ir <= $dictr; $ir++) {
  4934. $laststrongtype = -1;
  4935. for ($nc = 0; $nc < $numchunks; $nc++) {
  4936. $chardata = & $para[$nc][18]['char_data'];
  4937. $numchars = count($chardata);
  4938. for ($i = 0; $i < $numchars; ++$i) {
  4939. if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) {
  4940. continue;
  4941. } // Ignore characters in a different isolate run
  4942. if (isset($chardata[$i]['sor'])) {
  4943. $laststrongtype = $chardata[$i]['sor'];
  4944. }
  4945. if (isset($chardata[$i]['type']) && $chardata[$i]['type'] == UCDN::BIDI_CLASS_EN && $laststrongtype == UCDN::BIDI_CLASS_L) {
  4946. $chardata[$i]['type'] = UCDN::BIDI_CLASS_L;
  4947. }
  4948. if (isset($chardata[$i]['type']) && ($chardata[$i]['type'] == UCDN::BIDI_CLASS_L || $chardata[$i]['type'] == UCDN::BIDI_CLASS_R || $chardata[$i]['type'] == UCDN::BIDI_CLASS_AL)) {
  4949. $laststrongtype = $chardata[$i]['type'];
  4950. }
  4951. }
  4952. }
  4953. }
  4954. // N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. European and Arabic numbers act as if they were R in terms of their influence on neutrals. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries.
  4955. for ($ir = 0; $ir <= $dictr; $ir++) {
  4956. $laststrongtype = -1;
  4957. for ($nc = 0; $nc < $numchunks; $nc++) {
  4958. $chardata = & $para[$nc][18]['char_data'];
  4959. $numchars = count($chardata);
  4960. for ($i = 0; $i < $numchars; ++$i) {
  4961. if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) {
  4962. continue;
  4963. } // Ignore characters in a different isolate run
  4964. if (isset($chardata[$i]['sor'])) {
  4965. $laststrongtype = $chardata[$i]['sor'];
  4966. }
  4967. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_ON || $chardata[$i]['type'] == UCDN::BIDI_CLASS_WS) {
  4968. $left = -1;
  4969. // LEFT
  4970. if ($laststrongtype == UCDN::BIDI_CLASS_R || $laststrongtype == UCDN::BIDI_CLASS_EN || $laststrongtype == UCDN::BIDI_CLASS_AN) {
  4971. $left = UCDN::BIDI_CLASS_R;
  4972. } else if ($laststrongtype == UCDN::BIDI_CLASS_L) {
  4973. $left = UCDN::BIDI_CLASS_L;
  4974. }
  4975. // RIGHT
  4976. $right = -1;
  4977. // move to the right of any following neutrals OR hit a run boundary
  4978. if (isset($chardata[$i]['eor'])) {
  4979. $right = $chardata[$i]['eor'];
  4980. } else {
  4981. $nexttype = -1;
  4982. $nc2 = $nc;
  4983. $i2 = $i;
  4984. while (!($nc2 == ($numchunks - 1) && $i2 == ((count($para[$nc2][18]['char_data'])) - 1))) { // while not at end of last chunk
  4985. $i2++;
  4986. if ($i2 >= count($para[$nc2][18]['char_data'])) {
  4987. $nc2++;
  4988. $i2 = 0;
  4989. }
  4990. if (!isset($para[$nc2][18]['char_data'][$i2]['diid']) || $para[$nc2][18]['char_data'][$i2]['diid'] != $ir) {
  4991. continue;
  4992. }
  4993. $nexttype = $para[$nc2][18]['char_data'][$i2]['type'];
  4994. if ($nexttype == UCDN::BIDI_CLASS_R || $nexttype == UCDN::BIDI_CLASS_EN || $nexttype == UCDN::BIDI_CLASS_AN) {
  4995. $right = UCDN::BIDI_CLASS_R;
  4996. break;
  4997. } else if ($nexttype == UCDN::BIDI_CLASS_L) {
  4998. $right = UCDN::BIDI_CLASS_L;
  4999. break;
  5000. } else if (isset($para[$nc2][18]['char_data'][$i2]['eor'])) {
  5001. $right = $para[$nc2][18]['char_data'][$i2]['eor'];
  5002. break;
  5003. }
  5004. }
  5005. }
  5006. if ($left > -1 && $left == $right) {
  5007. $chardata[$i]['orig_type'] = $chardata[$i]['type']; // Need to store the original 'WS' for reference in L1 below
  5008. $chardata[$i]['type'] = $left;
  5009. }
  5010. } else if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_L || $chardata[$i]['type'] == UCDN::BIDI_CLASS_R || $chardata[$i]['type'] == UCDN::BIDI_CLASS_EN || $chardata[$i]['type'] == UCDN::BIDI_CLASS_AN) {
  5011. $laststrongtype = $chardata[$i]['type'];
  5012. }
  5013. }
  5014. }
  5015. }
  5016. // N2. Any remaining neutrals take the embedding direction
  5017. for ($nc = 0; $nc < $numchunks; $nc++) {
  5018. $chardata = & $para[$nc][18]['char_data'];
  5019. $numchars = count($chardata);
  5020. for ($i = 0; $i < $numchars; ++$i) {
  5021. if (isset($chardata[$i]['type']) && ($chardata[$i]['type'] == UCDN::BIDI_CLASS_ON || $chardata[$i]['type'] == UCDN::BIDI_CLASS_WS)) {
  5022. $chardata[$i]['orig_type'] = $chardata[$i]['type']; // Need to store the original 'WS' for reference in L1 below
  5023. $chardata[$i]['type'] = ($chardata[$i]['level'] % 2) ? UCDN::BIDI_CLASS_R : UCDN::BIDI_CLASS_L;
  5024. }
  5025. }
  5026. }
  5027. // I1. For all characters with an even (left-to-right) embedding direction, those of type R go up one level and those of type AN or EN go up two levels.
  5028. // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
  5029. for ($nc = 0; $nc < $numchunks; $nc++) {
  5030. $chardata = & $para[$nc][18]['char_data'];
  5031. $numchars = count($chardata);
  5032. for ($i = 0; $i < $numchars; ++$i) {
  5033. if (isset($chardata[$i]['level'])) {
  5034. $odd = $chardata[$i]['level'] % 2;
  5035. if ($odd) {
  5036. if (($chardata[$i]['type'] == UCDN::BIDI_CLASS_L) || ($chardata[$i]['type'] == UCDN::BIDI_CLASS_AN) || ($chardata[$i]['type'] == UCDN::BIDI_CLASS_EN)) {
  5037. $chardata[$i]['level'] += 1;
  5038. }
  5039. } else {
  5040. if ($chardata[$i]['type'] == UCDN::BIDI_CLASS_R) {
  5041. $chardata[$i]['level'] += 1;
  5042. } else if (($chardata[$i]['type'] == UCDN::BIDI_CLASS_AN) || ($chardata[$i]['type'] == UCDN::BIDI_CLASS_EN)) {
  5043. $chardata[$i]['level'] += 2;
  5044. }
  5045. }
  5046. }
  5047. }
  5048. }
  5049. // Remove Isolate formatters
  5050. $numchunks = count($para);
  5051. if ($controlchars) {
  5052. for ($nc = 0; $nc < $numchunks; $nc++) {
  5053. $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa6");
  5054. $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa7");
  5055. $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa8");
  5056. $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa9");
  5057. preg_replace("/\x{2066}-\x{2069}/u", '', $para[$nc][0]);
  5058. }
  5059. // Remove any blank chunks made by removing directional codes
  5060. for ($nc = ($numchunks - 1); $nc >= 0; $nc--) {
  5061. if (count($para[$nc][18]['char_data']) == 0) {
  5062. array_splice($para, $nc, 1);
  5063. }
  5064. }
  5065. }
  5066. }
  5067. // Reorder, once divided into lines
  5068. function _bidiReorder(&$chunkorder, &$content, &$cOTLdata, $blockdir)
  5069. {
  5070. $bidiData = array();
  5071. // First combine into one array (and get the highest level in use)
  5072. $numchunks = count($content);
  5073. $maxlevel = 0;
  5074. for ($nc = 0; $nc < $numchunks; $nc++) {
  5075. $numchars = count($cOTLdata[$nc]['char_data']);
  5076. for ($i = 0; $i < $numchars; ++$i) {
  5077. $carac = array();
  5078. if (isset($cOTLdata[$nc]['GPOSinfo'][$i])) {
  5079. $carac['GPOSinfo'] = $cOTLdata[$nc]['GPOSinfo'][$i];
  5080. }
  5081. $carac['uni'] = $cOTLdata[$nc]['char_data'][$i]['uni'];
  5082. if (isset($cOTLdata[$nc]['char_data'][$i]['type']))
  5083. $carac['type'] = $cOTLdata[$nc]['char_data'][$i]['type'];
  5084. if (isset($cOTLdata[$nc]['char_data'][$i]['level']))
  5085. $carac['level'] = $cOTLdata[$nc]['char_data'][$i]['level'];
  5086. if (isset($cOTLdata[$nc]['char_data'][$i]['orig_type'])) {
  5087. $carac['orig_type'] = $cOTLdata[$nc]['char_data'][$i]['orig_type'];
  5088. }
  5089. $carac['group'] = $cOTLdata[$nc]['group']{$i};
  5090. $carac['chunkid'] = $chunkorder[$nc]; // gives font id and/or object ID
  5091. $maxlevel = max((isset($carac['level']) ? $carac['level'] : 0), $maxlevel);
  5092. $bidiData[] = $carac;
  5093. }
  5094. }
  5095. if ($maxlevel == 0) {
  5096. return;
  5097. }
  5098. $numchars = count($bidiData);
  5099. // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
  5100. // 1. Segment separators (Tab) 'S',
  5101. // 2. Paragraph separators 'B',
  5102. // 3. Any sequence of whitespace characters 'WS' preceding a segment separator or paragraph separator, and
  5103. // 4. Any sequence of whitespace characters 'WS' at the end of the line.
  5104. // The types of characters used here are the original types, not those modified by the previous phase cf N1 and N2*******
  5105. // Because a Paragraph Separator breaks lines, there will be at most one per line, at the end of that line.
  5106. // Set the initial paragraph embedding level
  5107. if ($blockdir == 'rtl') {
  5108. $pel = 1;
  5109. } else {
  5110. $pel = 0;
  5111. }
  5112. for ($i = ($numchars - 1); $i > 0; $i--) {
  5113. if ($bidiData[$i]['type'] == UCDN::BIDI_CLASS_WS || (isset($bidiData[$i]['orig_type']) && $bidiData[$i]['orig_type'] == UCDN::BIDI_CLASS_WS)) {
  5114. $bidiData[$i]['level'] = $pel;
  5115. } else {
  5116. break;
  5117. }
  5118. }
  5119. // L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher.
  5120. for ($j = $maxlevel; $j > 0; $j--) {
  5121. $ordarray = array();
  5122. $revarr = array();
  5123. $onlevel = false;
  5124. for ($i = 0; $i < $numchars; ++$i) {
  5125. if ($bidiData[$i]['level'] >= $j) {
  5126. $onlevel = true;
  5127. // L4. A character is depicted by a mirrored glyph if and only if (a) the resolved directionality of that character is R, and (b) the Bidi_Mirrored property value of that character is true.
  5128. if (isset(UCDN::$mirror_pairs[$bidiData[$i]['uni']]) && $bidiData[$i]['type'] == UCDN::BIDI_CLASS_R) {
  5129. $bidiData[$i]['uni'] = UCDN::$mirror_pairs[$bidiData[$i]['uni']];
  5130. }
  5131. $revarr[] = $bidiData[$i];
  5132. } else {
  5133. if ($onlevel) {
  5134. $revarr = array_reverse($revarr);
  5135. $ordarray = array_merge($ordarray, $revarr);
  5136. $revarr = Array();
  5137. $onlevel = false;
  5138. }
  5139. $ordarray[] = $bidiData[$i];
  5140. }
  5141. }
  5142. if ($onlevel) {
  5143. $revarr = array_reverse($revarr);
  5144. $ordarray = array_merge($ordarray, $revarr);
  5145. }
  5146. $bidiData = $ordarray;
  5147. }
  5148. $content = array();
  5149. $cOTLdata = array();
  5150. $chunkorder = array();
  5151. $nc = -1; // New chunk order ID
  5152. $chunkid = -1;
  5153. foreach ($bidiData as $carac) {
  5154. if ($carac['chunkid'] != $chunkid) {
  5155. $nc++;
  5156. $chunkorder[$nc] = $carac['chunkid'];
  5157. $cctr = 0;
  5158. $content[$nc] = '';
  5159. $cOTLdata[$nc]['group'] = '';
  5160. }
  5161. if ($carac['uni'] != 0xFFFC) { // Object replacement character (65532)
  5162. $content[$nc] .= code2utf($carac['uni']);
  5163. $cOTLdata[$nc]['group'] .= $carac['group'];
  5164. if (!empty($carac['GPOSinfo'])) {
  5165. if (isset($carac['GPOSinfo'])) {
  5166. $cOTLdata[$nc]['GPOSinfo'][$cctr] = $carac['GPOSinfo'];
  5167. }
  5168. $cOTLdata[$nc]['GPOSinfo'][$cctr]['wDir'] = ($carac['level'] % 2) ? 'RTL' : 'LTR';
  5169. }
  5170. }
  5171. $chunkid = $carac['chunkid'];
  5172. $cctr++;
  5173. }
  5174. }
  5175. ////////////////////////////////////////////////////////////////
  5176. ////////////////////////////////////////////////////////////////
  5177. // These functions are called from mpdf after GSUB/GPOS has taken place
  5178. // At this stage the bidi-type is in string form
  5179. ////////////////////////////////////////////////////////////////
  5180. ////////////////////////////////////////////////////////////////
  5181. function splitOTLdata(&$cOTLdata, $OTLcutoffpos, $OTLrestartpos = '')
  5182. {
  5183. if (!$OTLrestartpos) {
  5184. $OTLrestartpos = $OTLcutoffpos;
  5185. }
  5186. $newOTLdata = array('GPOSinfo' => array(), 'char_data' => array());
  5187. $newOTLdata['group'] = substr($cOTLdata['group'], $OTLrestartpos);
  5188. $cOTLdata['group'] = substr($cOTLdata['group'], 0, $OTLcutoffpos);
  5189. if (isset($cOTLdata['GPOSinfo']) && $cOTLdata['GPOSinfo']) {
  5190. foreach ($cOTLdata['GPOSinfo'] AS $k => $val) {
  5191. if ($k >= $OTLrestartpos) {
  5192. $newOTLdata['GPOSinfo'][($k - $OTLrestartpos)] = $val;
  5193. }
  5194. if ($k >= $OTLcutoffpos) {
  5195. unset($cOTLdata['GPOSinfo'][$k]);
  5196. //$cOTLdata['GPOSinfo'][$k] = array();
  5197. }
  5198. }
  5199. }
  5200. if (isset($cOTLdata['char_data'])) {
  5201. $newOTLdata['char_data'] = array_slice($cOTLdata['char_data'], $OTLrestartpos);
  5202. array_splice($cOTLdata['char_data'], $OTLcutoffpos);
  5203. }
  5204. // Not necessary - easier to debug
  5205. if (isset($cOTLdata['GPOSinfo']))
  5206. ksort($cOTLdata['GPOSinfo']);
  5207. if (isset($newOTLdata['GPOSinfo']))
  5208. ksort($newOTLdata['GPOSinfo']);
  5209. return $newOTLdata;
  5210. }
  5211. function sliceOTLdata($OTLdata, $pos, $len)
  5212. {
  5213. $newOTLdata = array('GPOSinfo' => array(), 'char_data' => array());
  5214. $newOTLdata['group'] = substr($OTLdata['group'], $pos, $len);
  5215. if ($OTLdata['GPOSinfo']) {
  5216. foreach ($OTLdata['GPOSinfo'] AS $k => $val) {
  5217. if ($k >= $pos && $k < ($pos + $len)) {
  5218. $newOTLdata['GPOSinfo'][($k - $pos)] = $val;
  5219. }
  5220. }
  5221. }
  5222. if (isset($OTLdata['char_data'])) {
  5223. $newOTLdata['char_data'] = array_slice($OTLdata['char_data'], $pos, $len);
  5224. }
  5225. // Not necessary - easier to debug
  5226. if ($newOTLdata['GPOSinfo'])
  5227. ksort($newOTLdata['GPOSinfo']);
  5228. return $newOTLdata;
  5229. }
  5230. // Remove one or more occurrences of $char (single character) from $txt and adjust OTLdata
  5231. function removeChar(&$txt, &$cOTLdata, $char)
  5232. {
  5233. while (mb_strpos($txt, $char, 0, $this->mpdf->mb_enc) !== false) {
  5234. $pos = mb_strpos($txt, $char, 0, $this->mpdf->mb_enc);
  5235. $newGPOSinfo = array();
  5236. $cOTLdata['group'] = substr_replace($cOTLdata['group'], '', $pos, 1);
  5237. if ($cOTLdata['GPOSinfo']) {
  5238. foreach ($cOTLdata['GPOSinfo'] AS $k => $val) {
  5239. if ($k > $pos) {
  5240. $newGPOSinfo[($k - 1)] = $val;
  5241. } else if ($k != $pos) {
  5242. $newGPOSinfo[$k] = $val;
  5243. }
  5244. }
  5245. $cOTLdata['GPOSinfo'] = $newGPOSinfo;
  5246. }
  5247. if (isset($cOTLdata['char_data'])) {
  5248. array_splice($cOTLdata['char_data'], $pos, 1);
  5249. }
  5250. $txt = preg_replace("/" . $char . "/", '', $txt, 1);
  5251. }
  5252. }
  5253. // Remove one or more occurrences of $char (single character) from $txt and adjust OTLdata
  5254. function replaceSpace(&$txt, &$cOTLdata)
  5255. {
  5256. $char = chr(194) . chr(160); // NBSP
  5257. while (mb_strpos($txt, $char, 0, $this->mpdf->mb_enc) !== false) {
  5258. $pos = mb_strpos($txt, $char, 0, $this->mpdf->mb_enc);
  5259. if ($cOTLdata['char_data'][$pos]['uni'] == 160) {
  5260. $cOTLdata['char_data'][$pos]['uni'] = 32;
  5261. }
  5262. $txt = preg_replace("/" . $char . "/", ' ', $txt, 1);
  5263. }
  5264. }
  5265. function trimOTLdata(&$cOTLdata, $Left = true, $Right = true)
  5266. {
  5267. $len = count($cOTLdata['char_data']);
  5268. $nLeft = 0;
  5269. $nRight = 0;
  5270. for ($i = 0; $i < $len; $i++) {
  5271. if ($cOTLdata['char_data'][$i]['uni'] == 32 || $cOTLdata['char_data'][$i]['uni'] == 12288) {
  5272. $nLeft++;
  5273. } // 12288 = 0x3000 = CJK space
  5274. else {
  5275. break;
  5276. }
  5277. }
  5278. for ($i = ($len - 1); $i >= 0; $i--) {
  5279. if ($cOTLdata['char_data'][$i]['uni'] == 32 || $cOTLdata['char_data'][$i]['uni'] == 12288) {
  5280. $nRight++;
  5281. } // 12288 = 0x3000 = CJK space
  5282. else {
  5283. break;
  5284. }
  5285. }
  5286. // Trim Right
  5287. if ($Right && $nRight) {
  5288. $cOTLdata['group'] = substr($cOTLdata['group'], 0, strlen($cOTLdata['group']) - $nRight);
  5289. if ($cOTLdata['GPOSinfo']) {
  5290. foreach ($cOTLdata['GPOSinfo'] AS $k => $val) {
  5291. if ($k >= $len - $nRight) {
  5292. unset($cOTLdata['GPOSinfo'][$k]);
  5293. }
  5294. }
  5295. }
  5296. if (isset($cOTLdata['char_data'])) {
  5297. for ($i = 0; $i < $nRight; $i++) {
  5298. array_pop($cOTLdata['char_data']);
  5299. }
  5300. }
  5301. }
  5302. // Trim Left
  5303. if ($Left && $nLeft) {
  5304. $cOTLdata['group'] = substr($cOTLdata['group'], $nLeft);
  5305. if ($cOTLdata['GPOSinfo']) {
  5306. $newPOSinfo = array();
  5307. foreach ($cOTLdata['GPOSinfo'] AS $k => $val) {
  5308. if ($k >= $nLeft) {
  5309. $newPOSinfo[$k - $nLeft] = $cOTLdata['GPOSinfo'][$k];
  5310. }
  5311. }
  5312. $cOTLdata['GPOSinfo'] = $newPOSinfo;
  5313. }
  5314. if (isset($cOTLdata['char_data'])) {
  5315. for ($i = 0; $i < $nLeft; $i++) {
  5316. array_shift($cOTLdata['char_data']);
  5317. }
  5318. }
  5319. }
  5320. }
  5321. ////////////////////////////////////////////////////////////////
  5322. ////////////////////////////////////////////////////////////////
  5323. ////////// GENERAL OTL FUNCTIONS /////////////////
  5324. ////////////////////////////////////////////////////////////////
  5325. ////////////////////////////////////////////////////////////////
  5326. function glyphToChar($gid)
  5327. {
  5328. return (ord($this->glyphIDtoUni[$gid * 3]) << 16) + (ord($this->glyphIDtoUni[$gid * 3 + 1]) << 8) + ord($this->glyphIDtoUni[$gid * 3 + 2]);
  5329. }
  5330. function unicode_hex($unicode_dec)
  5331. {
  5332. return (str_pad(strtoupper(dechex($unicode_dec)), 5, '0', STR_PAD_LEFT));
  5333. }
  5334. function seek($pos)
  5335. {
  5336. $this->_pos = $pos;
  5337. }
  5338. function skip($delta)
  5339. {
  5340. $this->_pos += $delta;
  5341. }
  5342. function read_short()
  5343. {
  5344. $a = (ord($this->ttfOTLdata[$this->_pos]) << 8) + ord($this->ttfOTLdata[$this->_pos + 1]);
  5345. if ($a & (1 << 15)) {
  5346. $a = ($a - (1 << 16));
  5347. }
  5348. $this->_pos += 2;
  5349. return $a;
  5350. }
  5351. function read_ushort()
  5352. {
  5353. $a = (ord($this->ttfOTLdata[$this->_pos]) << 8) + ord($this->ttfOTLdata[$this->_pos + 1]);
  5354. $this->_pos += 2;
  5355. return $a;
  5356. }
  5357. function _getCoverageGID()
  5358. {
  5359. // Called from Lookup Type 1, Format 1 - returns glyphIDs rather than hexstrings
  5360. // Need to do this separately to cache separately
  5361. // Otherwise the same as fn below _getCoverage
  5362. $offset = $this->_pos;
  5363. if (isset($this->LuDataCache[$this->fontkey]['GID'][$offset])) {
  5364. $g = $this->LuDataCache[$this->fontkey]['GID'][$offset];
  5365. } else {
  5366. $g = array();
  5367. $CoverageFormat = $this->read_ushort();
  5368. if ($CoverageFormat == 1) {
  5369. $CoverageGlyphCount = $this->read_ushort();
  5370. for ($gid = 0; $gid < $CoverageGlyphCount; $gid++) {
  5371. $glyphID = $this->read_ushort();
  5372. $g[] = $glyphID;
  5373. }
  5374. }
  5375. if ($CoverageFormat == 2) {
  5376. $RangeCount = $this->read_ushort();
  5377. for ($r = 0; $r < $RangeCount; $r++) {
  5378. $start = $this->read_ushort();
  5379. $end = $this->read_ushort();
  5380. $StartCoverageIndex = $this->read_ushort(); // n/a
  5381. for ($glyphID = $start; $glyphID <= $end; $glyphID++) {
  5382. $g[] = $glyphID;
  5383. }
  5384. }
  5385. }
  5386. $this->LuDataCache[$this->fontkey]['GID'][$offset] = $g;
  5387. }
  5388. return $g;
  5389. }
  5390. function _getCoverage()
  5391. {
  5392. $offset = $this->_pos;
  5393. if (isset($this->LuDataCache[$this->fontkey][$offset])) {
  5394. $g = $this->LuDataCache[$this->fontkey][$offset];
  5395. } else {
  5396. $g = array();
  5397. $CoverageFormat = $this->read_ushort();
  5398. if ($CoverageFormat == 1) {
  5399. $CoverageGlyphCount = $this->read_ushort();
  5400. for ($gid = 0; $gid < $CoverageGlyphCount; $gid++) {
  5401. $glyphID = $this->read_ushort();
  5402. $g[] = $this->unicode_hex($this->glyphToChar($glyphID));
  5403. }
  5404. }
  5405. if ($CoverageFormat == 2) {
  5406. $RangeCount = $this->read_ushort();
  5407. for ($r = 0; $r < $RangeCount; $r++) {
  5408. $start = $this->read_ushort();
  5409. $end = $this->read_ushort();
  5410. $StartCoverageIndex = $this->read_ushort(); // n/a
  5411. for ($glyphID = $start; $glyphID <= $end; $glyphID++) {
  5412. $g[] = $this->unicode_hex($this->glyphToChar($glyphID));
  5413. }
  5414. }
  5415. }
  5416. $this->LuDataCache[$this->fontkey][$offset] = $g;
  5417. }
  5418. return $g;
  5419. }
  5420. function _getClasses($offset)
  5421. {
  5422. if (isset($this->LuDataCache[$this->fontkey][$offset])) {
  5423. $GlyphByClass = $this->LuDataCache[$this->fontkey][$offset];
  5424. } else {
  5425. $this->seek($offset);
  5426. $ClassFormat = $this->read_ushort();
  5427. $GlyphByClass = array();
  5428. if ($ClassFormat == 1) {
  5429. $StartGlyph = $this->read_ushort();
  5430. $GlyphCount = $this->read_ushort();
  5431. for ($i = 0; $i < $GlyphCount; $i++) {
  5432. $startGlyphID = $StartGlyph + $i;
  5433. $endGlyphID = $StartGlyph + $i;
  5434. $class = $this->read_ushort();
  5435. // Note: Font FreeSerif , tag "blws"
  5436. // $BacktrackClasses[0] is defined ? a mistake in the font ???
  5437. // Let's ignore for now
  5438. if ($class > 0) {
  5439. for ($g = $startGlyphID; $g <= $endGlyphID; $g++) {
  5440. if ($this->glyphToChar($g)) {
  5441. $GlyphByClass[$class][$this->glyphToChar($g)] = 1;
  5442. }
  5443. }
  5444. }
  5445. }
  5446. } else if ($ClassFormat == 2) {
  5447. $tableCount = $this->read_ushort();
  5448. for ($i = 0; $i < $tableCount; $i++) {
  5449. $startGlyphID = $this->read_ushort();
  5450. $endGlyphID = $this->read_ushort();
  5451. $class = $this->read_ushort();
  5452. // Note: Font FreeSerif , tag "blws"
  5453. // $BacktrackClasses[0] is defined ? a mistake in the font ???
  5454. // Let's ignore for now
  5455. if ($class > 0) {
  5456. for ($g = $startGlyphID; $g <= $endGlyphID; $g++) {
  5457. if ($this->glyphToChar($g)) {
  5458. $GlyphByClass[$class][$this->glyphToChar($g)] = 1;
  5459. }
  5460. }
  5461. }
  5462. }
  5463. }
  5464. $this->LuDataCache[$this->fontkey][$offset] = $GlyphByClass;
  5465. }
  5466. return $GlyphByClass;
  5467. }
  5468. function _getOTLscriptTag($ScriptLang, $scripttag, $scriptblock, $shaper, $useOTL, $mode)
  5469. {
  5470. // ScriptLang is the array of available script/lang tags supported by the font
  5471. // $scriptblock is the (number/code) for the script of the actual text string based on Unicode properties (UCDN::$uni_scriptblock)
  5472. // $scripttag is the default tag derived from $scriptblock
  5473. /*
  5474. http://www.microsoft.com/typography/otspec/ttoreg.htm
  5475. http://www.microsoft.com/typography/otspec/scripttags.htm
  5476. Values for useOTL
  5477. Bit dn hn Value
  5478. 1 1 0x0001 GSUB/GPOS - Latin scripts
  5479. 2 2 0x0002 GSUB/GPOS - Cyrillic scripts
  5480. 3 4 0x0004 GSUB/GPOS - Greek scripts
  5481. 4 8 0x0008 GSUB/GPOS - CJK scripts (excluding Hangul-Jamo)
  5482. 5 16 0x0010 (Reserved)
  5483. 6 32 0x0020 (Reserved)
  5484. 7 64 0x0040 (Reserved)
  5485. 8 128 0x0080 GSUB/GPOS - All other scripts (including all RTL scripts, complex scripts with shapers etc)
  5486. NB If change for RTL - cf. function magic_reverse_dir in mpdf.php to update
  5487. */
  5488. if ($scriptblock == UCDN::SCRIPT_LATIN) {
  5489. if (!($useOTL & 0x01)) {
  5490. return array('', false);
  5491. }
  5492. } else if ($scriptblock == UCDN::SCRIPT_CYRILLIC) {
  5493. if (!($useOTL & 0x02)) {
  5494. return array('', false);
  5495. }
  5496. } else if ($scriptblock == UCDN::SCRIPT_GREEK) {
  5497. if (!($useOTL & 0x04)) {
  5498. return array('', false);
  5499. }
  5500. } else if ($scriptblock >= UCDN::SCRIPT_HIRAGANA && $scriptblock <= UCDN::SCRIPT_YI) {
  5501. if (!($useOTL & 0x08)) {
  5502. return array('', false);
  5503. }
  5504. } else {
  5505. if (!($useOTL & 0x80)) {
  5506. return array('', false);
  5507. }
  5508. }
  5509. // If availabletags includes scripttag - choose
  5510. if (isset($ScriptLang[$scripttag])) {
  5511. return array($scripttag, false);
  5512. }
  5513. // If INDIC (or Myanmar) and available tag not includes new version, check if includes old version & choose old version
  5514. if ($shaper) {
  5515. switch ($scripttag) {
  5516. CASE 'bng2': if (isset($ScriptLang['beng']))
  5517. return array('beng', true);
  5518. CASE 'dev2': if (isset($ScriptLang['deva']))
  5519. return array('deva', true);
  5520. CASE 'gjr2': if (isset($ScriptLang['gujr']))
  5521. return array('gujr', true);
  5522. CASE 'gur2': if (isset($ScriptLang['guru']))
  5523. return array('guru', true);
  5524. CASE 'knd2': if (isset($ScriptLang['knda']))
  5525. return array('knda', true);
  5526. CASE 'mlm2': if (isset($ScriptLang['mlym']))
  5527. return array('mlym', true);
  5528. CASE 'ory2': if (isset($ScriptLang['orya']))
  5529. return array('orya', true);
  5530. CASE 'tml2': if (isset($ScriptLang['taml']))
  5531. return array('taml', true);
  5532. CASE 'tel2': if (isset($ScriptLang['telu']))
  5533. return array('telu', true);
  5534. CASE 'mym2': if (isset($ScriptLang['mymr']))
  5535. return array('mymr', true);
  5536. }
  5537. }
  5538. // choose DFLT if present
  5539. if (isset($ScriptLang['DFLT'])) {
  5540. return array('DFLT', false);
  5541. }
  5542. // else choose dflt if present
  5543. if (isset($ScriptLang['dflt'])) {
  5544. return array('dflt', false);
  5545. }
  5546. // else return no scriptTag
  5547. if (isset($ScriptLang['latn'])) {
  5548. return array('latn', false);
  5549. }
  5550. // else return no scriptTag
  5551. return array('', false);
  5552. }
  5553. // LangSys tags
  5554. function _getOTLLangTag($ietf, $available)
  5555. {
  5556. // http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
  5557. // http://www.microsoft.com/typography/otspec/languagetags.htm
  5558. // IETF tag = e.g. en-US, und-Arab, sr-Cyrl cf. config_lang2fonts.php
  5559. if ($available == '') {
  5560. return '';
  5561. }
  5562. $tags = preg_split('/-/', $ietf);
  5563. $lang = '';
  5564. $country = '';
  5565. $script = '';
  5566. $lang = strtolower($tags[0]);
  5567. if (isset($tags[1]) && $tags[1]) {
  5568. if (strlen($tags[1]) == 2) {
  5569. $country = strtolower($tags[1]);
  5570. }
  5571. }
  5572. if (isset($tags[2]) && $tags[2]) {
  5573. $country = strtolower($tags[2]);
  5574. }
  5575. if ($lang != '' && isset(UCDN::$ot_languages[$lang])) {
  5576. $langsys = UCDN::$ot_languages[$lang];
  5577. } else if ($lang != '' && $country != '' && isset(UCDN::$ot_languages[$lang . '' . $country])) {
  5578. $langsys = UCDN::$ot_languages[$lang . '' . $country];
  5579. } else {
  5580. $langsys = "DFLT";
  5581. }
  5582. if (strpos($available, $langsys) === false) {
  5583. if (strpos($available, "DFLT") !== false) {
  5584. return "DFLT";
  5585. } else
  5586. return '';
  5587. }
  5588. return $langsys;
  5589. }
  5590. function _dumpproc($GPOSSUB, $lookupID, $subtable, $Type, $Format, $ptr, $currGlyph, $level)
  5591. {
  5592. echo '<div style="padding-left: ' . ($level * 2) . 'em;">';
  5593. echo $GPOSSUB . ' LookupID #' . $lookupID . ' Subtable#' . $subtable . ' Type: ' . $Type . ' Format: ' . $Format . '<br />';
  5594. echo '<div style="font-family:monospace">';
  5595. echo 'Glyph position: ' . $ptr . ' Current Glyph: ' . $currGlyph . '<br />';
  5596. for ($i = 0; $i < count($this->OTLdata); $i++) {
  5597. if ($i == $ptr) {
  5598. echo '<b>';
  5599. }
  5600. echo $this->OTLdata[$i]['hex'] . ' ';
  5601. if ($i == $ptr) {
  5602. echo '</b>';
  5603. }
  5604. }
  5605. echo '<br />';
  5606. for ($i = 0; $i < count($this->OTLdata); $i++) {
  5607. if ($i == $ptr) {
  5608. echo '<b>';
  5609. }
  5610. echo str_pad($this->OTLdata[$i]['uni'], 5) . ' ';
  5611. if ($i == $ptr) {
  5612. echo '</b>';
  5613. }
  5614. }
  5615. echo '<br />';
  5616. if ($GPOSSUB == 'GPOS') {
  5617. for ($i = 0; $i < count($this->OTLdata); $i++) {
  5618. if (!empty($this->OTLdata[$i]['GPOSinfo'])) {
  5619. echo $this->OTLdata[$i]['hex'] . ' &#x' . $this->OTLdata[$i]['hex'] . '; ';
  5620. print_r($this->OTLdata[$i]['GPOSinfo']);
  5621. echo ' ';
  5622. }
  5623. }
  5624. }
  5625. echo '</div>';
  5626. echo '</div>';
  5627. }
  5628. }