123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568 |
- /**
- * https://opentype.js.org v1.3.4 | (c) Frederik De Bleser and other contributors | MIT License | Uses tiny-inflate by Devon Govett and string.prototype.codepointat polyfill by Mathias Bynens
- */
- /*! https://mths.be/codepointat v0.2.0 by @mathias */
- if (!String.prototype.codePointAt) {
- (function() {
- var defineProperty = (function() {
- // IE 8 only supports `Object.defineProperty` on DOM elements
- try {
- var object = {};
- var $defineProperty = Object.defineProperty;
- var result = $defineProperty(object, object, object) && $defineProperty;
- } catch(error) {}
- return result;
- }());
- var codePointAt = function(position) {
- if (this == null) {
- throw TypeError();
- }
- var string = String(this);
- var size = string.length;
- // `ToInteger`
- var index = position ? Number(position) : 0;
- if (index != index) { // better `isNaN`
- index = 0;
- }
- // Account for out-of-bounds indices:
- if (index < 0 || index >= size) {
- return undefined;
- }
- // Get the first code unit
- var first = string.charCodeAt(index);
- var second;
- if ( // check if it’s the start of a surrogate pair
- first >= 0xD800 && first <= 0xDBFF && // high surrogate
- size > index + 1 // there is a next code unit
- ) {
- second = string.charCodeAt(index + 1);
- if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
- // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
- return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
- }
- }
- return first;
- };
- if (defineProperty) {
- defineProperty(String.prototype, 'codePointAt', {
- 'value': codePointAt,
- 'configurable': true,
- 'writable': true
- });
- } else {
- String.prototype.codePointAt = codePointAt;
- }
- }());
- }
- var TINF_OK = 0;
- var TINF_DATA_ERROR = -3;
- function Tree() {
- this.table = new Uint16Array(16); /* table of code length counts */
- this.trans = new Uint16Array(288); /* code -> symbol translation table */
- }
- function Data(source, dest) {
- this.source = source;
- this.sourceIndex = 0;
- this.tag = 0;
- this.bitcount = 0;
-
- this.dest = dest;
- this.destLen = 0;
-
- this.ltree = new Tree(); /* dynamic length/symbol tree */
- this.dtree = new Tree(); /* dynamic distance tree */
- }
- /* --------------------------------------------------- *
- * -- uninitialized global data (static structures) -- *
- * --------------------------------------------------- */
- var sltree = new Tree();
- var sdtree = new Tree();
- /* extra bits and base tables for length codes */
- var length_bits = new Uint8Array(30);
- var length_base = new Uint16Array(30);
- /* extra bits and base tables for distance codes */
- var dist_bits = new Uint8Array(30);
- var dist_base = new Uint16Array(30);
- /* special ordering of code length codes */
- var clcidx = new Uint8Array([
- 16, 17, 18, 0, 8, 7, 9, 6,
- 10, 5, 11, 4, 12, 3, 13, 2,
- 14, 1, 15
- ]);
- /* used by tinf_decode_trees, avoids allocations every call */
- var code_tree = new Tree();
- var lengths = new Uint8Array(288 + 32);
- /* ----------------------- *
- * -- utility functions -- *
- * ----------------------- */
- /* build extra bits and base tables */
- function tinf_build_bits_base(bits, base, delta, first) {
- var i, sum;
- /* build bits table */
- for (i = 0; i < delta; ++i) { bits[i] = 0; }
- for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; }
- /* build base table */
- for (sum = first, i = 0; i < 30; ++i) {
- base[i] = sum;
- sum += 1 << bits[i];
- }
- }
- /* build the fixed huffman trees */
- function tinf_build_fixed_trees(lt, dt) {
- var i;
- /* build fixed length tree */
- for (i = 0; i < 7; ++i) { lt.table[i] = 0; }
- lt.table[7] = 24;
- lt.table[8] = 152;
- lt.table[9] = 112;
- for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; }
- for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; }
- for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; }
- for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; }
- /* build fixed distance tree */
- for (i = 0; i < 5; ++i) { dt.table[i] = 0; }
- dt.table[5] = 32;
- for (i = 0; i < 32; ++i) { dt.trans[i] = i; }
- }
- /* given an array of code lengths, build a tree */
- var offs = new Uint16Array(16);
- function tinf_build_tree(t, lengths, off, num) {
- var i, sum;
- /* clear code length count table */
- for (i = 0; i < 16; ++i) { t.table[i] = 0; }
- /* scan symbol lengths, and sum code length counts */
- for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; }
- t.table[0] = 0;
- /* compute offset table for distribution sort */
- for (sum = 0, i = 0; i < 16; ++i) {
- offs[i] = sum;
- sum += t.table[i];
- }
- /* create code->symbol translation table (symbols sorted by code) */
- for (i = 0; i < num; ++i) {
- if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; }
- }
- }
- /* ---------------------- *
- * -- decode functions -- *
- * ---------------------- */
- /* get one bit from source stream */
- function tinf_getbit(d) {
- /* check if tag is empty */
- if (!d.bitcount--) {
- /* load next tag */
- d.tag = d.source[d.sourceIndex++];
- d.bitcount = 7;
- }
- /* shift bit out of tag */
- var bit = d.tag & 1;
- d.tag >>>= 1;
- return bit;
- }
- /* read a num bit value from a stream and add base */
- function tinf_read_bits(d, num, base) {
- if (!num)
- { return base; }
- while (d.bitcount < 24) {
- d.tag |= d.source[d.sourceIndex++] << d.bitcount;
- d.bitcount += 8;
- }
- var val = d.tag & (0xffff >>> (16 - num));
- d.tag >>>= num;
- d.bitcount -= num;
- return val + base;
- }
- /* given a data stream and a tree, decode a symbol */
- function tinf_decode_symbol(d, t) {
- while (d.bitcount < 24) {
- d.tag |= d.source[d.sourceIndex++] << d.bitcount;
- d.bitcount += 8;
- }
-
- var sum = 0, cur = 0, len = 0;
- var tag = d.tag;
- /* get more bits while code value is above sum */
- do {
- cur = 2 * cur + (tag & 1);
- tag >>>= 1;
- ++len;
- sum += t.table[len];
- cur -= t.table[len];
- } while (cur >= 0);
-
- d.tag = tag;
- d.bitcount -= len;
- return t.trans[sum + cur];
- }
- /* given a data stream, decode dynamic trees from it */
- function tinf_decode_trees(d, lt, dt) {
- var hlit, hdist, hclen;
- var i, num, length;
- /* get 5 bits HLIT (257-286) */
- hlit = tinf_read_bits(d, 5, 257);
- /* get 5 bits HDIST (1-32) */
- hdist = tinf_read_bits(d, 5, 1);
- /* get 4 bits HCLEN (4-19) */
- hclen = tinf_read_bits(d, 4, 4);
- for (i = 0; i < 19; ++i) { lengths[i] = 0; }
- /* read code lengths for code length alphabet */
- for (i = 0; i < hclen; ++i) {
- /* get 3 bits code length (0-7) */
- var clen = tinf_read_bits(d, 3, 0);
- lengths[clcidx[i]] = clen;
- }
- /* build code length tree */
- tinf_build_tree(code_tree, lengths, 0, 19);
- /* decode code lengths for the dynamic trees */
- for (num = 0; num < hlit + hdist;) {
- var sym = tinf_decode_symbol(d, code_tree);
- switch (sym) {
- case 16:
- /* copy previous code length 3-6 times (read 2 bits) */
- var prev = lengths[num - 1];
- for (length = tinf_read_bits(d, 2, 3); length; --length) {
- lengths[num++] = prev;
- }
- break;
- case 17:
- /* repeat code length 0 for 3-10 times (read 3 bits) */
- for (length = tinf_read_bits(d, 3, 3); length; --length) {
- lengths[num++] = 0;
- }
- break;
- case 18:
- /* repeat code length 0 for 11-138 times (read 7 bits) */
- for (length = tinf_read_bits(d, 7, 11); length; --length) {
- lengths[num++] = 0;
- }
- break;
- default:
- /* values 0-15 represent the actual code lengths */
- lengths[num++] = sym;
- break;
- }
- }
- /* build dynamic trees */
- tinf_build_tree(lt, lengths, 0, hlit);
- tinf_build_tree(dt, lengths, hlit, hdist);
- }
- /* ----------------------------- *
- * -- block inflate functions -- *
- * ----------------------------- */
- /* given a stream and two trees, inflate a block of data */
- function tinf_inflate_block_data(d, lt, dt) {
- while (1) {
- var sym = tinf_decode_symbol(d, lt);
- /* check for end of block */
- if (sym === 256) {
- return TINF_OK;
- }
- if (sym < 256) {
- d.dest[d.destLen++] = sym;
- } else {
- var length, dist, offs;
- var i;
- sym -= 257;
- /* possibly get more bits from length code */
- length = tinf_read_bits(d, length_bits[sym], length_base[sym]);
- dist = tinf_decode_symbol(d, dt);
- /* possibly get more bits from distance code */
- offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]);
- /* copy match */
- for (i = offs; i < offs + length; ++i) {
- d.dest[d.destLen++] = d.dest[i];
- }
- }
- }
- }
- /* inflate an uncompressed block of data */
- function tinf_inflate_uncompressed_block(d) {
- var length, invlength;
- var i;
-
- /* unread from bitbuffer */
- while (d.bitcount > 8) {
- d.sourceIndex--;
- d.bitcount -= 8;
- }
- /* get length */
- length = d.source[d.sourceIndex + 1];
- length = 256 * length + d.source[d.sourceIndex];
- /* get one's complement of length */
- invlength = d.source[d.sourceIndex + 3];
- invlength = 256 * invlength + d.source[d.sourceIndex + 2];
- /* check length */
- if (length !== (~invlength & 0x0000ffff))
- { return TINF_DATA_ERROR; }
- d.sourceIndex += 4;
- /* copy block */
- for (i = length; i; --i)
- { d.dest[d.destLen++] = d.source[d.sourceIndex++]; }
- /* make sure we start next block on a byte boundary */
- d.bitcount = 0;
- return TINF_OK;
- }
- /* inflate stream from source to dest */
- function tinf_uncompress(source, dest) {
- var d = new Data(source, dest);
- var bfinal, btype, res;
- do {
- /* read final block flag */
- bfinal = tinf_getbit(d);
- /* read block type (2 bits) */
- btype = tinf_read_bits(d, 2, 0);
- /* decompress block */
- switch (btype) {
- case 0:
- /* decompress uncompressed block */
- res = tinf_inflate_uncompressed_block(d);
- break;
- case 1:
- /* decompress block with fixed huffman trees */
- res = tinf_inflate_block_data(d, sltree, sdtree);
- break;
- case 2:
- /* decompress block with dynamic huffman trees */
- tinf_decode_trees(d, d.ltree, d.dtree);
- res = tinf_inflate_block_data(d, d.ltree, d.dtree);
- break;
- default:
- res = TINF_DATA_ERROR;
- }
- if (res !== TINF_OK)
- { throw new Error('Data error'); }
- } while (!bfinal);
- if (d.destLen < d.dest.length) {
- if (typeof d.dest.slice === 'function')
- { return d.dest.slice(0, d.destLen); }
- else
- { return d.dest.subarray(0, d.destLen); }
- }
-
- return d.dest;
- }
- /* -------------------- *
- * -- initialization -- *
- * -------------------- */
- /* build fixed huffman trees */
- tinf_build_fixed_trees(sltree, sdtree);
- /* build extra bits and base tables */
- tinf_build_bits_base(length_bits, length_base, 4, 3);
- tinf_build_bits_base(dist_bits, dist_base, 2, 1);
- /* fix a special case */
- length_bits[28] = 0;
- length_base[28] = 258;
- var tinyInflate = tinf_uncompress;
- // The Bounding Box object
- function derive(v0, v1, v2, v3, t) {
- return Math.pow(1 - t, 3) * v0 +
- 3 * Math.pow(1 - t, 2) * t * v1 +
- 3 * (1 - t) * Math.pow(t, 2) * v2 +
- Math.pow(t, 3) * v3;
- }
- /**
- * A bounding box is an enclosing box that describes the smallest measure within which all the points lie.
- * It is used to calculate the bounding box of a glyph or text path.
- *
- * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`.
- *
- * @exports opentype.BoundingBox
- * @class
- * @constructor
- */
- function BoundingBox() {
- this.x1 = Number.NaN;
- this.y1 = Number.NaN;
- this.x2 = Number.NaN;
- this.y2 = Number.NaN;
- }
- /**
- * Returns true if the bounding box is empty, that is, no points have been added to the box yet.
- */
- BoundingBox.prototype.isEmpty = function() {
- return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2);
- };
- /**
- * Add the point to the bounding box.
- * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point.
- * @param {number} x - The X coordinate of the point.
- * @param {number} y - The Y coordinate of the point.
- */
- BoundingBox.prototype.addPoint = function(x, y) {
- if (typeof x === 'number') {
- if (isNaN(this.x1) || isNaN(this.x2)) {
- this.x1 = x;
- this.x2 = x;
- }
- if (x < this.x1) {
- this.x1 = x;
- }
- if (x > this.x2) {
- this.x2 = x;
- }
- }
- if (typeof y === 'number') {
- if (isNaN(this.y1) || isNaN(this.y2)) {
- this.y1 = y;
- this.y2 = y;
- }
- if (y < this.y1) {
- this.y1 = y;
- }
- if (y > this.y2) {
- this.y2 = y;
- }
- }
- };
- /**
- * Add a X coordinate to the bounding box.
- * This extends the bounding box to include the X coordinate.
- * This function is used internally inside of addBezier.
- * @param {number} x - The X coordinate of the point.
- */
- BoundingBox.prototype.addX = function(x) {
- this.addPoint(x, null);
- };
- /**
- * Add a Y coordinate to the bounding box.
- * This extends the bounding box to include the Y coordinate.
- * This function is used internally inside of addBezier.
- * @param {number} y - The Y coordinate of the point.
- */
- BoundingBox.prototype.addY = function(y) {
- this.addPoint(null, y);
- };
- /**
- * Add a Bézier curve to the bounding box.
- * This extends the bounding box to include the entire Bézier.
- * @param {number} x0 - The starting X coordinate.
- * @param {number} y0 - The starting Y coordinate.
- * @param {number} x1 - The X coordinate of the first control point.
- * @param {number} y1 - The Y coordinate of the first control point.
- * @param {number} x2 - The X coordinate of the second control point.
- * @param {number} y2 - The Y coordinate of the second control point.
- * @param {number} x - The ending X coordinate.
- * @param {number} y - The ending Y coordinate.
- */
- BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) {
- // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html
- // and https://github.com/icons8/svg-path-bounding-box
- var p0 = [x0, y0];
- var p1 = [x1, y1];
- var p2 = [x2, y2];
- var p3 = [x, y];
- this.addPoint(x0, y0);
- this.addPoint(x, y);
- for (var i = 0; i <= 1; i++) {
- var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
- var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
- var c = 3 * p1[i] - 3 * p0[i];
- if (a === 0) {
- if (b === 0) { continue; }
- var t = -c / b;
- if (0 < t && t < 1) {
- if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); }
- if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); }
- }
- continue;
- }
- var b2ac = Math.pow(b, 2) - 4 * c * a;
- if (b2ac < 0) { continue; }
- var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
- if (0 < t1 && t1 < 1) {
- if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); }
- if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); }
- }
- var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
- if (0 < t2 && t2 < 1) {
- if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); }
- if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); }
- }
- }
- };
- /**
- * Add a quadratic curve to the bounding box.
- * This extends the bounding box to include the entire quadratic curve.
- * @param {number} x0 - The starting X coordinate.
- * @param {number} y0 - The starting Y coordinate.
- * @param {number} x1 - The X coordinate of the control point.
- * @param {number} y1 - The Y coordinate of the control point.
- * @param {number} x - The ending X coordinate.
- * @param {number} y - The ending Y coordinate.
- */
- BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) {
- var cp1x = x0 + 2 / 3 * (x1 - x0);
- var cp1y = y0 + 2 / 3 * (y1 - y0);
- var cp2x = cp1x + 1 / 3 * (x - x0);
- var cp2y = cp1y + 1 / 3 * (y - y0);
- this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y);
- };
- // Geometric objects
- /**
- * A bézier path containing a set of path commands similar to a SVG path.
- * Paths can be drawn on a context using `draw`.
- * @exports opentype.Path
- * @class
- * @constructor
- */
- function Path() {
- this.commands = [];
- this.fill = 'black';
- this.stroke = null;
- this.strokeWidth = 1;
- }
- /**
- * @param {number} x
- * @param {number} y
- */
- Path.prototype.moveTo = function(x, y) {
- this.commands.push({
- type: 'M',
- x: x,
- y: y
- });
- };
- /**
- * @param {number} x
- * @param {number} y
- */
- Path.prototype.lineTo = function(x, y) {
- this.commands.push({
- type: 'L',
- x: x,
- y: y
- });
- };
- /**
- * Draws cubic curve
- * @function
- * curveTo
- * @memberof opentype.Path.prototype
- * @param {number} x1 - x of control 1
- * @param {number} y1 - y of control 1
- * @param {number} x2 - x of control 2
- * @param {number} y2 - y of control 2
- * @param {number} x - x of path point
- * @param {number} y - y of path point
- */
- /**
- * Draws cubic curve
- * @function
- * bezierCurveTo
- * @memberof opentype.Path.prototype
- * @param {number} x1 - x of control 1
- * @param {number} y1 - y of control 1
- * @param {number} x2 - x of control 2
- * @param {number} y2 - y of control 2
- * @param {number} x - x of path point
- * @param {number} y - y of path point
- * @see curveTo
- */
- Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) {
- this.commands.push({
- type: 'C',
- x1: x1,
- y1: y1,
- x2: x2,
- y2: y2,
- x: x,
- y: y
- });
- };
- /**
- * Draws quadratic curve
- * @function
- * quadraticCurveTo
- * @memberof opentype.Path.prototype
- * @param {number} x1 - x of control
- * @param {number} y1 - y of control
- * @param {number} x - x of path point
- * @param {number} y - y of path point
- */
- /**
- * Draws quadratic curve
- * @function
- * quadTo
- * @memberof opentype.Path.prototype
- * @param {number} x1 - x of control
- * @param {number} y1 - y of control
- * @param {number} x - x of path point
- * @param {number} y - y of path point
- */
- Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) {
- this.commands.push({
- type: 'Q',
- x1: x1,
- y1: y1,
- x: x,
- y: y
- });
- };
- /**
- * Closes the path
- * @function closePath
- * @memberof opentype.Path.prototype
- */
- /**
- * Close the path
- * @function close
- * @memberof opentype.Path.prototype
- */
- Path.prototype.close = Path.prototype.closePath = function() {
- this.commands.push({
- type: 'Z'
- });
- };
- /**
- * Add the given path or list of commands to the commands of this path.
- * @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands.
- */
- Path.prototype.extend = function(pathOrCommands) {
- if (pathOrCommands.commands) {
- pathOrCommands = pathOrCommands.commands;
- } else if (pathOrCommands instanceof BoundingBox) {
- var box = pathOrCommands;
- this.moveTo(box.x1, box.y1);
- this.lineTo(box.x2, box.y1);
- this.lineTo(box.x2, box.y2);
- this.lineTo(box.x1, box.y2);
- this.close();
- return;
- }
- Array.prototype.push.apply(this.commands, pathOrCommands);
- };
- /**
- * Calculate the bounding box of the path.
- * @returns {opentype.BoundingBox}
- */
- Path.prototype.getBoundingBox = function() {
- var box = new BoundingBox();
- var startX = 0;
- var startY = 0;
- var prevX = 0;
- var prevY = 0;
- for (var i = 0; i < this.commands.length; i++) {
- var cmd = this.commands[i];
- switch (cmd.type) {
- case 'M':
- box.addPoint(cmd.x, cmd.y);
- startX = prevX = cmd.x;
- startY = prevY = cmd.y;
- break;
- case 'L':
- box.addPoint(cmd.x, cmd.y);
- prevX = cmd.x;
- prevY = cmd.y;
- break;
- case 'Q':
- box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y);
- prevX = cmd.x;
- prevY = cmd.y;
- break;
- case 'C':
- box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
- prevX = cmd.x;
- prevY = cmd.y;
- break;
- case 'Z':
- prevX = startX;
- prevY = startY;
- break;
- default:
- throw new Error('Unexpected path command ' + cmd.type);
- }
- }
- if (box.isEmpty()) {
- box.addPoint(0, 0);
- }
- return box;
- };
- /**
- * Draw the path to a 2D context.
- * @param {CanvasRenderingContext2D} ctx - A 2D drawing context.
- */
- Path.prototype.draw = function(ctx) {
- ctx.beginPath();
- for (var i = 0; i < this.commands.length; i += 1) {
- var cmd = this.commands[i];
- if (cmd.type === 'M') {
- ctx.moveTo(cmd.x, cmd.y);
- } else if (cmd.type === 'L') {
- ctx.lineTo(cmd.x, cmd.y);
- } else if (cmd.type === 'C') {
- ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
- } else if (cmd.type === 'Q') {
- ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y);
- } else if (cmd.type === 'Z') {
- ctx.closePath();
- }
- }
- if (this.fill) {
- ctx.fillStyle = this.fill;
- ctx.fill();
- }
- if (this.stroke) {
- ctx.strokeStyle = this.stroke;
- ctx.lineWidth = this.strokeWidth;
- ctx.stroke();
- }
- };
- /**
- * Convert the Path to a string of path data instructions
- * See http://www.w3.org/TR/SVG/paths.html#PathData
- * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values
- * @return {string}
- */
- Path.prototype.toPathData = function(decimalPlaces) {
- decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2;
- function floatToString(v) {
- if (Math.round(v) === v) {
- return '' + Math.round(v);
- } else {
- return v.toFixed(decimalPlaces);
- }
- }
- function packValues() {
- var arguments$1 = arguments;
- var s = '';
- for (var i = 0; i < arguments.length; i += 1) {
- var v = arguments$1[i];
- if (v >= 0 && i > 0) {
- s += ' ';
- }
- s += floatToString(v);
- }
- return s;
- }
- var d = '';
- for (var i = 0; i < this.commands.length; i += 1) {
- var cmd = this.commands[i];
- if (cmd.type === 'M') {
- d += 'M' + packValues(cmd.x, cmd.y);
- } else if (cmd.type === 'L') {
- d += 'L' + packValues(cmd.x, cmd.y);
- } else if (cmd.type === 'C') {
- d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
- } else if (cmd.type === 'Q') {
- d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y);
- } else if (cmd.type === 'Z') {
- d += 'Z';
- }
- }
- return d;
- };
- /**
- * Convert the path to an SVG <path> element, as a string.
- * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values
- * @return {string}
- */
- Path.prototype.toSVG = function(decimalPlaces) {
- var svg = '<path d="';
- svg += this.toPathData(decimalPlaces);
- svg += '"';
- if (this.fill && this.fill !== 'black') {
- if (this.fill === null) {
- svg += ' fill="none"';
- } else {
- svg += ' fill="' + this.fill + '"';
- }
- }
- if (this.stroke) {
- svg += ' stroke="' + this.stroke + '" stroke-width="' + this.strokeWidth + '"';
- }
- svg += '/>';
- return svg;
- };
- /**
- * Convert the path to a DOM element.
- * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values
- * @return {SVGPathElement}
- */
- Path.prototype.toDOMElement = function(decimalPlaces) {
- var temporaryPath = this.toPathData(decimalPlaces);
- var newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
- newPath.setAttribute('d', temporaryPath);
- return newPath;
- };
- // Run-time checking of preconditions.
- function fail(message) {
- throw new Error(message);
- }
- // Precondition function that checks if the given predicate is true.
- // If not, it will throw an error.
- function argument(predicate, message) {
- if (!predicate) {
- fail(message);
- }
- }
- var check = { fail: fail, argument: argument, assert: argument };
- // Data types used in the OpenType font file.
- var LIMIT16 = 32768; // The limit at which a 16-bit number switches signs == 2^15
- var LIMIT32 = 2147483648; // The limit at which a 32-bit number switches signs == 2 ^ 31
- /**
- * @exports opentype.decode
- * @class
- */
- var decode = {};
- /**
- * @exports opentype.encode
- * @class
- */
- var encode = {};
- /**
- * @exports opentype.sizeOf
- * @class
- */
- var sizeOf = {};
- // Return a function that always returns the same value.
- function constant(v) {
- return function() {
- return v;
- };
- }
- // OpenType data types //////////////////////////////////////////////////////
- /**
- * Convert an 8-bit unsigned integer to a list of 1 byte.
- * @param {number}
- * @returns {Array}
- */
- encode.BYTE = function(v) {
- check.argument(v >= 0 && v <= 255, 'Byte value should be between 0 and 255.');
- return [v];
- };
- /**
- * @constant
- * @type {number}
- */
- sizeOf.BYTE = constant(1);
- /**
- * Convert a 8-bit signed integer to a list of 1 byte.
- * @param {string}
- * @returns {Array}
- */
- encode.CHAR = function(v) {
- return [v.charCodeAt(0)];
- };
- /**
- * @constant
- * @type {number}
- */
- sizeOf.CHAR = constant(1);
- /**
- * Convert an ASCII string to a list of bytes.
- * @param {string}
- * @returns {Array}
- */
- encode.CHARARRAY = function(v) {
- if (typeof v === 'undefined') {
- v = '';
- console.warn('Undefined CHARARRAY encountered and treated as an empty string. This is probably caused by a missing glyph name.');
- }
- var b = [];
- for (var i = 0; i < v.length; i += 1) {
- b[i] = v.charCodeAt(i);
- }
- return b;
- };
- /**
- * @param {Array}
- * @returns {number}
- */
- sizeOf.CHARARRAY = function(v) {
- if (typeof v === 'undefined') {
- return 0;
- }
- return v.length;
- };
- /**
- * Convert a 16-bit unsigned integer to a list of 2 bytes.
- * @param {number}
- * @returns {Array}
- */
- encode.USHORT = function(v) {
- return [(v >> 8) & 0xFF, v & 0xFF];
- };
- /**
- * @constant
- * @type {number}
- */
- sizeOf.USHORT = constant(2);
- /**
- * Convert a 16-bit signed integer to a list of 2 bytes.
- * @param {number}
- * @returns {Array}
- */
- encode.SHORT = function(v) {
- // Two's complement
- if (v >= LIMIT16) {
- v = -(2 * LIMIT16 - v);
- }
- return [(v >> 8) & 0xFF, v & 0xFF];
- };
- /**
- * @constant
- * @type {number}
- */
- sizeOf.SHORT = constant(2);
- /**
- * Convert a 24-bit unsigned integer to a list of 3 bytes.
- * @param {number}
- * @returns {Array}
- */
- encode.UINT24 = function(v) {
- return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
- };
- /**
- * @constant
- * @type {number}
- */
- sizeOf.UINT24 = constant(3);
- /**
- * Convert a 32-bit unsigned integer to a list of 4 bytes.
- * @param {number}
- * @returns {Array}
- */
- encode.ULONG = function(v) {
- return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
- };
- /**
- * @constant
- * @type {number}
- */
- sizeOf.ULONG = constant(4);
- /**
- * Convert a 32-bit unsigned integer to a list of 4 bytes.
- * @param {number}
- * @returns {Array}
- */
- encode.LONG = function(v) {
- // Two's complement
- if (v >= LIMIT32) {
- v = -(2 * LIMIT32 - v);
- }
- return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
- };
- /**
- * @constant
- * @type {number}
- */
- sizeOf.LONG = constant(4);
- encode.FIXED = encode.ULONG;
- sizeOf.FIXED = sizeOf.ULONG;
- encode.FWORD = encode.SHORT;
- sizeOf.FWORD = sizeOf.SHORT;
- encode.UFWORD = encode.USHORT;
- sizeOf.UFWORD = sizeOf.USHORT;
- /**
- * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp.
- * @param {number}
- * @returns {Array}
- */
- encode.LONGDATETIME = function(v) {
- return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
- };
- /**
- * @constant
- * @type {number}
- */
- sizeOf.LONGDATETIME = constant(8);
- /**
- * Convert a 4-char tag to a list of 4 bytes.
- * @param {string}
- * @returns {Array}
- */
- encode.TAG = function(v) {
- check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.');
- return [v.charCodeAt(0),
- v.charCodeAt(1),
- v.charCodeAt(2),
- v.charCodeAt(3)];
- };
- /**
- * @constant
- * @type {number}
- */
- sizeOf.TAG = constant(4);
- // CFF data types ///////////////////////////////////////////////////////////
- encode.Card8 = encode.BYTE;
- sizeOf.Card8 = sizeOf.BYTE;
- encode.Card16 = encode.USHORT;
- sizeOf.Card16 = sizeOf.USHORT;
- encode.OffSize = encode.BYTE;
- sizeOf.OffSize = sizeOf.BYTE;
- encode.SID = encode.USHORT;
- sizeOf.SID = sizeOf.USHORT;
- // Convert a numeric operand or charstring number to a variable-size list of bytes.
- /**
- * Convert a numeric operand or charstring number to a variable-size list of bytes.
- * @param {number}
- * @returns {Array}
- */
- encode.NUMBER = function(v) {
- if (v >= -107 && v <= 107) {
- return [v + 139];
- } else if (v >= 108 && v <= 1131) {
- v = v - 108;
- return [(v >> 8) + 247, v & 0xFF];
- } else if (v >= -1131 && v <= -108) {
- v = -v - 108;
- return [(v >> 8) + 251, v & 0xFF];
- } else if (v >= -32768 && v <= 32767) {
- return encode.NUMBER16(v);
- } else {
- return encode.NUMBER32(v);
- }
- };
- /**
- * @param {number}
- * @returns {number}
- */
- sizeOf.NUMBER = function(v) {
- return encode.NUMBER(v).length;
- };
- /**
- * Convert a signed number between -32768 and +32767 to a three-byte value.
- * This ensures we always use three bytes, but is not the most compact format.
- * @param {number}
- * @returns {Array}
- */
- encode.NUMBER16 = function(v) {
- return [28, (v >> 8) & 0xFF, v & 0xFF];
- };
- /**
- * @constant
- * @type {number}
- */
- sizeOf.NUMBER16 = constant(3);
- /**
- * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value.
- * This is useful if you want to be sure you always use four bytes,
- * at the expense of wasting a few bytes for smaller numbers.
- * @param {number}
- * @returns {Array}
- */
- encode.NUMBER32 = function(v) {
- return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
- };
- /**
- * @constant
- * @type {number}
- */
- sizeOf.NUMBER32 = constant(5);
- /**
- * @param {number}
- * @returns {Array}
- */
- encode.REAL = function(v) {
- var value = v.toString();
- // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7)
- // This code converts it back to a number without the epsilon.
- var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value);
- if (m) {
- var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length));
- value = (Math.round(v * epsilon) / epsilon).toString();
- }
- var nibbles = '';
- for (var i = 0, ii = value.length; i < ii; i += 1) {
- var c = value[i];
- if (c === 'e') {
- nibbles += value[++i] === '-' ? 'c' : 'b';
- } else if (c === '.') {
- nibbles += 'a';
- } else if (c === '-') {
- nibbles += 'e';
- } else {
- nibbles += c;
- }
- }
- nibbles += (nibbles.length & 1) ? 'f' : 'ff';
- var out = [30];
- for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) {
- out.push(parseInt(nibbles.substr(i$1, 2), 16));
- }
- return out;
- };
- /**
- * @param {number}
- * @returns {number}
- */
- sizeOf.REAL = function(v) {
- return encode.REAL(v).length;
- };
- encode.NAME = encode.CHARARRAY;
- sizeOf.NAME = sizeOf.CHARARRAY;
- encode.STRING = encode.CHARARRAY;
- sizeOf.STRING = sizeOf.CHARARRAY;
- /**
- * @param {DataView} data
- * @param {number} offset
- * @param {number} numBytes
- * @returns {string}
- */
- decode.UTF8 = function(data, offset, numBytes) {
- var codePoints = [];
- var numChars = numBytes;
- for (var j = 0; j < numChars; j++, offset += 1) {
- codePoints[j] = data.getUint8(offset);
- }
- return String.fromCharCode.apply(null, codePoints);
- };
- /**
- * @param {DataView} data
- * @param {number} offset
- * @param {number} numBytes
- * @returns {string}
- */
- decode.UTF16 = function(data, offset, numBytes) {
- var codePoints = [];
- var numChars = numBytes / 2;
- for (var j = 0; j < numChars; j++, offset += 2) {
- codePoints[j] = data.getUint16(offset);
- }
- return String.fromCharCode.apply(null, codePoints);
- };
- /**
- * Convert a JavaScript string to UTF16-BE.
- * @param {string}
- * @returns {Array}
- */
- encode.UTF16 = function(v) {
- var b = [];
- for (var i = 0; i < v.length; i += 1) {
- var codepoint = v.charCodeAt(i);
- b[b.length] = (codepoint >> 8) & 0xFF;
- b[b.length] = codepoint & 0xFF;
- }
- return b;
- };
- /**
- * @param {string}
- * @returns {number}
- */
- sizeOf.UTF16 = function(v) {
- return v.length * 2;
- };
- // Data for converting old eight-bit Macintosh encodings to Unicode.
- // This representation is optimized for decoding; encoding is slower
- // and needs more memory. The assumption is that all opentype.js users
- // want to open fonts, but saving a font will be comparatively rare
- // so it can be more expensive. Keyed by IANA character set name.
- //
- // Python script for generating these strings:
- //
- // s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)])
- // print(s.encode('utf-8'))
- /**
- * @private
- */
- var eightBitMacEncodings = {
- 'x-mac-croatian': // Python: 'mac_croatian'
- 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' +
- '¿¡¬√ƒ≈Ć«Č… ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ',
- 'x-mac-cyrillic': // Python: 'mac_cyrillic'
- 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' +
- 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю',
- 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT
- 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' +
- 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ',
- 'x-mac-greek': // Python: 'mac_greek'
- 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' +
- 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD',
- 'x-mac-icelandic': // Python: 'mac_iceland'
- 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüÝ°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' +
- '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ',
- 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT
- 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' +
- 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł',
- 'x-mac-ce': // Python: 'mac_latin2'
- 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' +
- 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ',
- macintosh: // Python: 'mac_roman'
- 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' +
- '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ',
- 'x-mac-romanian': // Python: 'mac_romanian'
- 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' +
- '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ',
- 'x-mac-turkish': // Python: 'mac_turkish'
- 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' +
- '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ'
- };
- /**
- * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript
- * string, or 'undefined' if the encoding is unsupported. For example, we do
- * not support Chinese, Japanese or Korean because these would need large
- * mapping tables.
- * @param {DataView} dataView
- * @param {number} offset
- * @param {number} dataLength
- * @param {string} encoding
- * @returns {string}
- */
- decode.MACSTRING = function(dataView, offset, dataLength, encoding) {
- var table = eightBitMacEncodings[encoding];
- if (table === undefined) {
- return undefined;
- }
- var result = '';
- for (var i = 0; i < dataLength; i++) {
- var c = dataView.getUint8(offset + i);
- // In all eight-bit Mac encodings, the characters 0x00..0x7F are
- // mapped to U+0000..U+007F; we only need to look up the others.
- if (c <= 0x7F) {
- result += String.fromCharCode(c);
- } else {
- result += table[c & 0x7F];
- }
- }
- return result;
- };
- // Helper function for encode.MACSTRING. Returns a dictionary for mapping
- // Unicode character codes to their 8-bit MacOS equivalent. This table
- // is not exactly a super cheap data structure, but we do not care because
- // encoding Macintosh strings is only rarely needed in typical applications.
- var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap();
- var macEncodingCacheKeys;
- var getMacEncodingTable = function (encoding) {
- // Since we use encoding as a cache key for WeakMap, it has to be
- // a String object and not a literal. And at least on NodeJS 2.10.1,
- // WeakMap requires that the same String instance is passed for cache hits.
- if (!macEncodingCacheKeys) {
- macEncodingCacheKeys = {};
- for (var e in eightBitMacEncodings) {
- /*jshint -W053 */ // Suppress "Do not use String as a constructor."
- macEncodingCacheKeys[e] = new String(e);
- }
- }
- var cacheKey = macEncodingCacheKeys[encoding];
- if (cacheKey === undefined) {
- return undefined;
- }
- // We can't do "if (cache.has(key)) {return cache.get(key)}" here:
- // since garbage collection may run at any time, it could also kick in
- // between the calls to cache.has() and cache.get(). In that case,
- // we would return 'undefined' even though we do support the encoding.
- if (macEncodingTableCache) {
- var cachedTable = macEncodingTableCache.get(cacheKey);
- if (cachedTable !== undefined) {
- return cachedTable;
- }
- }
- var decodingTable = eightBitMacEncodings[encoding];
- if (decodingTable === undefined) {
- return undefined;
- }
- var encodingTable = {};
- for (var i = 0; i < decodingTable.length; i++) {
- encodingTable[decodingTable.charCodeAt(i)] = i + 0x80;
- }
- if (macEncodingTableCache) {
- macEncodingTableCache.set(cacheKey, encodingTable);
- }
- return encodingTable;
- };
- /**
- * Encodes an old-style Macintosh string. Returns a byte array upon success.
- * If the requested encoding is unsupported, or if the input string contains
- * a character that cannot be expressed in the encoding, the function returns
- * 'undefined'.
- * @param {string} str
- * @param {string} encoding
- * @returns {Array}
- */
- encode.MACSTRING = function(str, encoding) {
- var table = getMacEncodingTable(encoding);
- if (table === undefined) {
- return undefined;
- }
- var result = [];
- for (var i = 0; i < str.length; i++) {
- var c = str.charCodeAt(i);
- // In all eight-bit Mac encodings, the characters 0x00..0x7F are
- // mapped to U+0000..U+007F; we only need to look up the others.
- if (c >= 0x80) {
- c = table[c];
- if (c === undefined) {
- // str contains a Unicode character that cannot be encoded
- // in the requested encoding.
- return undefined;
- }
- }
- result[i] = c;
- // result.push(c);
- }
- return result;
- };
- /**
- * @param {string} str
- * @param {string} encoding
- * @returns {number}
- */
- sizeOf.MACSTRING = function(str, encoding) {
- var b = encode.MACSTRING(str, encoding);
- if (b !== undefined) {
- return b.length;
- } else {
- return 0;
- }
- };
- // Helper for encode.VARDELTAS
- function isByteEncodable(value) {
- return value >= -128 && value <= 127;
- }
- // Helper for encode.VARDELTAS
- function encodeVarDeltaRunAsZeroes(deltas, pos, result) {
- var runLength = 0;
- var numDeltas = deltas.length;
- while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) {
- ++pos;
- ++runLength;
- }
- result.push(0x80 | (runLength - 1));
- return pos;
- }
- // Helper for encode.VARDELTAS
- function encodeVarDeltaRunAsBytes(deltas, offset, result) {
- var runLength = 0;
- var numDeltas = deltas.length;
- var pos = offset;
- while (pos < numDeltas && runLength < 64) {
- var value = deltas[pos];
- if (!isByteEncodable(value)) {
- break;
- }
- // Within a byte-encoded run of deltas, a single zero is best
- // stored literally as 0x00 value. However, if we have two or
- // more zeroes in a sequence, it is better to start a new run.
- // Fore example, the sequence of deltas [15, 15, 0, 15, 15]
- // becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero
- // within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F)
- // when starting a new run.
- if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) {
- break;
- }
- ++pos;
- ++runLength;
- }
- result.push(runLength - 1);
- for (var i = offset; i < pos; ++i) {
- result.push((deltas[i] + 256) & 0xff);
- }
- return pos;
- }
- // Helper for encode.VARDELTAS
- function encodeVarDeltaRunAsWords(deltas, offset, result) {
- var runLength = 0;
- var numDeltas = deltas.length;
- var pos = offset;
- while (pos < numDeltas && runLength < 64) {
- var value = deltas[pos];
- // Within a word-encoded run of deltas, it is easiest to start
- // a new run (with a different encoding) whenever we encounter
- // a zero value. For example, the sequence [0x6666, 0, 0x7777]
- // needs 7 bytes when storing the zero inside the current run
- // (42 66 66 00 00 77 77), and equally 7 bytes when starting a
- // new run (40 66 66 80 40 77 77).
- if (value === 0) {
- break;
- }
- // Within a word-encoded run of deltas, a single value in the
- // range (-128..127) should be encoded within the current run
- // because it is more compact. For example, the sequence
- // [0x6666, 2, 0x7777] becomes 7 bytes when storing the value
- // literally (42 66 66 00 02 77 77), but 8 bytes when starting
- // a new run (40 66 66 00 02 40 77 77).
- if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) {
- break;
- }
- ++pos;
- ++runLength;
- }
- result.push(0x40 | (runLength - 1));
- for (var i = offset; i < pos; ++i) {
- var val = deltas[i];
- result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff);
- }
- return pos;
- }
- /**
- * Encode a list of variation adjustment deltas.
- *
- * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables.
- * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted
- * when generating instances of variation fonts.
- *
- * @see https://www.microsoft.com/typography/otspec/gvar.htm
- * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
- * @param {Array}
- * @return {Array}
- */
- encode.VARDELTAS = function(deltas) {
- var pos = 0;
- var result = [];
- while (pos < deltas.length) {
- var value = deltas[pos];
- if (value === 0) {
- pos = encodeVarDeltaRunAsZeroes(deltas, pos, result);
- } else if (value >= -128 && value <= 127) {
- pos = encodeVarDeltaRunAsBytes(deltas, pos, result);
- } else {
- pos = encodeVarDeltaRunAsWords(deltas, pos, result);
- }
- }
- return result;
- };
- // Convert a list of values to a CFF INDEX structure.
- // The values should be objects containing name / type / value.
- /**
- * @param {Array} l
- * @returns {Array}
- */
- encode.INDEX = function(l) {
- //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data,
- // i, v;
- // Because we have to know which data type to use to encode the offsets,
- // we have to go through the values twice: once to encode the data and
- // calculate the offsets, then again to encode the offsets using the fitting data type.
- var offset = 1; // First offset is always 1.
- var offsets = [offset];
- var data = [];
- for (var i = 0; i < l.length; i += 1) {
- var v = encode.OBJECT(l[i]);
- Array.prototype.push.apply(data, v);
- offset += v.length;
- offsets.push(offset);
- }
- if (data.length === 0) {
- return [0, 0];
- }
- var encodedOffsets = [];
- var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0;
- var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize];
- for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) {
- var encodedOffset = offsetEncoder(offsets[i$1]);
- Array.prototype.push.apply(encodedOffsets, encodedOffset);
- }
- return Array.prototype.concat(encode.Card16(l.length),
- encode.OffSize(offSize),
- encodedOffsets,
- data);
- };
- /**
- * @param {Array}
- * @returns {number}
- */
- sizeOf.INDEX = function(v) {
- return encode.INDEX(v).length;
- };
- /**
- * Convert an object to a CFF DICT structure.
- * The keys should be numeric.
- * The values should be objects containing name / type / value.
- * @param {Object} m
- * @returns {Array}
- */
- encode.DICT = function(m) {
- var d = [];
- var keys = Object.keys(m);
- var length = keys.length;
- for (var i = 0; i < length; i += 1) {
- // Object.keys() return string keys, but our keys are always numeric.
- var k = parseInt(keys[i], 0);
- var v = m[k];
- // Value comes before the key.
- d = d.concat(encode.OPERAND(v.value, v.type));
- d = d.concat(encode.OPERATOR(k));
- }
- return d;
- };
- /**
- * @param {Object}
- * @returns {number}
- */
- sizeOf.DICT = function(m) {
- return encode.DICT(m).length;
- };
- /**
- * @param {number}
- * @returns {Array}
- */
- encode.OPERATOR = function(v) {
- if (v < 1200) {
- return [v];
- } else {
- return [12, v - 1200];
- }
- };
- /**
- * @param {Array} v
- * @param {string}
- * @returns {Array}
- */
- encode.OPERAND = function(v, type) {
- var d = [];
- if (Array.isArray(type)) {
- for (var i = 0; i < type.length; i += 1) {
- check.argument(v.length === type.length, 'Not enough arguments given for type' + type);
- d = d.concat(encode.OPERAND(v[i], type[i]));
- }
- } else {
- if (type === 'SID') {
- d = d.concat(encode.NUMBER(v));
- } else if (type === 'offset') {
- // We make it easy for ourselves and always encode offsets as
- // 4 bytes. This makes offset calculation for the top dict easier.
- d = d.concat(encode.NUMBER32(v));
- } else if (type === 'number') {
- d = d.concat(encode.NUMBER(v));
- } else if (type === 'real') {
- d = d.concat(encode.REAL(v));
- } else {
- throw new Error('Unknown operand type ' + type);
- // FIXME Add support for booleans
- }
- }
- return d;
- };
- encode.OP = encode.BYTE;
- sizeOf.OP = sizeOf.BYTE;
- // memoize charstring encoding using WeakMap if available
- var wmm = typeof WeakMap === 'function' && new WeakMap();
- /**
- * Convert a list of CharString operations to bytes.
- * @param {Array}
- * @returns {Array}
- */
- encode.CHARSTRING = function(ops) {
- // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))".
- if (wmm) {
- var cachedValue = wmm.get(ops);
- if (cachedValue !== undefined) {
- return cachedValue;
- }
- }
- var d = [];
- var length = ops.length;
- for (var i = 0; i < length; i += 1) {
- var op = ops[i];
- d = d.concat(encode[op.type](op.value));
- }
- if (wmm) {
- wmm.set(ops, d);
- }
- return d;
- };
- /**
- * @param {Array}
- * @returns {number}
- */
- sizeOf.CHARSTRING = function(ops) {
- return encode.CHARSTRING(ops).length;
- };
- // Utility functions ////////////////////////////////////////////////////////
- /**
- * Convert an object containing name / type / value to bytes.
- * @param {Object}
- * @returns {Array}
- */
- encode.OBJECT = function(v) {
- var encodingFunction = encode[v.type];
- check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type);
- return encodingFunction(v.value);
- };
- /**
- * @param {Object}
- * @returns {number}
- */
- sizeOf.OBJECT = function(v) {
- var sizeOfFunction = sizeOf[v.type];
- check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type);
- return sizeOfFunction(v.value);
- };
- /**
- * Convert a table object to bytes.
- * A table contains a list of fields containing the metadata (name, type and default value).
- * The table itself has the field values set as attributes.
- * @param {opentype.Table}
- * @returns {Array}
- */
- encode.TABLE = function(table) {
- var d = [];
- var length = table.fields.length;
- var subtables = [];
- var subtableOffsets = [];
- for (var i = 0; i < length; i += 1) {
- var field = table.fields[i];
- var encodingFunction = encode[field.type];
- check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')');
- var value = table[field.name];
- if (value === undefined) {
- value = field.value;
- }
- var bytes = encodingFunction(value);
- if (field.type === 'TABLE') {
- subtableOffsets.push(d.length);
- d = d.concat([0, 0]);
- subtables.push(bytes);
- } else {
- d = d.concat(bytes);
- }
- }
- for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) {
- var o = subtableOffsets[i$1];
- var offset = d.length;
- check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.');
- d[o] = offset >> 8;
- d[o + 1] = offset & 0xff;
- d = d.concat(subtables[i$1]);
- }
- return d;
- };
- /**
- * @param {opentype.Table}
- * @returns {number}
- */
- sizeOf.TABLE = function(table) {
- var numBytes = 0;
- var length = table.fields.length;
- for (var i = 0; i < length; i += 1) {
- var field = table.fields[i];
- var sizeOfFunction = sizeOf[field.type];
- check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')');
- var value = table[field.name];
- if (value === undefined) {
- value = field.value;
- }
- numBytes += sizeOfFunction(value);
- // Subtables take 2 more bytes for offsets.
- if (field.type === 'TABLE') {
- numBytes += 2;
- }
- }
- return numBytes;
- };
- encode.RECORD = encode.TABLE;
- sizeOf.RECORD = sizeOf.TABLE;
- // Merge in a list of bytes.
- encode.LITERAL = function(v) {
- return v;
- };
- sizeOf.LITERAL = function(v) {
- return v.length;
- };
- // Table metadata
- /**
- * @exports opentype.Table
- * @class
- * @param {string} tableName
- * @param {Array} fields
- * @param {Object} options
- * @constructor
- */
- function Table(tableName, fields, options) {
- // For coverage tables with coverage format 2, we do not want to add the coverage data directly to the table object,
- // as this will result in wrong encoding order of the coverage data on serialization to bytes.
- // The fallback of using the field values directly when not present on the table is handled in types.encode.TABLE() already.
- if (fields.length && (fields[0].name !== 'coverageFormat' || fields[0].value === 1)) {
- for (var i = 0; i < fields.length; i += 1) {
- var field = fields[i];
- this[field.name] = field.value;
- }
- }
- this.tableName = tableName;
- this.fields = fields;
- if (options) {
- var optionKeys = Object.keys(options);
- for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) {
- var k = optionKeys[i$1];
- var v = options[k];
- if (this[k] !== undefined) {
- this[k] = v;
- }
- }
- }
- }
- /**
- * Encodes the table and returns an array of bytes
- * @return {Array}
- */
- Table.prototype.encode = function() {
- return encode.TABLE(this);
- };
- /**
- * Get the size of the table.
- * @return {number}
- */
- Table.prototype.sizeOf = function() {
- return sizeOf.TABLE(this);
- };
- /**
- * @private
- */
- function ushortList(itemName, list, count) {
- if (count === undefined) {
- count = list.length;
- }
- var fields = new Array(list.length + 1);
- fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count};
- for (var i = 0; i < list.length; i++) {
- fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]};
- }
- return fields;
- }
- /**
- * @private
- */
- function tableList(itemName, records, itemCallback) {
- var count = records.length;
- var fields = new Array(count + 1);
- fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count};
- for (var i = 0; i < count; i++) {
- fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)};
- }
- return fields;
- }
- /**
- * @private
- */
- function recordList(itemName, records, itemCallback) {
- var count = records.length;
- var fields = [];
- fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count};
- for (var i = 0; i < count; i++) {
- fields = fields.concat(itemCallback(records[i], i));
- }
- return fields;
- }
- // Common Layout Tables
- /**
- * @exports opentype.Coverage
- * @class
- * @param {opentype.Table}
- * @constructor
- * @extends opentype.Table
- */
- function Coverage(coverageTable) {
- if (coverageTable.format === 1) {
- Table.call(this, 'coverageTable',
- [{name: 'coverageFormat', type: 'USHORT', value: 1}]
- .concat(ushortList('glyph', coverageTable.glyphs))
- );
- } else if (coverageTable.format === 2) {
- Table.call(this, 'coverageTable',
- [{name: 'coverageFormat', type: 'USHORT', value: 2}]
- .concat(recordList('rangeRecord', coverageTable.ranges, function(RangeRecord) {
- return [
- {name: 'startGlyphID', type: 'USHORT', value: RangeRecord.start},
- {name: 'endGlyphID', type: 'USHORT', value: RangeRecord.end},
- {name: 'startCoverageIndex', type: 'USHORT', value: RangeRecord.index} ];
- }))
- );
- } else {
- check.assert(false, 'Coverage format must be 1 or 2.');
- }
- }
- Coverage.prototype = Object.create(Table.prototype);
- Coverage.prototype.constructor = Coverage;
- function ScriptList(scriptListTable) {
- Table.call(this, 'scriptListTable',
- recordList('scriptRecord', scriptListTable, function(scriptRecord, i) {
- var script = scriptRecord.script;
- var defaultLangSys = script.defaultLangSys;
- check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.');
- return [
- {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag},
- {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [
- {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [
- {name: 'lookupOrder', type: 'USHORT', value: 0},
- {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}]
- .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))}
- ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) {
- var langSys = langSysRecord.langSys;
- return [
- {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag},
- {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [
- {name: 'lookupOrder', type: 'USHORT', value: 0},
- {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex}
- ].concat(ushortList('featureIndex', langSys.featureIndexes)))}
- ];
- })))}
- ];
- })
- );
- }
- ScriptList.prototype = Object.create(Table.prototype);
- ScriptList.prototype.constructor = ScriptList;
- /**
- * @exports opentype.FeatureList
- * @class
- * @param {opentype.Table}
- * @constructor
- * @extends opentype.Table
- */
- function FeatureList(featureListTable) {
- Table.call(this, 'featureListTable',
- recordList('featureRecord', featureListTable, function(featureRecord, i) {
- var feature = featureRecord.feature;
- return [
- {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag},
- {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [
- {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))}
- ];
- })
- );
- }
- FeatureList.prototype = Object.create(Table.prototype);
- FeatureList.prototype.constructor = FeatureList;
- /**
- * @exports opentype.LookupList
- * @class
- * @param {opentype.Table}
- * @param {Object}
- * @constructor
- * @extends opentype.Table
- */
- function LookupList(lookupListTable, subtableMakers) {
- Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) {
- var subtableCallback = subtableMakers[lookupTable.lookupType];
- check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.');
- return new Table('lookupTable', [
- {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType},
- {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag}
- ].concat(tableList('subtable', lookupTable.subtables, subtableCallback)));
- }));
- }
- LookupList.prototype = Object.create(Table.prototype);
- LookupList.prototype.constructor = LookupList;
- // Record = same as Table, but inlined (a Table has an offset and its data is further in the stream)
- // Don't use offsets inside Records (probable bug), only in Tables.
- var table = {
- Table: Table,
- Record: Table,
- Coverage: Coverage,
- ScriptList: ScriptList,
- FeatureList: FeatureList,
- LookupList: LookupList,
- ushortList: ushortList,
- tableList: tableList,
- recordList: recordList,
- };
- // Parsing utility functions
- // Retrieve an unsigned byte from the DataView.
- function getByte(dataView, offset) {
- return dataView.getUint8(offset);
- }
- // Retrieve an unsigned 16-bit short from the DataView.
- // The value is stored in big endian.
- function getUShort(dataView, offset) {
- return dataView.getUint16(offset, false);
- }
- // Retrieve a signed 16-bit short from the DataView.
- // The value is stored in big endian.
- function getShort(dataView, offset) {
- return dataView.getInt16(offset, false);
- }
- // Retrieve an unsigned 32-bit long from the DataView.
- // The value is stored in big endian.
- function getULong(dataView, offset) {
- return dataView.getUint32(offset, false);
- }
- // Retrieve a 32-bit signed fixed-point number (16.16) from the DataView.
- // The value is stored in big endian.
- function getFixed(dataView, offset) {
- var decimal = dataView.getInt16(offset, false);
- var fraction = dataView.getUint16(offset + 2, false);
- return decimal + fraction / 65535;
- }
- // Retrieve a 4-character tag from the DataView.
- // Tags are used to identify tables.
- function getTag(dataView, offset) {
- var tag = '';
- for (var i = offset; i < offset + 4; i += 1) {
- tag += String.fromCharCode(dataView.getInt8(i));
- }
- return tag;
- }
- // Retrieve an offset from the DataView.
- // Offsets are 1 to 4 bytes in length, depending on the offSize argument.
- function getOffset(dataView, offset, offSize) {
- var v = 0;
- for (var i = 0; i < offSize; i += 1) {
- v <<= 8;
- v += dataView.getUint8(offset + i);
- }
- return v;
- }
- // Retrieve a number of bytes from start offset to the end offset from the DataView.
- function getBytes(dataView, startOffset, endOffset) {
- var bytes = [];
- for (var i = startOffset; i < endOffset; i += 1) {
- bytes.push(dataView.getUint8(i));
- }
- return bytes;
- }
- // Convert the list of bytes to a string.
- function bytesToString(bytes) {
- var s = '';
- for (var i = 0; i < bytes.length; i += 1) {
- s += String.fromCharCode(bytes[i]);
- }
- return s;
- }
- var typeOffsets = {
- byte: 1,
- uShort: 2,
- short: 2,
- uLong: 4,
- fixed: 4,
- longDateTime: 8,
- tag: 4
- };
- // A stateful parser that changes the offset whenever a value is retrieved.
- // The data is a DataView.
- function Parser(data, offset) {
- this.data = data;
- this.offset = offset;
- this.relativeOffset = 0;
- }
- Parser.prototype.parseByte = function() {
- var v = this.data.getUint8(this.offset + this.relativeOffset);
- this.relativeOffset += 1;
- return v;
- };
- Parser.prototype.parseChar = function() {
- var v = this.data.getInt8(this.offset + this.relativeOffset);
- this.relativeOffset += 1;
- return v;
- };
- Parser.prototype.parseCard8 = Parser.prototype.parseByte;
- Parser.prototype.parseUShort = function() {
- var v = this.data.getUint16(this.offset + this.relativeOffset);
- this.relativeOffset += 2;
- return v;
- };
- Parser.prototype.parseCard16 = Parser.prototype.parseUShort;
- Parser.prototype.parseSID = Parser.prototype.parseUShort;
- Parser.prototype.parseOffset16 = Parser.prototype.parseUShort;
- Parser.prototype.parseShort = function() {
- var v = this.data.getInt16(this.offset + this.relativeOffset);
- this.relativeOffset += 2;
- return v;
- };
- Parser.prototype.parseF2Dot14 = function() {
- var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384;
- this.relativeOffset += 2;
- return v;
- };
- Parser.prototype.parseULong = function() {
- var v = getULong(this.data, this.offset + this.relativeOffset);
- this.relativeOffset += 4;
- return v;
- };
- Parser.prototype.parseOffset32 = Parser.prototype.parseULong;
- Parser.prototype.parseFixed = function() {
- var v = getFixed(this.data, this.offset + this.relativeOffset);
- this.relativeOffset += 4;
- return v;
- };
- Parser.prototype.parseString = function(length) {
- var dataView = this.data;
- var offset = this.offset + this.relativeOffset;
- var string = '';
- this.relativeOffset += length;
- for (var i = 0; i < length; i++) {
- string += String.fromCharCode(dataView.getUint8(offset + i));
- }
- return string;
- };
- Parser.prototype.parseTag = function() {
- return this.parseString(4);
- };
- // LONGDATETIME is a 64-bit integer.
- // JavaScript and unix timestamps traditionally use 32 bits, so we
- // only take the last 32 bits.
- // + Since until 2038 those bits will be filled by zeros we can ignore them.
- Parser.prototype.parseLongDateTime = function() {
- var v = getULong(this.data, this.offset + this.relativeOffset + 4);
- // Subtract seconds between 01/01/1904 and 01/01/1970
- // to convert Apple Mac timestamp to Standard Unix timestamp
- v -= 2082844800;
- this.relativeOffset += 8;
- return v;
- };
- Parser.prototype.parseVersion = function(minorBase) {
- var major = getUShort(this.data, this.offset + this.relativeOffset);
- // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1
- // Default returns the correct number if minor = 0xN000 where N is 0-9
- // Set minorBase to 1 for tables that use minor = N where N is 0-9
- var minor = getUShort(this.data, this.offset + this.relativeOffset + 2);
- this.relativeOffset += 4;
- if (minorBase === undefined) { minorBase = 0x1000; }
- return major + minor / minorBase / 10;
- };
- Parser.prototype.skip = function(type, amount) {
- if (amount === undefined) {
- amount = 1;
- }
- this.relativeOffset += typeOffsets[type] * amount;
- };
- ///// Parsing lists and records ///////////////////////////////
- // Parse a list of 32 bit unsigned integers.
- Parser.prototype.parseULongList = function(count) {
- if (count === undefined) { count = this.parseULong(); }
- var offsets = new Array(count);
- var dataView = this.data;
- var offset = this.offset + this.relativeOffset;
- for (var i = 0; i < count; i++) {
- offsets[i] = dataView.getUint32(offset);
- offset += 4;
- }
- this.relativeOffset += count * 4;
- return offsets;
- };
- // Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream
- // or provided as an argument.
- Parser.prototype.parseOffset16List =
- Parser.prototype.parseUShortList = function(count) {
- if (count === undefined) { count = this.parseUShort(); }
- var offsets = new Array(count);
- var dataView = this.data;
- var offset = this.offset + this.relativeOffset;
- for (var i = 0; i < count; i++) {
- offsets[i] = dataView.getUint16(offset);
- offset += 2;
- }
- this.relativeOffset += count * 2;
- return offsets;
- };
- // Parses a list of 16 bit signed integers.
- Parser.prototype.parseShortList = function(count) {
- var list = new Array(count);
- var dataView = this.data;
- var offset = this.offset + this.relativeOffset;
- for (var i = 0; i < count; i++) {
- list[i] = dataView.getInt16(offset);
- offset += 2;
- }
- this.relativeOffset += count * 2;
- return list;
- };
- // Parses a list of bytes.
- Parser.prototype.parseByteList = function(count) {
- var list = new Array(count);
- var dataView = this.data;
- var offset = this.offset + this.relativeOffset;
- for (var i = 0; i < count; i++) {
- list[i] = dataView.getUint8(offset++);
- }
- this.relativeOffset += count;
- return list;
- };
- /**
- * Parse a list of items.
- * Record count is optional, if omitted it is read from the stream.
- * itemCallback is one of the Parser methods.
- */
- Parser.prototype.parseList = function(count, itemCallback) {
- if (!itemCallback) {
- itemCallback = count;
- count = this.parseUShort();
- }
- var list = new Array(count);
- for (var i = 0; i < count; i++) {
- list[i] = itemCallback.call(this);
- }
- return list;
- };
- Parser.prototype.parseList32 = function(count, itemCallback) {
- if (!itemCallback) {
- itemCallback = count;
- count = this.parseULong();
- }
- var list = new Array(count);
- for (var i = 0; i < count; i++) {
- list[i] = itemCallback.call(this);
- }
- return list;
- };
- /**
- * Parse a list of records.
- * Record count is optional, if omitted it is read from the stream.
- * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort }
- */
- Parser.prototype.parseRecordList = function(count, recordDescription) {
- // If the count argument is absent, read it in the stream.
- if (!recordDescription) {
- recordDescription = count;
- count = this.parseUShort();
- }
- var records = new Array(count);
- var fields = Object.keys(recordDescription);
- for (var i = 0; i < count; i++) {
- var rec = {};
- for (var j = 0; j < fields.length; j++) {
- var fieldName = fields[j];
- var fieldType = recordDescription[fieldName];
- rec[fieldName] = fieldType.call(this);
- }
- records[i] = rec;
- }
- return records;
- };
- Parser.prototype.parseRecordList32 = function(count, recordDescription) {
- // If the count argument is absent, read it in the stream.
- if (!recordDescription) {
- recordDescription = count;
- count = this.parseULong();
- }
- var records = new Array(count);
- var fields = Object.keys(recordDescription);
- for (var i = 0; i < count; i++) {
- var rec = {};
- for (var j = 0; j < fields.length; j++) {
- var fieldName = fields[j];
- var fieldType = recordDescription[fieldName];
- rec[fieldName] = fieldType.call(this);
- }
- records[i] = rec;
- }
- return records;
- };
- // Parse a data structure into an object
- // Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort }
- Parser.prototype.parseStruct = function(description) {
- if (typeof description === 'function') {
- return description.call(this);
- } else {
- var fields = Object.keys(description);
- var struct = {};
- for (var j = 0; j < fields.length; j++) {
- var fieldName = fields[j];
- var fieldType = description[fieldName];
- struct[fieldName] = fieldType.call(this);
- }
- return struct;
- }
- };
- /**
- * Parse a GPOS valueRecord
- * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record
- * valueFormat is optional, if omitted it is read from the stream.
- */
- Parser.prototype.parseValueRecord = function(valueFormat) {
- if (valueFormat === undefined) {
- valueFormat = this.parseUShort();
- }
- if (valueFormat === 0) {
- // valueFormat2 in kerning pairs is most often 0
- // in this case return undefined instead of an empty object, to save space
- return;
- }
- var valueRecord = {};
- if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); }
- if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); }
- if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); }
- if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); }
- // Device table (non-variable font) / VariationIndex table (variable font) not supported
- // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls
- if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); }
- if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); }
- if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); }
- if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); }
- return valueRecord;
- };
- /**
- * Parse a list of GPOS valueRecords
- * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record
- * valueFormat and valueCount are read from the stream.
- */
- Parser.prototype.parseValueRecordList = function() {
- var valueFormat = this.parseUShort();
- var valueCount = this.parseUShort();
- var values = new Array(valueCount);
- for (var i = 0; i < valueCount; i++) {
- values[i] = this.parseValueRecord(valueFormat);
- }
- return values;
- };
- Parser.prototype.parsePointer = function(description) {
- var structOffset = this.parseOffset16();
- if (structOffset > 0) {
- // NULL offset => return undefined
- return new Parser(this.data, this.offset + structOffset).parseStruct(description);
- }
- return undefined;
- };
- Parser.prototype.parsePointer32 = function(description) {
- var structOffset = this.parseOffset32();
- if (structOffset > 0) {
- // NULL offset => return undefined
- return new Parser(this.data, this.offset + structOffset).parseStruct(description);
- }
- return undefined;
- };
- /**
- * Parse a list of offsets to lists of 16-bit integers,
- * or a list of offsets to lists of offsets to any kind of items.
- * If itemCallback is not provided, a list of list of UShort is assumed.
- * If provided, itemCallback is called on each item and must parse the item.
- * See examples in tables/gsub.js
- */
- Parser.prototype.parseListOfLists = function(itemCallback) {
- var offsets = this.parseOffset16List();
- var count = offsets.length;
- var relativeOffset = this.relativeOffset;
- var list = new Array(count);
- for (var i = 0; i < count; i++) {
- var start = offsets[i];
- if (start === 0) {
- // NULL offset
- // Add i as owned property to list. Convenient with assert.
- list[i] = undefined;
- continue;
- }
- this.relativeOffset = start;
- if (itemCallback) {
- var subOffsets = this.parseOffset16List();
- var subList = new Array(subOffsets.length);
- for (var j = 0; j < subOffsets.length; j++) {
- this.relativeOffset = start + subOffsets[j];
- subList[j] = itemCallback.call(this);
- }
- list[i] = subList;
- } else {
- list[i] = this.parseUShortList();
- }
- }
- this.relativeOffset = relativeOffset;
- return list;
- };
- ///// Complex tables parsing //////////////////////////////////
- // Parse a coverage table in a GSUB, GPOS or GDEF table.
- // https://www.microsoft.com/typography/OTSPEC/chapter2.htm
- // parser.offset must point to the start of the table containing the coverage.
- Parser.prototype.parseCoverage = function() {
- var startOffset = this.offset + this.relativeOffset;
- var format = this.parseUShort();
- var count = this.parseUShort();
- if (format === 1) {
- return {
- format: 1,
- glyphs: this.parseUShortList(count)
- };
- } else if (format === 2) {
- var ranges = new Array(count);
- for (var i = 0; i < count; i++) {
- ranges[i] = {
- start: this.parseUShort(),
- end: this.parseUShort(),
- index: this.parseUShort()
- };
- }
- return {
- format: 2,
- ranges: ranges
- };
- }
- throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.');
- };
- // Parse a Class Definition Table in a GSUB, GPOS or GDEF table.
- // https://www.microsoft.com/typography/OTSPEC/chapter2.htm
- Parser.prototype.parseClassDef = function() {
- var startOffset = this.offset + this.relativeOffset;
- var format = this.parseUShort();
- if (format === 1) {
- return {
- format: 1,
- startGlyph: this.parseUShort(),
- classes: this.parseUShortList()
- };
- } else if (format === 2) {
- return {
- format: 2,
- ranges: this.parseRecordList({
- start: Parser.uShort,
- end: Parser.uShort,
- classId: Parser.uShort
- })
- };
- }
- throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.');
- };
- ///// Static methods ///////////////////////////////////
- // These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance.
- Parser.list = function(count, itemCallback) {
- return function() {
- return this.parseList(count, itemCallback);
- };
- };
- Parser.list32 = function(count, itemCallback) {
- return function() {
- return this.parseList32(count, itemCallback);
- };
- };
- Parser.recordList = function(count, recordDescription) {
- return function() {
- return this.parseRecordList(count, recordDescription);
- };
- };
- Parser.recordList32 = function(count, recordDescription) {
- return function() {
- return this.parseRecordList32(count, recordDescription);
- };
- };
- Parser.pointer = function(description) {
- return function() {
- return this.parsePointer(description);
- };
- };
- Parser.pointer32 = function(description) {
- return function() {
- return this.parsePointer32(description);
- };
- };
- Parser.tag = Parser.prototype.parseTag;
- Parser.byte = Parser.prototype.parseByte;
- Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort;
- Parser.uShortList = Parser.prototype.parseUShortList;
- Parser.uLong = Parser.offset32 = Parser.prototype.parseULong;
- Parser.uLongList = Parser.prototype.parseULongList;
- Parser.struct = Parser.prototype.parseStruct;
- Parser.coverage = Parser.prototype.parseCoverage;
- Parser.classDef = Parser.prototype.parseClassDef;
- ///// Script, Feature, Lookup lists ///////////////////////////////////////////////
- // https://www.microsoft.com/typography/OTSPEC/chapter2.htm
- var langSysTable = {
- reserved: Parser.uShort,
- reqFeatureIndex: Parser.uShort,
- featureIndexes: Parser.uShortList
- };
- Parser.prototype.parseScriptList = function() {
- return this.parsePointer(Parser.recordList({
- tag: Parser.tag,
- script: Parser.pointer({
- defaultLangSys: Parser.pointer(langSysTable),
- langSysRecords: Parser.recordList({
- tag: Parser.tag,
- langSys: Parser.pointer(langSysTable)
- })
- })
- })) || [];
- };
- Parser.prototype.parseFeatureList = function() {
- return this.parsePointer(Parser.recordList({
- tag: Parser.tag,
- feature: Parser.pointer({
- featureParams: Parser.offset16,
- lookupListIndexes: Parser.uShortList
- })
- })) || [];
- };
- Parser.prototype.parseLookupList = function(lookupTableParsers) {
- return this.parsePointer(Parser.list(Parser.pointer(function() {
- var lookupType = this.parseUShort();
- check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.');
- var lookupFlag = this.parseUShort();
- var useMarkFilteringSet = lookupFlag & 0x10;
- return {
- lookupType: lookupType,
- lookupFlag: lookupFlag,
- subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])),
- markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined
- };
- }))) || [];
- };
- Parser.prototype.parseFeatureVariationsList = function() {
- return this.parsePointer32(function() {
- var majorVersion = this.parseUShort();
- var minorVersion = this.parseUShort();
- check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.');
- var featureVariations = this.parseRecordList32({
- conditionSetOffset: Parser.offset32,
- featureTableSubstitutionOffset: Parser.offset32
- });
- return featureVariations;
- }) || [];
- };
- var parse = {
- getByte: getByte,
- getCard8: getByte,
- getUShort: getUShort,
- getCard16: getUShort,
- getShort: getShort,
- getULong: getULong,
- getFixed: getFixed,
- getTag: getTag,
- getOffset: getOffset,
- getBytes: getBytes,
- bytesToString: bytesToString,
- Parser: Parser,
- };
- // The `cmap` table stores the mappings from characters to glyphs.
- function parseCmapTableFormat12(cmap, p) {
- //Skip reserved.
- p.parseUShort();
- // Length in bytes of the sub-tables.
- cmap.length = p.parseULong();
- cmap.language = p.parseULong();
- var groupCount;
- cmap.groupCount = groupCount = p.parseULong();
- cmap.glyphIndexMap = {};
- for (var i = 0; i < groupCount; i += 1) {
- var startCharCode = p.parseULong();
- var endCharCode = p.parseULong();
- var startGlyphId = p.parseULong();
- for (var c = startCharCode; c <= endCharCode; c += 1) {
- cmap.glyphIndexMap[c] = startGlyphId;
- startGlyphId++;
- }
- }
- }
- function parseCmapTableFormat4(cmap, p, data, start, offset) {
- // Length in bytes of the sub-tables.
- cmap.length = p.parseUShort();
- cmap.language = p.parseUShort();
- // segCount is stored x 2.
- var segCount;
- cmap.segCount = segCount = p.parseUShort() >> 1;
- // Skip searchRange, entrySelector, rangeShift.
- p.skip('uShort', 3);
- // The "unrolled" mapping from character codes to glyph indices.
- cmap.glyphIndexMap = {};
- var endCountParser = new parse.Parser(data, start + offset + 14);
- var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2);
- var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4);
- var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6);
- var glyphIndexOffset = start + offset + 16 + segCount * 8;
- for (var i = 0; i < segCount - 1; i += 1) {
- var glyphIndex = (void 0);
- var endCount = endCountParser.parseUShort();
- var startCount = startCountParser.parseUShort();
- var idDelta = idDeltaParser.parseShort();
- var idRangeOffset = idRangeOffsetParser.parseUShort();
- for (var c = startCount; c <= endCount; c += 1) {
- if (idRangeOffset !== 0) {
- // The idRangeOffset is relative to the current position in the idRangeOffset array.
- // Take the current offset in the idRangeOffset array.
- glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2);
- // Add the value of the idRangeOffset, which will move us into the glyphIndex array.
- glyphIndexOffset += idRangeOffset;
- // Then add the character index of the current segment, multiplied by 2 for USHORTs.
- glyphIndexOffset += (c - startCount) * 2;
- glyphIndex = parse.getUShort(data, glyphIndexOffset);
- if (glyphIndex !== 0) {
- glyphIndex = (glyphIndex + idDelta) & 0xFFFF;
- }
- } else {
- glyphIndex = (c + idDelta) & 0xFFFF;
- }
- cmap.glyphIndexMap[c] = glyphIndex;
- }
- }
- }
- // Parse the `cmap` table. This table stores the mappings from characters to glyphs.
- // There are many available formats, but we only support the Windows format 4 and 12.
- // This function returns a `CmapEncoding` object or null if no supported format could be found.
- function parseCmapTable(data, start) {
- var cmap = {};
- cmap.version = parse.getUShort(data, start);
- check.argument(cmap.version === 0, 'cmap table version should be 0.');
- // The cmap table can contain many sub-tables, each with their own format.
- // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table.
- cmap.numTables = parse.getUShort(data, start + 2);
- var offset = -1;
- for (var i = cmap.numTables - 1; i >= 0; i -= 1) {
- var platformId = parse.getUShort(data, start + 4 + (i * 8));
- var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2);
- if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) ||
- (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) {
- offset = parse.getULong(data, start + 4 + (i * 8) + 4);
- break;
- }
- }
- if (offset === -1) {
- // There is no cmap table in the font that we support.
- throw new Error('No valid cmap sub-tables found.');
- }
- var p = new parse.Parser(data, start + offset);
- cmap.format = p.parseUShort();
- if (cmap.format === 12) {
- parseCmapTableFormat12(cmap, p);
- } else if (cmap.format === 4) {
- parseCmapTableFormat4(cmap, p, data, start, offset);
- } else {
- throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').');
- }
- return cmap;
- }
- function addSegment(t, code, glyphIndex) {
- t.segments.push({
- end: code,
- start: code,
- delta: -(code - glyphIndex),
- offset: 0,
- glyphIndex: glyphIndex
- });
- }
- function addTerminatorSegment(t) {
- t.segments.push({
- end: 0xFFFF,
- start: 0xFFFF,
- delta: 1,
- offset: 0
- });
- }
- // Make cmap table, format 4 by default, 12 if needed only
- function makeCmapTable(glyphs) {
- // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit)
- var isPlan0Only = true;
- var i;
- // Check if we need to add cmap format 12 or if format 4 only is fine
- for (i = glyphs.length - 1; i > 0; i -= 1) {
- var g = glyphs.get(i);
- if (g.unicode > 65535) {
- console.log('Adding CMAP format 12 (needed!)');
- isPlan0Only = false;
- break;
- }
- }
- var cmapTable = [
- {name: 'version', type: 'USHORT', value: 0},
- {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2},
- // CMAP 4 header
- {name: 'platformID', type: 'USHORT', value: 3},
- {name: 'encodingID', type: 'USHORT', value: 1},
- {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)}
- ];
- if (!isPlan0Only)
- { cmapTable = cmapTable.concat([
- // CMAP 12 header
- {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere
- {name: 'cmap12EncodingID', type: 'USHORT', value: 10},
- {name: 'cmap12Offset', type: 'ULONG', value: 0}
- ]); }
- cmapTable = cmapTable.concat([
- // CMAP 4 Subtable
- {name: 'format', type: 'USHORT', value: 4},
- {name: 'cmap4Length', type: 'USHORT', value: 0},
- {name: 'language', type: 'USHORT', value: 0},
- {name: 'segCountX2', type: 'USHORT', value: 0},
- {name: 'searchRange', type: 'USHORT', value: 0},
- {name: 'entrySelector', type: 'USHORT', value: 0},
- {name: 'rangeShift', type: 'USHORT', value: 0}
- ]);
- var t = new table.Table('cmap', cmapTable);
- t.segments = [];
- for (i = 0; i < glyphs.length; i += 1) {
- var glyph = glyphs.get(i);
- for (var j = 0; j < glyph.unicodes.length; j += 1) {
- addSegment(t, glyph.unicodes[j], i);
- }
- t.segments = t.segments.sort(function (a, b) {
- return a.start - b.start;
- });
- }
- addTerminatorSegment(t);
- var segCount = t.segments.length;
- var segCountToRemove = 0;
- // CMAP 4
- // Set up parallel segment arrays.
- var endCounts = [];
- var startCounts = [];
- var idDeltas = [];
- var idRangeOffsets = [];
- var glyphIds = [];
- // CMAP 12
- var cmap12Groups = [];
- // Reminder this loop is not following the specification at 100%
- // The specification -> find suites of characters and make a group
- // Here we're doing one group for each letter
- // Doing as the spec can save 8 times (or more) space
- for (i = 0; i < segCount; i += 1) {
- var segment = t.segments[i];
- // CMAP 4
- if (segment.end <= 65535 && segment.start <= 65535) {
- endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end});
- startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start});
- idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta});
- idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset});
- if (segment.glyphId !== undefined) {
- glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId});
- }
- } else {
- // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12
- segCountToRemove += 1;
- }
- // CMAP 12
- // Skip Terminator Segment
- if (!isPlan0Only && segment.glyphIndex !== undefined) {
- cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start});
- cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end});
- cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex});
- }
- }
- // CMAP 4 Subtable
- t.segCountX2 = (segCount - segCountToRemove) * 2;
- t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2;
- t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2);
- t.rangeShift = t.segCountX2 - t.searchRange;
- t.fields = t.fields.concat(endCounts);
- t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0});
- t.fields = t.fields.concat(startCounts);
- t.fields = t.fields.concat(idDeltas);
- t.fields = t.fields.concat(idRangeOffsets);
- t.fields = t.fields.concat(glyphIds);
- t.cmap4Length = 14 + // Subtable header
- endCounts.length * 2 +
- 2 + // reservedPad
- startCounts.length * 2 +
- idDeltas.length * 2 +
- idRangeOffsets.length * 2 +
- glyphIds.length * 2;
- if (!isPlan0Only) {
- // CMAP 12 Subtable
- var cmap12Length = 16 + // Subtable header
- cmap12Groups.length * 4;
- t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length;
- t.fields = t.fields.concat([
- {name: 'cmap12Format', type: 'USHORT', value: 12},
- {name: 'cmap12Reserved', type: 'USHORT', value: 0},
- {name: 'cmap12Length', type: 'ULONG', value: cmap12Length},
- {name: 'cmap12Language', type: 'ULONG', value: 0},
- {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3}
- ]);
- t.fields = t.fields.concat(cmap12Groups);
- }
- return t;
- }
- var cmap = { parse: parseCmapTable, make: makeCmapTable };
- // Glyph encoding
- var cffStandardStrings = [
- '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright',
- 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two',
- 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater',
- 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
- 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
- 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
- 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling',
- 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
- 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph',
- 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
- 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring',
- 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE',
- 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu',
- 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn',
- 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright',
- 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex',
- 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex',
- 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute',
- 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute',
- 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute',
- 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
- 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior',
- 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader',
- 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle',
- 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
- 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior',
- 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl',
- 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall',
- 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall',
- 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall',
- 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall',
- 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall',
- 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
- 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds',
- 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior',
- 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior',
- 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior',
- 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall',
- 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall',
- 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall',
- 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall',
- 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000',
- '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold'];
- var cffStandardEncoding = [
- '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
- '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright',
- 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two',
- 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater',
- 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
- 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
- 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
- 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
- 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle',
- 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger',
- 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright',
- 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde',
- 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron',
- 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '',
- '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '',
- 'lslash', 'oslash', 'oe', 'germandbls'];
- var cffExpertEncoding = [
- '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
- '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior',
- 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
- 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle',
- 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon',
- 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior',
- 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior',
- 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl',
- 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall',
- 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall',
- 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall',
- 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
- 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall',
- 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior',
- '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters',
- 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '',
- '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior',
- 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior',
- 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior',
- 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
- 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall',
- 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall',
- 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
- 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall',
- 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall'];
- var standardNames = [
- '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent',
- 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash',
- 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less',
- 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
- 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright',
- 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
- 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde',
- 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave',
- 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis',
- 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis',
- 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section',
- 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal',
- 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation',
- 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown',
- 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright',
- 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft',
- 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction',
- 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase',
- 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute',
- 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex',
- 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut',
- 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth',
- 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior',
- 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla',
- 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat'];
- /**
- * This is the encoding used for fonts created from scratch.
- * It loops through all glyphs and finds the appropriate unicode value.
- * Since it's linear time, other encodings will be faster.
- * @exports opentype.DefaultEncoding
- * @class
- * @constructor
- * @param {opentype.Font}
- */
- function DefaultEncoding(font) {
- this.font = font;
- }
- DefaultEncoding.prototype.charToGlyphIndex = function(c) {
- var code = c.codePointAt(0);
- var glyphs = this.font.glyphs;
- if (glyphs) {
- for (var i = 0; i < glyphs.length; i += 1) {
- var glyph = glyphs.get(i);
- for (var j = 0; j < glyph.unicodes.length; j += 1) {
- if (glyph.unicodes[j] === code) {
- return i;
- }
- }
- }
- }
- return null;
- };
- /**
- * @exports opentype.CmapEncoding
- * @class
- * @constructor
- * @param {Object} cmap - a object with the cmap encoded data
- */
- function CmapEncoding(cmap) {
- this.cmap = cmap;
- }
- /**
- * @param {string} c - the character
- * @return {number} The glyph index.
- */
- CmapEncoding.prototype.charToGlyphIndex = function(c) {
- return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0;
- };
- /**
- * @exports opentype.CffEncoding
- * @class
- * @constructor
- * @param {string} encoding - The encoding
- * @param {Array} charset - The character set.
- */
- function CffEncoding(encoding, charset) {
- this.encoding = encoding;
- this.charset = charset;
- }
- /**
- * @param {string} s - The character
- * @return {number} The index.
- */
- CffEncoding.prototype.charToGlyphIndex = function(s) {
- var code = s.codePointAt(0);
- var charName = this.encoding[code];
- return this.charset.indexOf(charName);
- };
- /**
- * @exports opentype.GlyphNames
- * @class
- * @constructor
- * @param {Object} post
- */
- function GlyphNames(post) {
- switch (post.version) {
- case 1:
- this.names = standardNames.slice();
- break;
- case 2:
- this.names = new Array(post.numberOfGlyphs);
- for (var i = 0; i < post.numberOfGlyphs; i++) {
- if (post.glyphNameIndex[i] < standardNames.length) {
- this.names[i] = standardNames[post.glyphNameIndex[i]];
- } else {
- this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length];
- }
- }
- break;
- case 2.5:
- this.names = new Array(post.numberOfGlyphs);
- for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) {
- this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]];
- }
- break;
- case 3:
- this.names = [];
- break;
- default:
- this.names = [];
- break;
- }
- }
- /**
- * Gets the index of a glyph by name.
- * @param {string} name - The glyph name
- * @return {number} The index
- */
- GlyphNames.prototype.nameToGlyphIndex = function(name) {
- return this.names.indexOf(name);
- };
- /**
- * @param {number} gid
- * @return {string}
- */
- GlyphNames.prototype.glyphIndexToName = function(gid) {
- return this.names[gid];
- };
- function addGlyphNamesAll(font) {
- var glyph;
- var glyphIndexMap = font.tables.cmap.glyphIndexMap;
- var charCodes = Object.keys(glyphIndexMap);
- for (var i = 0; i < charCodes.length; i += 1) {
- var c = charCodes[i];
- var glyphIndex = glyphIndexMap[c];
- glyph = font.glyphs.get(glyphIndex);
- glyph.addUnicode(parseInt(c));
- }
- for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) {
- glyph = font.glyphs.get(i$1);
- if (font.cffEncoding) {
- if (font.isCIDFont) {
- glyph.name = 'gid' + i$1;
- } else {
- glyph.name = font.cffEncoding.charset[i$1];
- }
- } else if (font.glyphNames.names) {
- glyph.name = font.glyphNames.glyphIndexToName(i$1);
- }
- }
- }
- function addGlyphNamesToUnicodeMap(font) {
- font._IndexToUnicodeMap = {};
- var glyphIndexMap = font.tables.cmap.glyphIndexMap;
- var charCodes = Object.keys(glyphIndexMap);
- for (var i = 0; i < charCodes.length; i += 1) {
- var c = charCodes[i];
- var glyphIndex = glyphIndexMap[c];
- if (font._IndexToUnicodeMap[glyphIndex] === undefined) {
- font._IndexToUnicodeMap[glyphIndex] = {
- unicodes: [parseInt(c)]
- };
- } else {
- font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c));
- }
- }
- }
- /**
- * @alias opentype.addGlyphNames
- * @param {opentype.Font}
- * @param {Object}
- */
- function addGlyphNames(font, opt) {
- if (opt.lowMemory) {
- addGlyphNamesToUnicodeMap(font);
- } else {
- addGlyphNamesAll(font);
- }
- }
- // Drawing utility functions.
- // Draw a line on the given context from point `x1,y1` to point `x2,y2`.
- function line(ctx, x1, y1, x2, y2) {
- ctx.beginPath();
- ctx.moveTo(x1, y1);
- ctx.lineTo(x2, y2);
- ctx.stroke();
- }
- var draw = { line: line };
- // The Glyph object
- // import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency
- function getPathDefinition(glyph, path) {
- var _path = path || new Path();
- return {
- configurable: true,
- get: function() {
- if (typeof _path === 'function') {
- _path = _path();
- }
- return _path;
- },
- set: function(p) {
- _path = p;
- }
- };
- }
- /**
- * @typedef GlyphOptions
- * @type Object
- * @property {string} [name] - The glyph name
- * @property {number} [unicode]
- * @property {Array} [unicodes]
- * @property {number} [xMin]
- * @property {number} [yMin]
- * @property {number} [xMax]
- * @property {number} [yMax]
- * @property {number} [advanceWidth]
- */
- // A Glyph is an individual mark that often corresponds to a character.
- // Some glyphs, such as ligatures, are a combination of many characters.
- // Glyphs are the basic building blocks of a font.
- //
- // The `Glyph` class contains utility methods for drawing the path and its points.
- /**
- * @exports opentype.Glyph
- * @class
- * @param {GlyphOptions}
- * @constructor
- */
- function Glyph(options) {
- // By putting all the code on a prototype function (which is only declared once)
- // we reduce the memory requirements for larger fonts by some 2%
- this.bindConstructorValues(options);
- }
- /**
- * @param {GlyphOptions}
- */
- Glyph.prototype.bindConstructorValues = function(options) {
- this.index = options.index || 0;
- // These three values cannot be deferred for memory optimization:
- this.name = options.name || null;
- this.unicode = options.unicode || undefined;
- this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : [];
- // But by binding these values only when necessary, we reduce can
- // the memory requirements by almost 3% for larger fonts.
- if ('xMin' in options) {
- this.xMin = options.xMin;
- }
- if ('yMin' in options) {
- this.yMin = options.yMin;
- }
- if ('xMax' in options) {
- this.xMax = options.xMax;
- }
- if ('yMax' in options) {
- this.yMax = options.yMax;
- }
- if ('advanceWidth' in options) {
- this.advanceWidth = options.advanceWidth;
- }
- // The path for a glyph is the most memory intensive, and is bound as a value
- // with a getter/setter to ensure we actually do path parsing only once the
- // path is actually needed by anything.
- Object.defineProperty(this, 'path', getPathDefinition(this, options.path));
- };
- /**
- * @param {number}
- */
- Glyph.prototype.addUnicode = function(unicode) {
- if (this.unicodes.length === 0) {
- this.unicode = unicode;
- }
- this.unicodes.push(unicode);
- };
- /**
- * Calculate the minimum bounding box for this glyph.
- * @return {opentype.BoundingBox}
- */
- Glyph.prototype.getBoundingBox = function() {
- return this.path.getBoundingBox();
- };
- /**
- * Convert the glyph to a Path we can draw on a drawing context.
- * @param {number} [x=0] - Horizontal position of the beginning of the text.
- * @param {number} [y=0] - Vertical position of the *baseline* of the text.
- * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
- * @param {Object=} options - xScale, yScale to stretch the glyph.
- * @param {opentype.Font} if hinting is to be used, the font
- * @return {opentype.Path}
- */
- Glyph.prototype.getPath = function(x, y, fontSize, options, font) {
- x = x !== undefined ? x : 0;
- y = y !== undefined ? y : 0;
- fontSize = fontSize !== undefined ? fontSize : 72;
- var commands;
- var hPoints;
- if (!options) { options = { }; }
- var xScale = options.xScale;
- var yScale = options.yScale;
- if (options.hinting && font && font.hinting) {
- // in case of hinting, the hinting engine takes care
- // of scaling the points (not the path) before hinting.
- hPoints = this.path && font.hinting.exec(this, fontSize);
- // in case the hinting engine failed hPoints is undefined
- // and thus reverts to plain rending
- }
- if (hPoints) {
- // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency
- commands = font.hinting.getCommands(hPoints);
- x = Math.round(x);
- y = Math.round(y);
- // TODO in case of hinting xyScaling is not yet supported
- xScale = yScale = 1;
- } else {
- commands = this.path.commands;
- var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize;
- if (xScale === undefined) { xScale = scale; }
- if (yScale === undefined) { yScale = scale; }
- }
- var p = new Path();
- for (var i = 0; i < commands.length; i += 1) {
- var cmd = commands[i];
- if (cmd.type === 'M') {
- p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale));
- } else if (cmd.type === 'L') {
- p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale));
- } else if (cmd.type === 'Q') {
- p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale),
- x + (cmd.x * xScale), y + (-cmd.y * yScale));
- } else if (cmd.type === 'C') {
- p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale),
- x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale),
- x + (cmd.x * xScale), y + (-cmd.y * yScale));
- } else if (cmd.type === 'Z') {
- p.closePath();
- }
- }
- return p;
- };
- /**
- * Split the glyph into contours.
- * This function is here for backwards compatibility, and to
- * provide raw access to the TrueType glyph outlines.
- * @return {Array}
- */
- Glyph.prototype.getContours = function() {
- if (this.points === undefined) {
- return [];
- }
- var contours = [];
- var currentContour = [];
- for (var i = 0; i < this.points.length; i += 1) {
- var pt = this.points[i];
- currentContour.push(pt);
- if (pt.lastPointOfContour) {
- contours.push(currentContour);
- currentContour = [];
- }
- }
- check.argument(currentContour.length === 0, 'There are still points left in the current contour.');
- return contours;
- };
- /**
- * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph.
- * @return {Object}
- */
- Glyph.prototype.getMetrics = function() {
- var commands = this.path.commands;
- var xCoords = [];
- var yCoords = [];
- for (var i = 0; i < commands.length; i += 1) {
- var cmd = commands[i];
- if (cmd.type !== 'Z') {
- xCoords.push(cmd.x);
- yCoords.push(cmd.y);
- }
- if (cmd.type === 'Q' || cmd.type === 'C') {
- xCoords.push(cmd.x1);
- yCoords.push(cmd.y1);
- }
- if (cmd.type === 'C') {
- xCoords.push(cmd.x2);
- yCoords.push(cmd.y2);
- }
- }
- var metrics = {
- xMin: Math.min.apply(null, xCoords),
- yMin: Math.min.apply(null, yCoords),
- xMax: Math.max.apply(null, xCoords),
- yMax: Math.max.apply(null, yCoords),
- leftSideBearing: this.leftSideBearing
- };
- if (!isFinite(metrics.xMin)) {
- metrics.xMin = 0;
- }
- if (!isFinite(metrics.xMax)) {
- metrics.xMax = this.advanceWidth;
- }
- if (!isFinite(metrics.yMin)) {
- metrics.yMin = 0;
- }
- if (!isFinite(metrics.yMax)) {
- metrics.yMax = 0;
- }
- metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin);
- return metrics;
- };
- /**
- * Draw the glyph on the given context.
- * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.
- * @param {number} [x=0] - Horizontal position of the beginning of the text.
- * @param {number} [y=0] - Vertical position of the *baseline* of the text.
- * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
- * @param {Object=} options - xScale, yScale to stretch the glyph.
- */
- Glyph.prototype.draw = function(ctx, x, y, fontSize, options) {
- this.getPath(x, y, fontSize, options).draw(ctx);
- };
- /**
- * Draw the points of the glyph.
- * On-curve points will be drawn in blue, off-curve points will be drawn in red.
- * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.
- * @param {number} [x=0] - Horizontal position of the beginning of the text.
- * @param {number} [y=0] - Vertical position of the *baseline* of the text.
- * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
- */
- Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) {
- function drawCircles(l, x, y, scale) {
- ctx.beginPath();
- for (var j = 0; j < l.length; j += 1) {
- ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale));
- ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false);
- }
- ctx.closePath();
- ctx.fill();
- }
- x = x !== undefined ? x : 0;
- y = y !== undefined ? y : 0;
- fontSize = fontSize !== undefined ? fontSize : 24;
- var scale = 1 / this.path.unitsPerEm * fontSize;
- var blueCircles = [];
- var redCircles = [];
- var path = this.path;
- for (var i = 0; i < path.commands.length; i += 1) {
- var cmd = path.commands[i];
- if (cmd.x !== undefined) {
- blueCircles.push({x: cmd.x, y: -cmd.y});
- }
- if (cmd.x1 !== undefined) {
- redCircles.push({x: cmd.x1, y: -cmd.y1});
- }
- if (cmd.x2 !== undefined) {
- redCircles.push({x: cmd.x2, y: -cmd.y2});
- }
- }
- ctx.fillStyle = 'blue';
- drawCircles(blueCircles, x, y, scale);
- ctx.fillStyle = 'red';
- drawCircles(redCircles, x, y, scale);
- };
- /**
- * Draw lines indicating important font measurements.
- * Black lines indicate the origin of the coordinate system (point 0,0).
- * Blue lines indicate the glyph bounding box.
- * Green line indicates the advance width of the glyph.
- * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.
- * @param {number} [x=0] - Horizontal position of the beginning of the text.
- * @param {number} [y=0] - Vertical position of the *baseline* of the text.
- * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
- */
- Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) {
- var scale;
- x = x !== undefined ? x : 0;
- y = y !== undefined ? y : 0;
- fontSize = fontSize !== undefined ? fontSize : 24;
- scale = 1 / this.path.unitsPerEm * fontSize;
- ctx.lineWidth = 1;
- // Draw the origin
- ctx.strokeStyle = 'black';
- draw.line(ctx, x, -10000, x, 10000);
- draw.line(ctx, -10000, y, 10000, y);
- // This code is here due to memory optimization: by not using
- // defaults in the constructor, we save a notable amount of memory.
- var xMin = this.xMin || 0;
- var yMin = this.yMin || 0;
- var xMax = this.xMax || 0;
- var yMax = this.yMax || 0;
- var advanceWidth = this.advanceWidth || 0;
- // Draw the glyph box
- ctx.strokeStyle = 'blue';
- draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000);
- draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000);
- draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale));
- draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale));
- // Draw the advance width
- ctx.strokeStyle = 'green';
- draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000);
- };
- // The GlyphSet object
- // Define a property on the glyph that depends on the path being loaded.
- function defineDependentProperty(glyph, externalName, internalName) {
- Object.defineProperty(glyph, externalName, {
- get: function() {
- // Request the path property to make sure the path is loaded.
- glyph.path; // jshint ignore:line
- return glyph[internalName];
- },
- set: function(newValue) {
- glyph[internalName] = newValue;
- },
- enumerable: true,
- configurable: true
- });
- }
- /**
- * A GlyphSet represents all glyphs available in the font, but modelled using
- * a deferred glyph loader, for retrieving glyphs only once they are absolutely
- * necessary, to keep the memory footprint down.
- * @exports opentype.GlyphSet
- * @class
- * @param {opentype.Font}
- * @param {Array}
- */
- function GlyphSet(font, glyphs) {
- this.font = font;
- this.glyphs = {};
- if (Array.isArray(glyphs)) {
- for (var i = 0; i < glyphs.length; i++) {
- var glyph = glyphs[i];
- glyph.path.unitsPerEm = font.unitsPerEm;
- this.glyphs[i] = glyph;
- }
- }
- this.length = (glyphs && glyphs.length) || 0;
- }
- /**
- * @param {number} index
- * @return {opentype.Glyph}
- */
- GlyphSet.prototype.get = function(index) {
- // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only.
- if (this.glyphs[index] === undefined) {
- this.font._push(index);
- if (typeof this.glyphs[index] === 'function') {
- this.glyphs[index] = this.glyphs[index]();
- }
- var glyph = this.glyphs[index];
- var unicodeObj = this.font._IndexToUnicodeMap[index];
- if (unicodeObj) {
- for (var j = 0; j < unicodeObj.unicodes.length; j++)
- { glyph.addUnicode(unicodeObj.unicodes[j]); }
- }
- if (this.font.cffEncoding) {
- if (this.font.isCIDFont) {
- glyph.name = 'gid' + index;
- } else {
- glyph.name = this.font.cffEncoding.charset[index];
- }
- } else if (this.font.glyphNames.names) {
- glyph.name = this.font.glyphNames.glyphIndexToName(index);
- }
- this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth;
- this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing;
- } else {
- if (typeof this.glyphs[index] === 'function') {
- this.glyphs[index] = this.glyphs[index]();
- }
- }
- return this.glyphs[index];
- };
- /**
- * @param {number} index
- * @param {Object}
- */
- GlyphSet.prototype.push = function(index, loader) {
- this.glyphs[index] = loader;
- this.length++;
- };
- /**
- * @alias opentype.glyphLoader
- * @param {opentype.Font} font
- * @param {number} index
- * @return {opentype.Glyph}
- */
- function glyphLoader(font, index) {
- return new Glyph({index: index, font: font});
- }
- /**
- * Generate a stub glyph that can be filled with all metadata *except*
- * the "points" and "path" properties, which must be loaded only once
- * the glyph's path is actually requested for text shaping.
- * @alias opentype.ttfGlyphLoader
- * @param {opentype.Font} font
- * @param {number} index
- * @param {Function} parseGlyph
- * @param {Object} data
- * @param {number} position
- * @param {Function} buildPath
- * @return {opentype.Glyph}
- */
- function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) {
- return function() {
- var glyph = new Glyph({index: index, font: font});
- glyph.path = function() {
- parseGlyph(glyph, data, position);
- var path = buildPath(font.glyphs, glyph);
- path.unitsPerEm = font.unitsPerEm;
- return path;
- };
- defineDependentProperty(glyph, 'xMin', '_xMin');
- defineDependentProperty(glyph, 'xMax', '_xMax');
- defineDependentProperty(glyph, 'yMin', '_yMin');
- defineDependentProperty(glyph, 'yMax', '_yMax');
- return glyph;
- };
- }
- /**
- * @alias opentype.cffGlyphLoader
- * @param {opentype.Font} font
- * @param {number} index
- * @param {Function} parseCFFCharstring
- * @param {string} charstring
- * @return {opentype.Glyph}
- */
- function cffGlyphLoader(font, index, parseCFFCharstring, charstring) {
- return function() {
- var glyph = new Glyph({index: index, font: font});
- glyph.path = function() {
- var path = parseCFFCharstring(font, glyph, charstring);
- path.unitsPerEm = font.unitsPerEm;
- return path;
- };
- return glyph;
- };
- }
- var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader };
- // The `CFF` table contains the glyph outlines in PostScript format.
- // Custom equals function that can also check lists.
- function equals(a, b) {
- if (a === b) {
- return true;
- } else if (Array.isArray(a) && Array.isArray(b)) {
- if (a.length !== b.length) {
- return false;
- }
- for (var i = 0; i < a.length; i += 1) {
- if (!equals(a[i], b[i])) {
- return false;
- }
- }
- return true;
- } else {
- return false;
- }
- }
- // Subroutines are encoded using the negative half of the number space.
- // See type 2 chapter 4.7 "Subroutine operators".
- function calcCFFSubroutineBias(subrs) {
- var bias;
- if (subrs.length < 1240) {
- bias = 107;
- } else if (subrs.length < 33900) {
- bias = 1131;
- } else {
- bias = 32768;
- }
- return bias;
- }
- // Parse a `CFF` INDEX array.
- // An index array consists of a list of offsets, then a list of objects at those offsets.
- function parseCFFIndex(data, start, conversionFn) {
- var offsets = [];
- var objects = [];
- var count = parse.getCard16(data, start);
- var objectOffset;
- var endOffset;
- if (count !== 0) {
- var offsetSize = parse.getByte(data, start + 2);
- objectOffset = start + ((count + 1) * offsetSize) + 2;
- var pos = start + 3;
- for (var i = 0; i < count + 1; i += 1) {
- offsets.push(parse.getOffset(data, pos, offsetSize));
- pos += offsetSize;
- }
- // The total size of the index array is 4 header bytes + the value of the last offset.
- endOffset = objectOffset + offsets[count];
- } else {
- endOffset = start + 2;
- }
- for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) {
- var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]);
- if (conversionFn) {
- value = conversionFn(value);
- }
- objects.push(value);
- }
- return {objects: objects, startOffset: start, endOffset: endOffset};
- }
- function parseCFFIndexLowMemory(data, start) {
- var offsets = [];
- var count = parse.getCard16(data, start);
- var objectOffset;
- var endOffset;
- if (count !== 0) {
- var offsetSize = parse.getByte(data, start + 2);
- objectOffset = start + ((count + 1) * offsetSize) + 2;
- var pos = start + 3;
- for (var i = 0; i < count + 1; i += 1) {
- offsets.push(parse.getOffset(data, pos, offsetSize));
- pos += offsetSize;
- }
- // The total size of the index array is 4 header bytes + the value of the last offset.
- endOffset = objectOffset + offsets[count];
- } else {
- endOffset = start + 2;
- }
- return {offsets: offsets, startOffset: start, endOffset: endOffset};
- }
- function getCffIndexObject(i, offsets, data, start, conversionFn) {
- var count = parse.getCard16(data, start);
- var objectOffset = 0;
- if (count !== 0) {
- var offsetSize = parse.getByte(data, start + 2);
- objectOffset = start + ((count + 1) * offsetSize) + 2;
- }
- var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]);
- if (conversionFn) {
- value = conversionFn(value);
- }
- return value;
- }
- // Parse a `CFF` DICT real value.
- function parseFloatOperand(parser) {
- var s = '';
- var eof = 15;
- var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-'];
- while (true) {
- var b = parser.parseByte();
- var n1 = b >> 4;
- var n2 = b & 15;
- if (n1 === eof) {
- break;
- }
- s += lookup[n1];
- if (n2 === eof) {
- break;
- }
- s += lookup[n2];
- }
- return parseFloat(s);
- }
- // Parse a `CFF` DICT operand.
- function parseOperand(parser, b0) {
- var b1;
- var b2;
- var b3;
- var b4;
- if (b0 === 28) {
- b1 = parser.parseByte();
- b2 = parser.parseByte();
- return b1 << 8 | b2;
- }
- if (b0 === 29) {
- b1 = parser.parseByte();
- b2 = parser.parseByte();
- b3 = parser.parseByte();
- b4 = parser.parseByte();
- return b1 << 24 | b2 << 16 | b3 << 8 | b4;
- }
- if (b0 === 30) {
- return parseFloatOperand(parser);
- }
- if (b0 >= 32 && b0 <= 246) {
- return b0 - 139;
- }
- if (b0 >= 247 && b0 <= 250) {
- b1 = parser.parseByte();
- return (b0 - 247) * 256 + b1 + 108;
- }
- if (b0 >= 251 && b0 <= 254) {
- b1 = parser.parseByte();
- return -(b0 - 251) * 256 - b1 - 108;
- }
- throw new Error('Invalid b0 ' + b0);
- }
- // Convert the entries returned by `parseDict` to a proper dictionary.
- // If a value is a list of one, it is unpacked.
- function entriesToObject(entries) {
- var o = {};
- for (var i = 0; i < entries.length; i += 1) {
- var key = entries[i][0];
- var values = entries[i][1];
- var value = (void 0);
- if (values.length === 1) {
- value = values[0];
- } else {
- value = values;
- }
- if (o.hasOwnProperty(key) && !isNaN(o[key])) {
- throw new Error('Object ' + o + ' already has key ' + key);
- }
- o[key] = value;
- }
- return o;
- }
- // Parse a `CFF` DICT object.
- // A dictionary contains key-value pairs in a compact tokenized format.
- function parseCFFDict(data, start, size) {
- start = start !== undefined ? start : 0;
- var parser = new parse.Parser(data, start);
- var entries = [];
- var operands = [];
- size = size !== undefined ? size : data.length;
- while (parser.relativeOffset < size) {
- var op = parser.parseByte();
- // The first byte for each dict item distinguishes between operator (key) and operand (value).
- // Values <= 21 are operators.
- if (op <= 21) {
- // Two-byte operators have an initial escape byte of 12.
- if (op === 12) {
- op = 1200 + parser.parseByte();
- }
- entries.push([op, operands]);
- operands = [];
- } else {
- // Since the operands (values) come before the operators (keys), we store all operands in a list
- // until we encounter an operator.
- operands.push(parseOperand(parser, op));
- }
- }
- return entriesToObject(entries);
- }
- // Given a String Index (SID), return the value of the string.
- // Strings below index 392 are standard CFF strings and are not encoded in the font.
- function getCFFString(strings, index) {
- if (index <= 390) {
- index = cffStandardStrings[index];
- } else {
- index = strings[index - 391];
- }
- return index;
- }
- // Interpret a dictionary and return a new dictionary with readable keys and values for missing entries.
- // This function takes `meta` which is a list of objects containing `operand`, `name` and `default`.
- function interpretDict(dict, meta, strings) {
- var newDict = {};
- var value;
- // Because we also want to include missing values, we start out from the meta list
- // and lookup values in the dict.
- for (var i = 0; i < meta.length; i += 1) {
- var m = meta[i];
- if (Array.isArray(m.type)) {
- var values = [];
- values.length = m.type.length;
- for (var j = 0; j < m.type.length; j++) {
- value = dict[m.op] !== undefined ? dict[m.op][j] : undefined;
- if (value === undefined) {
- value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null;
- }
- if (m.type[j] === 'SID') {
- value = getCFFString(strings, value);
- }
- values[j] = value;
- }
- newDict[m.name] = values;
- } else {
- value = dict[m.op];
- if (value === undefined) {
- value = m.value !== undefined ? m.value : null;
- }
- if (m.type === 'SID') {
- value = getCFFString(strings, value);
- }
- newDict[m.name] = value;
- }
- }
- return newDict;
- }
- // Parse the CFF header.
- function parseCFFHeader(data, start) {
- var header = {};
- header.formatMajor = parse.getCard8(data, start);
- header.formatMinor = parse.getCard8(data, start + 1);
- header.size = parse.getCard8(data, start + 2);
- header.offsetSize = parse.getCard8(data, start + 3);
- header.startOffset = start;
- header.endOffset = start + 4;
- return header;
- }
- var TOP_DICT_META = [
- {name: 'version', op: 0, type: 'SID'},
- {name: 'notice', op: 1, type: 'SID'},
- {name: 'copyright', op: 1200, type: 'SID'},
- {name: 'fullName', op: 2, type: 'SID'},
- {name: 'familyName', op: 3, type: 'SID'},
- {name: 'weight', op: 4, type: 'SID'},
- {name: 'isFixedPitch', op: 1201, type: 'number', value: 0},
- {name: 'italicAngle', op: 1202, type: 'number', value: 0},
- {name: 'underlinePosition', op: 1203, type: 'number', value: -100},
- {name: 'underlineThickness', op: 1204, type: 'number', value: 50},
- {name: 'paintType', op: 1205, type: 'number', value: 0},
- {name: 'charstringType', op: 1206, type: 'number', value: 2},
- {
- name: 'fontMatrix',
- op: 1207,
- type: ['real', 'real', 'real', 'real', 'real', 'real'],
- value: [0.001, 0, 0, 0.001, 0, 0]
- },
- {name: 'uniqueId', op: 13, type: 'number'},
- {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]},
- {name: 'strokeWidth', op: 1208, type: 'number', value: 0},
- {name: 'xuid', op: 14, type: [], value: null},
- {name: 'charset', op: 15, type: 'offset', value: 0},
- {name: 'encoding', op: 16, type: 'offset', value: 0},
- {name: 'charStrings', op: 17, type: 'offset', value: 0},
- {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]},
- {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']},
- {name: 'cidFontVersion', op: 1231, type: 'number', value: 0},
- {name: 'cidFontRevision', op: 1232, type: 'number', value: 0},
- {name: 'cidFontType', op: 1233, type: 'number', value: 0},
- {name: 'cidCount', op: 1234, type: 'number', value: 8720},
- {name: 'uidBase', op: 1235, type: 'number'},
- {name: 'fdArray', op: 1236, type: 'offset'},
- {name: 'fdSelect', op: 1237, type: 'offset'},
- {name: 'fontName', op: 1238, type: 'SID'}
- ];
- var PRIVATE_DICT_META = [
- {name: 'subrs', op: 19, type: 'offset', value: 0},
- {name: 'defaultWidthX', op: 20, type: 'number', value: 0},
- {name: 'nominalWidthX', op: 21, type: 'number', value: 0}
- ];
- // Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary.
- // The top dictionary contains the essential metadata for the font, together with the private dictionary.
- function parseCFFTopDict(data, strings) {
- var dict = parseCFFDict(data, 0, data.byteLength);
- return interpretDict(dict, TOP_DICT_META, strings);
- }
- // Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need.
- function parseCFFPrivateDict(data, start, size, strings) {
- var dict = parseCFFDict(data, start, size);
- return interpretDict(dict, PRIVATE_DICT_META, strings);
- }
- // Returns a list of "Top DICT"s found using an INDEX list.
- // Used to read both the usual high-level Top DICTs and also the FDArray
- // discovered inside CID-keyed fonts. When a Top DICT has a reference to
- // a Private DICT that is read and saved into the Top DICT.
- //
- // In addition to the expected/optional values as outlined in TOP_DICT_META
- // the following values might be saved into the Top DICT.
- //
- // _subrs [] array of local CFF subroutines from Private DICT
- // _subrsBias bias value computed from number of subroutines
- // (see calcCFFSubroutineBias() and parseCFFCharstring())
- // _defaultWidthX default widths for CFF characters
- // _nominalWidthX bias added to width embedded within glyph description
- //
- // _privateDict saved copy of parsed Private DICT from Top DICT
- function gatherCFFTopDicts(data, start, cffIndex, strings) {
- var topDictArray = [];
- for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) {
- var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer);
- var topDict = parseCFFTopDict(topDictData, strings);
- topDict._subrs = [];
- topDict._subrsBias = 0;
- topDict._defaultWidthX = 0;
- topDict._nominalWidthX = 0;
- var privateSize = topDict.private[0];
- var privateOffset = topDict.private[1];
- if (privateSize !== 0 && privateOffset !== 0) {
- var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings);
- topDict._defaultWidthX = privateDict.defaultWidthX;
- topDict._nominalWidthX = privateDict.nominalWidthX;
- if (privateDict.subrs !== 0) {
- var subrOffset = privateOffset + privateDict.subrs;
- var subrIndex = parseCFFIndex(data, subrOffset + start);
- topDict._subrs = subrIndex.objects;
- topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs);
- }
- topDict._privateDict = privateDict;
- }
- topDictArray.push(topDict);
- }
- return topDictArray;
- }
- // Parse the CFF charset table, which contains internal names for all the glyphs.
- // This function will return a list of glyph names.
- // See Adobe TN #5176 chapter 13, "Charsets".
- function parseCFFCharset(data, start, nGlyphs, strings) {
- var sid;
- var count;
- var parser = new parse.Parser(data, start);
- // The .notdef glyph is not included, so subtract 1.
- nGlyphs -= 1;
- var charset = ['.notdef'];
- var format = parser.parseCard8();
- if (format === 0) {
- for (var i = 0; i < nGlyphs; i += 1) {
- sid = parser.parseSID();
- charset.push(getCFFString(strings, sid));
- }
- } else if (format === 1) {
- while (charset.length <= nGlyphs) {
- sid = parser.parseSID();
- count = parser.parseCard8();
- for (var i$1 = 0; i$1 <= count; i$1 += 1) {
- charset.push(getCFFString(strings, sid));
- sid += 1;
- }
- }
- } else if (format === 2) {
- while (charset.length <= nGlyphs) {
- sid = parser.parseSID();
- count = parser.parseCard16();
- for (var i$2 = 0; i$2 <= count; i$2 += 1) {
- charset.push(getCFFString(strings, sid));
- sid += 1;
- }
- }
- } else {
- throw new Error('Unknown charset format ' + format);
- }
- return charset;
- }
- // Parse the CFF encoding data. Only one encoding can be specified per font.
- // See Adobe TN #5176 chapter 12, "Encodings".
- function parseCFFEncoding(data, start, charset) {
- var code;
- var enc = {};
- var parser = new parse.Parser(data, start);
- var format = parser.parseCard8();
- if (format === 0) {
- var nCodes = parser.parseCard8();
- for (var i = 0; i < nCodes; i += 1) {
- code = parser.parseCard8();
- enc[code] = i;
- }
- } else if (format === 1) {
- var nRanges = parser.parseCard8();
- code = 1;
- for (var i$1 = 0; i$1 < nRanges; i$1 += 1) {
- var first = parser.parseCard8();
- var nLeft = parser.parseCard8();
- for (var j = first; j <= first + nLeft; j += 1) {
- enc[j] = code;
- code += 1;
- }
- }
- } else {
- throw new Error('Unknown encoding format ' + format);
- }
- return new CffEncoding(enc, charset);
- }
- // Take in charstring code and return a Glyph object.
- // The encoding is described in the Type 2 Charstring Format
- // https://www.microsoft.com/typography/OTSPEC/charstr2.htm
- function parseCFFCharstring(font, glyph, code) {
- var c1x;
- var c1y;
- var c2x;
- var c2y;
- var p = new Path();
- var stack = [];
- var nStems = 0;
- var haveWidth = false;
- var open = false;
- var x = 0;
- var y = 0;
- var subrs;
- var subrsBias;
- var defaultWidthX;
- var nominalWidthX;
- if (font.isCIDFont) {
- var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index];
- var fdDict = font.tables.cff.topDict._fdArray[fdIndex];
- subrs = fdDict._subrs;
- subrsBias = fdDict._subrsBias;
- defaultWidthX = fdDict._defaultWidthX;
- nominalWidthX = fdDict._nominalWidthX;
- } else {
- subrs = font.tables.cff.topDict._subrs;
- subrsBias = font.tables.cff.topDict._subrsBias;
- defaultWidthX = font.tables.cff.topDict._defaultWidthX;
- nominalWidthX = font.tables.cff.topDict._nominalWidthX;
- }
- var width = defaultWidthX;
- function newContour(x, y) {
- if (open) {
- p.closePath();
- }
- p.moveTo(x, y);
- open = true;
- }
- function parseStems() {
- var hasWidthArg;
- // The number of stem operators on the stack is always even.
- // If the value is uneven, that means a width is specified.
- hasWidthArg = stack.length % 2 !== 0;
- if (hasWidthArg && !haveWidth) {
- width = stack.shift() + nominalWidthX;
- }
- nStems += stack.length >> 1;
- stack.length = 0;
- haveWidth = true;
- }
- function parse(code) {
- var b1;
- var b2;
- var b3;
- var b4;
- var codeIndex;
- var subrCode;
- var jpx;
- var jpy;
- var c3x;
- var c3y;
- var c4x;
- var c4y;
- var i = 0;
- while (i < code.length) {
- var v = code[i];
- i += 1;
- switch (v) {
- case 1: // hstem
- parseStems();
- break;
- case 3: // vstem
- parseStems();
- break;
- case 4: // vmoveto
- if (stack.length > 1 && !haveWidth) {
- width = stack.shift() + nominalWidthX;
- haveWidth = true;
- }
- y += stack.pop();
- newContour(x, y);
- break;
- case 5: // rlineto
- while (stack.length > 0) {
- x += stack.shift();
- y += stack.shift();
- p.lineTo(x, y);
- }
- break;
- case 6: // hlineto
- while (stack.length > 0) {
- x += stack.shift();
- p.lineTo(x, y);
- if (stack.length === 0) {
- break;
- }
- y += stack.shift();
- p.lineTo(x, y);
- }
- break;
- case 7: // vlineto
- while (stack.length > 0) {
- y += stack.shift();
- p.lineTo(x, y);
- if (stack.length === 0) {
- break;
- }
- x += stack.shift();
- p.lineTo(x, y);
- }
- break;
- case 8: // rrcurveto
- while (stack.length > 0) {
- c1x = x + stack.shift();
- c1y = y + stack.shift();
- c2x = c1x + stack.shift();
- c2y = c1y + stack.shift();
- x = c2x + stack.shift();
- y = c2y + stack.shift();
- p.curveTo(c1x, c1y, c2x, c2y, x, y);
- }
- break;
- case 10: // callsubr
- codeIndex = stack.pop() + subrsBias;
- subrCode = subrs[codeIndex];
- if (subrCode) {
- parse(subrCode);
- }
- break;
- case 11: // return
- return;
- case 12: // flex operators
- v = code[i];
- i += 1;
- switch (v) {
- case 35: // flex
- // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |-
- c1x = x + stack.shift(); // dx1
- c1y = y + stack.shift(); // dy1
- c2x = c1x + stack.shift(); // dx2
- c2y = c1y + stack.shift(); // dy2
- jpx = c2x + stack.shift(); // dx3
- jpy = c2y + stack.shift(); // dy3
- c3x = jpx + stack.shift(); // dx4
- c3y = jpy + stack.shift(); // dy4
- c4x = c3x + stack.shift(); // dx5
- c4y = c3y + stack.shift(); // dy5
- x = c4x + stack.shift(); // dx6
- y = c4y + stack.shift(); // dy6
- stack.shift(); // flex depth
- p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
- p.curveTo(c3x, c3y, c4x, c4y, x, y);
- break;
- case 34: // hflex
- // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |-
- c1x = x + stack.shift(); // dx1
- c1y = y; // dy1
- c2x = c1x + stack.shift(); // dx2
- c2y = c1y + stack.shift(); // dy2
- jpx = c2x + stack.shift(); // dx3
- jpy = c2y; // dy3
- c3x = jpx + stack.shift(); // dx4
- c3y = c2y; // dy4
- c4x = c3x + stack.shift(); // dx5
- c4y = y; // dy5
- x = c4x + stack.shift(); // dx6
- p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
- p.curveTo(c3x, c3y, c4x, c4y, x, y);
- break;
- case 36: // hflex1
- // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |-
- c1x = x + stack.shift(); // dx1
- c1y = y + stack.shift(); // dy1
- c2x = c1x + stack.shift(); // dx2
- c2y = c1y + stack.shift(); // dy2
- jpx = c2x + stack.shift(); // dx3
- jpy = c2y; // dy3
- c3x = jpx + stack.shift(); // dx4
- c3y = c2y; // dy4
- c4x = c3x + stack.shift(); // dx5
- c4y = c3y + stack.shift(); // dy5
- x = c4x + stack.shift(); // dx6
- p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
- p.curveTo(c3x, c3y, c4x, c4y, x, y);
- break;
- case 37: // flex1
- // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |-
- c1x = x + stack.shift(); // dx1
- c1y = y + stack.shift(); // dy1
- c2x = c1x + stack.shift(); // dx2
- c2y = c1y + stack.shift(); // dy2
- jpx = c2x + stack.shift(); // dx3
- jpy = c2y + stack.shift(); // dy3
- c3x = jpx + stack.shift(); // dx4
- c3y = jpy + stack.shift(); // dy4
- c4x = c3x + stack.shift(); // dx5
- c4y = c3y + stack.shift(); // dy5
- if (Math.abs(c4x - x) > Math.abs(c4y - y)) {
- x = c4x + stack.shift();
- } else {
- y = c4y + stack.shift();
- }
- p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
- p.curveTo(c3x, c3y, c4x, c4y, x, y);
- break;
- default:
- console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v);
- stack.length = 0;
- }
- break;
- case 14: // endchar
- if (stack.length > 0 && !haveWidth) {
- width = stack.shift() + nominalWidthX;
- haveWidth = true;
- }
- if (open) {
- p.closePath();
- open = false;
- }
- break;
- case 18: // hstemhm
- parseStems();
- break;
- case 19: // hintmask
- case 20: // cntrmask
- parseStems();
- i += (nStems + 7) >> 3;
- break;
- case 21: // rmoveto
- if (stack.length > 2 && !haveWidth) {
- width = stack.shift() + nominalWidthX;
- haveWidth = true;
- }
- y += stack.pop();
- x += stack.pop();
- newContour(x, y);
- break;
- case 22: // hmoveto
- if (stack.length > 1 && !haveWidth) {
- width = stack.shift() + nominalWidthX;
- haveWidth = true;
- }
- x += stack.pop();
- newContour(x, y);
- break;
- case 23: // vstemhm
- parseStems();
- break;
- case 24: // rcurveline
- while (stack.length > 2) {
- c1x = x + stack.shift();
- c1y = y + stack.shift();
- c2x = c1x + stack.shift();
- c2y = c1y + stack.shift();
- x = c2x + stack.shift();
- y = c2y + stack.shift();
- p.curveTo(c1x, c1y, c2x, c2y, x, y);
- }
- x += stack.shift();
- y += stack.shift();
- p.lineTo(x, y);
- break;
- case 25: // rlinecurve
- while (stack.length > 6) {
- x += stack.shift();
- y += stack.shift();
- p.lineTo(x, y);
- }
- c1x = x + stack.shift();
- c1y = y + stack.shift();
- c2x = c1x + stack.shift();
- c2y = c1y + stack.shift();
- x = c2x + stack.shift();
- y = c2y + stack.shift();
- p.curveTo(c1x, c1y, c2x, c2y, x, y);
- break;
- case 26: // vvcurveto
- if (stack.length % 2) {
- x += stack.shift();
- }
- while (stack.length > 0) {
- c1x = x;
- c1y = y + stack.shift();
- c2x = c1x + stack.shift();
- c2y = c1y + stack.shift();
- x = c2x;
- y = c2y + stack.shift();
- p.curveTo(c1x, c1y, c2x, c2y, x, y);
- }
- break;
- case 27: // hhcurveto
- if (stack.length % 2) {
- y += stack.shift();
- }
- while (stack.length > 0) {
- c1x = x + stack.shift();
- c1y = y;
- c2x = c1x + stack.shift();
- c2y = c1y + stack.shift();
- x = c2x + stack.shift();
- y = c2y;
- p.curveTo(c1x, c1y, c2x, c2y, x, y);
- }
- break;
- case 28: // shortint
- b1 = code[i];
- b2 = code[i + 1];
- stack.push(((b1 << 24) | (b2 << 16)) >> 16);
- i += 2;
- break;
- case 29: // callgsubr
- codeIndex = stack.pop() + font.gsubrsBias;
- subrCode = font.gsubrs[codeIndex];
- if (subrCode) {
- parse(subrCode);
- }
- break;
- case 30: // vhcurveto
- while (stack.length > 0) {
- c1x = x;
- c1y = y + stack.shift();
- c2x = c1x + stack.shift();
- c2y = c1y + stack.shift();
- x = c2x + stack.shift();
- y = c2y + (stack.length === 1 ? stack.shift() : 0);
- p.curveTo(c1x, c1y, c2x, c2y, x, y);
- if (stack.length === 0) {
- break;
- }
- c1x = x + stack.shift();
- c1y = y;
- c2x = c1x + stack.shift();
- c2y = c1y + stack.shift();
- y = c2y + stack.shift();
- x = c2x + (stack.length === 1 ? stack.shift() : 0);
- p.curveTo(c1x, c1y, c2x, c2y, x, y);
- }
- break;
- case 31: // hvcurveto
- while (stack.length > 0) {
- c1x = x + stack.shift();
- c1y = y;
- c2x = c1x + stack.shift();
- c2y = c1y + stack.shift();
- y = c2y + stack.shift();
- x = c2x + (stack.length === 1 ? stack.shift() : 0);
- p.curveTo(c1x, c1y, c2x, c2y, x, y);
- if (stack.length === 0) {
- break;
- }
- c1x = x;
- c1y = y + stack.shift();
- c2x = c1x + stack.shift();
- c2y = c1y + stack.shift();
- x = c2x + stack.shift();
- y = c2y + (stack.length === 1 ? stack.shift() : 0);
- p.curveTo(c1x, c1y, c2x, c2y, x, y);
- }
- break;
- default:
- if (v < 32) {
- console.log('Glyph ' + glyph.index + ': unknown operator ' + v);
- } else if (v < 247) {
- stack.push(v - 139);
- } else if (v < 251) {
- b1 = code[i];
- i += 1;
- stack.push((v - 247) * 256 + b1 + 108);
- } else if (v < 255) {
- b1 = code[i];
- i += 1;
- stack.push(-(v - 251) * 256 - b1 - 108);
- } else {
- b1 = code[i];
- b2 = code[i + 1];
- b3 = code[i + 2];
- b4 = code[i + 3];
- i += 4;
- stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536);
- }
- }
- }
- }
- parse(code);
- glyph.advanceWidth = width;
- return p;
- }
- function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) {
- var fdSelect = [];
- var fdIndex;
- var parser = new parse.Parser(data, start);
- var format = parser.parseCard8();
- if (format === 0) {
- // Simple list of nGlyphs elements
- for (var iGid = 0; iGid < nGlyphs; iGid++) {
- fdIndex = parser.parseCard8();
- if (fdIndex >= fdArrayCount) {
- throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')');
- }
- fdSelect.push(fdIndex);
- }
- } else if (format === 3) {
- // Ranges
- var nRanges = parser.parseCard16();
- var first = parser.parseCard16();
- if (first !== 0) {
- throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first);
- }
- var next;
- for (var iRange = 0; iRange < nRanges; iRange++) {
- fdIndex = parser.parseCard8();
- next = parser.parseCard16();
- if (fdIndex >= fdArrayCount) {
- throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')');
- }
- if (next > nGlyphs) {
- throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next);
- }
- for (; first < next; first++) {
- fdSelect.push(fdIndex);
- }
- first = next;
- }
- if (next !== nGlyphs) {
- throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next);
- }
- } else {
- throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format);
- }
- return fdSelect;
- }
- // Parse the `CFF` table, which contains the glyph outlines in PostScript format.
- function parseCFFTable(data, start, font, opt) {
- font.tables.cff = {};
- var header = parseCFFHeader(data, start);
- var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString);
- var topDictIndex = parseCFFIndex(data, nameIndex.endOffset);
- var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString);
- var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset);
- font.gsubrs = globalSubrIndex.objects;
- font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs);
- var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects);
- if (topDictArray.length !== 1) {
- throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length);
- }
- var topDict = topDictArray[0];
- font.tables.cff.topDict = topDict;
- if (topDict._privateDict) {
- font.defaultWidthX = topDict._privateDict.defaultWidthX;
- font.nominalWidthX = topDict._privateDict.nominalWidthX;
- }
- if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) {
- font.isCIDFont = true;
- }
- if (font.isCIDFont) {
- var fdArrayOffset = topDict.fdArray;
- var fdSelectOffset = topDict.fdSelect;
- if (fdArrayOffset === 0 || fdSelectOffset === 0) {
- throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing');
- }
- fdArrayOffset += start;
- var fdArrayIndex = parseCFFIndex(data, fdArrayOffset);
- var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects);
- topDict._fdArray = fdArray;
- fdSelectOffset += start;
- topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length);
- }
- var privateDictOffset = start + topDict.private[1];
- var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects);
- font.defaultWidthX = privateDict.defaultWidthX;
- font.nominalWidthX = privateDict.nominalWidthX;
- if (privateDict.subrs !== 0) {
- var subrOffset = privateDictOffset + privateDict.subrs;
- var subrIndex = parseCFFIndex(data, subrOffset);
- font.subrs = subrIndex.objects;
- font.subrsBias = calcCFFSubroutineBias(font.subrs);
- } else {
- font.subrs = [];
- font.subrsBias = 0;
- }
- // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset.
- var charStringsIndex;
- if (opt.lowMemory) {
- charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings);
- font.nGlyphs = charStringsIndex.offsets.length;
- } else {
- charStringsIndex = parseCFFIndex(data, start + topDict.charStrings);
- font.nGlyphs = charStringsIndex.objects.length;
- }
- var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects);
- if (topDict.encoding === 0) {
- // Standard encoding
- font.cffEncoding = new CffEncoding(cffStandardEncoding, charset);
- } else if (topDict.encoding === 1) {
- // Expert encoding
- font.cffEncoding = new CffEncoding(cffExpertEncoding, charset);
- } else {
- font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset);
- }
- // Prefer the CMAP encoding to the CFF encoding.
- font.encoding = font.encoding || font.cffEncoding;
- font.glyphs = new glyphset.GlyphSet(font);
- if (opt.lowMemory) {
- font._push = function(i) {
- var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings);
- font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString));
- };
- } else {
- for (var i = 0; i < font.nGlyphs; i += 1) {
- var charString = charStringsIndex.objects[i];
- font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString));
- }
- }
- }
- // Convert a string to a String ID (SID).
- // The list of strings is modified in place.
- function encodeString(s, strings) {
- var sid;
- // Is the string in the CFF standard strings?
- var i = cffStandardStrings.indexOf(s);
- if (i >= 0) {
- sid = i;
- }
- // Is the string already in the string index?
- i = strings.indexOf(s);
- if (i >= 0) {
- sid = i + cffStandardStrings.length;
- } else {
- sid = cffStandardStrings.length + strings.length;
- strings.push(s);
- }
- return sid;
- }
- function makeHeader() {
- return new table.Record('Header', [
- {name: 'major', type: 'Card8', value: 1},
- {name: 'minor', type: 'Card8', value: 0},
- {name: 'hdrSize', type: 'Card8', value: 4},
- {name: 'major', type: 'Card8', value: 1}
- ]);
- }
- function makeNameIndex(fontNames) {
- var t = new table.Record('Name INDEX', [
- {name: 'names', type: 'INDEX', value: []}
- ]);
- t.names = [];
- for (var i = 0; i < fontNames.length; i += 1) {
- t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]});
- }
- return t;
- }
- // Given a dictionary's metadata, create a DICT structure.
- function makeDict(meta, attrs, strings) {
- var m = {};
- for (var i = 0; i < meta.length; i += 1) {
- var entry = meta[i];
- var value = attrs[entry.name];
- if (value !== undefined && !equals(value, entry.value)) {
- if (entry.type === 'SID') {
- value = encodeString(value, strings);
- }
- m[entry.op] = {name: entry.name, type: entry.type, value: value};
- }
- }
- return m;
- }
- // The Top DICT houses the global font attributes.
- function makeTopDict(attrs, strings) {
- var t = new table.Record('Top DICT', [
- {name: 'dict', type: 'DICT', value: {}}
- ]);
- t.dict = makeDict(TOP_DICT_META, attrs, strings);
- return t;
- }
- function makeTopDictIndex(topDict) {
- var t = new table.Record('Top DICT INDEX', [
- {name: 'topDicts', type: 'INDEX', value: []}
- ]);
- t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}];
- return t;
- }
- function makeStringIndex(strings) {
- var t = new table.Record('String INDEX', [
- {name: 'strings', type: 'INDEX', value: []}
- ]);
- t.strings = [];
- for (var i = 0; i < strings.length; i += 1) {
- t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]});
- }
- return t;
- }
- function makeGlobalSubrIndex() {
- // Currently we don't use subroutines.
- return new table.Record('Global Subr INDEX', [
- {name: 'subrs', type: 'INDEX', value: []}
- ]);
- }
- function makeCharsets(glyphNames, strings) {
- var t = new table.Record('Charsets', [
- {name: 'format', type: 'Card8', value: 0}
- ]);
- for (var i = 0; i < glyphNames.length; i += 1) {
- var glyphName = glyphNames[i];
- var glyphSID = encodeString(glyphName, strings);
- t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID});
- }
- return t;
- }
- function glyphToOps(glyph) {
- var ops = [];
- var path = glyph.path;
- ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth});
- var x = 0;
- var y = 0;
- for (var i = 0; i < path.commands.length; i += 1) {
- var dx = (void 0);
- var dy = (void 0);
- var cmd = path.commands[i];
- if (cmd.type === 'Q') {
- // CFF only supports bézier curves, so convert the quad to a bézier.
- var _13 = 1 / 3;
- var _23 = 2 / 3;
- // We're going to create a new command so we don't change the original path.
- // Since all coordinates are relative, we round() them ASAP to avoid propagating errors.
- cmd = {
- type: 'C',
- x: cmd.x,
- y: cmd.y,
- x1: Math.round(_13 * x + _23 * cmd.x1),
- y1: Math.round(_13 * y + _23 * cmd.y1),
- x2: Math.round(_13 * cmd.x + _23 * cmd.x1),
- y2: Math.round(_13 * cmd.y + _23 * cmd.y1)
- };
- }
- if (cmd.type === 'M') {
- dx = Math.round(cmd.x - x);
- dy = Math.round(cmd.y - y);
- ops.push({name: 'dx', type: 'NUMBER', value: dx});
- ops.push({name: 'dy', type: 'NUMBER', value: dy});
- ops.push({name: 'rmoveto', type: 'OP', value: 21});
- x = Math.round(cmd.x);
- y = Math.round(cmd.y);
- } else if (cmd.type === 'L') {
- dx = Math.round(cmd.x - x);
- dy = Math.round(cmd.y - y);
- ops.push({name: 'dx', type: 'NUMBER', value: dx});
- ops.push({name: 'dy', type: 'NUMBER', value: dy});
- ops.push({name: 'rlineto', type: 'OP', value: 5});
- x = Math.round(cmd.x);
- y = Math.round(cmd.y);
- } else if (cmd.type === 'C') {
- var dx1 = Math.round(cmd.x1 - x);
- var dy1 = Math.round(cmd.y1 - y);
- var dx2 = Math.round(cmd.x2 - cmd.x1);
- var dy2 = Math.round(cmd.y2 - cmd.y1);
- dx = Math.round(cmd.x - cmd.x2);
- dy = Math.round(cmd.y - cmd.y2);
- ops.push({name: 'dx1', type: 'NUMBER', value: dx1});
- ops.push({name: 'dy1', type: 'NUMBER', value: dy1});
- ops.push({name: 'dx2', type: 'NUMBER', value: dx2});
- ops.push({name: 'dy2', type: 'NUMBER', value: dy2});
- ops.push({name: 'dx', type: 'NUMBER', value: dx});
- ops.push({name: 'dy', type: 'NUMBER', value: dy});
- ops.push({name: 'rrcurveto', type: 'OP', value: 8});
- x = Math.round(cmd.x);
- y = Math.round(cmd.y);
- }
- // Contours are closed automatically.
- }
- ops.push({name: 'endchar', type: 'OP', value: 14});
- return ops;
- }
- function makeCharStringsIndex(glyphs) {
- var t = new table.Record('CharStrings INDEX', [
- {name: 'charStrings', type: 'INDEX', value: []}
- ]);
- for (var i = 0; i < glyphs.length; i += 1) {
- var glyph = glyphs.get(i);
- var ops = glyphToOps(glyph);
- t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops});
- }
- return t;
- }
- function makePrivateDict(attrs, strings) {
- var t = new table.Record('Private DICT', [
- {name: 'dict', type: 'DICT', value: {}}
- ]);
- t.dict = makeDict(PRIVATE_DICT_META, attrs, strings);
- return t;
- }
- function makeCFFTable(glyphs, options) {
- var t = new table.Table('CFF ', [
- {name: 'header', type: 'RECORD'},
- {name: 'nameIndex', type: 'RECORD'},
- {name: 'topDictIndex', type: 'RECORD'},
- {name: 'stringIndex', type: 'RECORD'},
- {name: 'globalSubrIndex', type: 'RECORD'},
- {name: 'charsets', type: 'RECORD'},
- {name: 'charStringsIndex', type: 'RECORD'},
- {name: 'privateDict', type: 'RECORD'}
- ]);
- var fontScale = 1 / options.unitsPerEm;
- // We use non-zero values for the offsets so that the DICT encodes them.
- // This is important because the size of the Top DICT plays a role in offset calculation,
- // and the size shouldn't change after we've written correct offsets.
- var attrs = {
- version: options.version,
- fullName: options.fullName,
- familyName: options.familyName,
- weight: options.weightName,
- fontBBox: options.fontBBox || [0, 0, 0, 0],
- fontMatrix: [fontScale, 0, 0, fontScale, 0, 0],
- charset: 999,
- encoding: 0,
- charStrings: 999,
- private: [0, 999]
- };
- var privateAttrs = {};
- var glyphNames = [];
- var glyph;
- // Skip first glyph (.notdef)
- for (var i = 1; i < glyphs.length; i += 1) {
- glyph = glyphs.get(i);
- glyphNames.push(glyph.name);
- }
- var strings = [];
- t.header = makeHeader();
- t.nameIndex = makeNameIndex([options.postScriptName]);
- var topDict = makeTopDict(attrs, strings);
- t.topDictIndex = makeTopDictIndex(topDict);
- t.globalSubrIndex = makeGlobalSubrIndex();
- t.charsets = makeCharsets(glyphNames, strings);
- t.charStringsIndex = makeCharStringsIndex(glyphs);
- t.privateDict = makePrivateDict(privateAttrs, strings);
- // Needs to come at the end, to encode all custom strings used in the font.
- t.stringIndex = makeStringIndex(strings);
- var startOffset = t.header.sizeOf() +
- t.nameIndex.sizeOf() +
- t.topDictIndex.sizeOf() +
- t.stringIndex.sizeOf() +
- t.globalSubrIndex.sizeOf();
- attrs.charset = startOffset;
- // We use the CFF standard encoding; proper encoding will be handled in cmap.
- attrs.encoding = 0;
- attrs.charStrings = attrs.charset + t.charsets.sizeOf();
- attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf();
- // Recreate the Top DICT INDEX with the correct offsets.
- topDict = makeTopDict(attrs, strings);
- t.topDictIndex = makeTopDictIndex(topDict);
- return t;
- }
- var cff = { parse: parseCFFTable, make: makeCFFTable };
- // The `head` table contains global information about the font.
- // Parse the header `head` table
- function parseHeadTable(data, start) {
- var head = {};
- var p = new parse.Parser(data, start);
- head.version = p.parseVersion();
- head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000;
- head.checkSumAdjustment = p.parseULong();
- head.magicNumber = p.parseULong();
- check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.');
- head.flags = p.parseUShort();
- head.unitsPerEm = p.parseUShort();
- head.created = p.parseLongDateTime();
- head.modified = p.parseLongDateTime();
- head.xMin = p.parseShort();
- head.yMin = p.parseShort();
- head.xMax = p.parseShort();
- head.yMax = p.parseShort();
- head.macStyle = p.parseUShort();
- head.lowestRecPPEM = p.parseUShort();
- head.fontDirectionHint = p.parseShort();
- head.indexToLocFormat = p.parseShort();
- head.glyphDataFormat = p.parseShort();
- return head;
- }
- function makeHeadTable(options) {
- // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970
- var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800;
- var createdTimestamp = timestamp;
- if (options.createdTimestamp) {
- createdTimestamp = options.createdTimestamp + 2082844800;
- }
- return new table.Table('head', [
- {name: 'version', type: 'FIXED', value: 0x00010000},
- {name: 'fontRevision', type: 'FIXED', value: 0x00010000},
- {name: 'checkSumAdjustment', type: 'ULONG', value: 0},
- {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5},
- {name: 'flags', type: 'USHORT', value: 0},
- {name: 'unitsPerEm', type: 'USHORT', value: 1000},
- {name: 'created', type: 'LONGDATETIME', value: createdTimestamp},
- {name: 'modified', type: 'LONGDATETIME', value: timestamp},
- {name: 'xMin', type: 'SHORT', value: 0},
- {name: 'yMin', type: 'SHORT', value: 0},
- {name: 'xMax', type: 'SHORT', value: 0},
- {name: 'yMax', type: 'SHORT', value: 0},
- {name: 'macStyle', type: 'USHORT', value: 0},
- {name: 'lowestRecPPEM', type: 'USHORT', value: 0},
- {name: 'fontDirectionHint', type: 'SHORT', value: 2},
- {name: 'indexToLocFormat', type: 'SHORT', value: 0},
- {name: 'glyphDataFormat', type: 'SHORT', value: 0}
- ], options);
- }
- var head = { parse: parseHeadTable, make: makeHeadTable };
- // The `hhea` table contains information for horizontal layout.
- // Parse the horizontal header `hhea` table
- function parseHheaTable(data, start) {
- var hhea = {};
- var p = new parse.Parser(data, start);
- hhea.version = p.parseVersion();
- hhea.ascender = p.parseShort();
- hhea.descender = p.parseShort();
- hhea.lineGap = p.parseShort();
- hhea.advanceWidthMax = p.parseUShort();
- hhea.minLeftSideBearing = p.parseShort();
- hhea.minRightSideBearing = p.parseShort();
- hhea.xMaxExtent = p.parseShort();
- hhea.caretSlopeRise = p.parseShort();
- hhea.caretSlopeRun = p.parseShort();
- hhea.caretOffset = p.parseShort();
- p.relativeOffset += 8;
- hhea.metricDataFormat = p.parseShort();
- hhea.numberOfHMetrics = p.parseUShort();
- return hhea;
- }
- function makeHheaTable(options) {
- return new table.Table('hhea', [
- {name: 'version', type: 'FIXED', value: 0x00010000},
- {name: 'ascender', type: 'FWORD', value: 0},
- {name: 'descender', type: 'FWORD', value: 0},
- {name: 'lineGap', type: 'FWORD', value: 0},
- {name: 'advanceWidthMax', type: 'UFWORD', value: 0},
- {name: 'minLeftSideBearing', type: 'FWORD', value: 0},
- {name: 'minRightSideBearing', type: 'FWORD', value: 0},
- {name: 'xMaxExtent', type: 'FWORD', value: 0},
- {name: 'caretSlopeRise', type: 'SHORT', value: 1},
- {name: 'caretSlopeRun', type: 'SHORT', value: 0},
- {name: 'caretOffset', type: 'SHORT', value: 0},
- {name: 'reserved1', type: 'SHORT', value: 0},
- {name: 'reserved2', type: 'SHORT', value: 0},
- {name: 'reserved3', type: 'SHORT', value: 0},
- {name: 'reserved4', type: 'SHORT', value: 0},
- {name: 'metricDataFormat', type: 'SHORT', value: 0},
- {name: 'numberOfHMetrics', type: 'USHORT', value: 0}
- ], options);
- }
- var hhea = { parse: parseHheaTable, make: makeHheaTable };
- // The `hmtx` table contains the horizontal metrics for all glyphs.
- function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) {
- var advanceWidth;
- var leftSideBearing;
- var p = new parse.Parser(data, start);
- for (var i = 0; i < numGlyphs; i += 1) {
- // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs.
- if (i < numMetrics) {
- advanceWidth = p.parseUShort();
- leftSideBearing = p.parseShort();
- }
- var glyph = glyphs.get(i);
- glyph.advanceWidth = advanceWidth;
- glyph.leftSideBearing = leftSideBearing;
- }
- }
- function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) {
- font._hmtxTableData = {};
- var advanceWidth;
- var leftSideBearing;
- var p = new parse.Parser(data, start);
- for (var i = 0; i < numGlyphs; i += 1) {
- // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs.
- if (i < numMetrics) {
- advanceWidth = p.parseUShort();
- leftSideBearing = p.parseShort();
- }
- font._hmtxTableData[i] = {
- advanceWidth: advanceWidth,
- leftSideBearing: leftSideBearing
- };
- }
- }
- // Parse the `hmtx` table, which contains the horizontal metrics for all glyphs.
- // This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph.
- function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) {
- if (opt.lowMemory)
- { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); }
- else
- { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); }
- }
- function makeHmtxTable(glyphs) {
- var t = new table.Table('hmtx', []);
- for (var i = 0; i < glyphs.length; i += 1) {
- var glyph = glyphs.get(i);
- var advanceWidth = glyph.advanceWidth || 0;
- var leftSideBearing = glyph.leftSideBearing || 0;
- t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth});
- t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing});
- }
- return t;
- }
- var hmtx = { parse: parseHmtxTable, make: makeHmtxTable };
- // The `ltag` table stores IETF BCP-47 language tags. It allows supporting
- function makeLtagTable(tags) {
- var result = new table.Table('ltag', [
- {name: 'version', type: 'ULONG', value: 1},
- {name: 'flags', type: 'ULONG', value: 0},
- {name: 'numTags', type: 'ULONG', value: tags.length}
- ]);
- var stringPool = '';
- var stringPoolOffset = 12 + tags.length * 4;
- for (var i = 0; i < tags.length; ++i) {
- var pos = stringPool.indexOf(tags[i]);
- if (pos < 0) {
- pos = stringPool.length;
- stringPool += tags[i];
- }
- result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos});
- result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length});
- }
- result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool});
- return result;
- }
- function parseLtagTable(data, start) {
- var p = new parse.Parser(data, start);
- var tableVersion = p.parseULong();
- check.argument(tableVersion === 1, 'Unsupported ltag table version.');
- // The 'ltag' specification does not define any flags; skip the field.
- p.skip('uLong', 1);
- var numTags = p.parseULong();
- var tags = [];
- for (var i = 0; i < numTags; i++) {
- var tag = '';
- var offset = start + p.parseUShort();
- var length = p.parseUShort();
- for (var j = offset; j < offset + length; ++j) {
- tag += String.fromCharCode(data.getInt8(j));
- }
- tags.push(tag);
- }
- return tags;
- }
- var ltag = { make: makeLtagTable, parse: parseLtagTable };
- // The `maxp` table establishes the memory requirements for the font.
- // Parse the maximum profile `maxp` table.
- function parseMaxpTable(data, start) {
- var maxp = {};
- var p = new parse.Parser(data, start);
- maxp.version = p.parseVersion();
- maxp.numGlyphs = p.parseUShort();
- if (maxp.version === 1.0) {
- maxp.maxPoints = p.parseUShort();
- maxp.maxContours = p.parseUShort();
- maxp.maxCompositePoints = p.parseUShort();
- maxp.maxCompositeContours = p.parseUShort();
- maxp.maxZones = p.parseUShort();
- maxp.maxTwilightPoints = p.parseUShort();
- maxp.maxStorage = p.parseUShort();
- maxp.maxFunctionDefs = p.parseUShort();
- maxp.maxInstructionDefs = p.parseUShort();
- maxp.maxStackElements = p.parseUShort();
- maxp.maxSizeOfInstructions = p.parseUShort();
- maxp.maxComponentElements = p.parseUShort();
- maxp.maxComponentDepth = p.parseUShort();
- }
- return maxp;
- }
- function makeMaxpTable(numGlyphs) {
- return new table.Table('maxp', [
- {name: 'version', type: 'FIXED', value: 0x00005000},
- {name: 'numGlyphs', type: 'USHORT', value: numGlyphs}
- ]);
- }
- var maxp = { parse: parseMaxpTable, make: makeMaxpTable };
- // The `name` naming table.
- // NameIDs for the name table.
- var nameTableNames = [
- 'copyright', // 0
- 'fontFamily', // 1
- 'fontSubfamily', // 2
- 'uniqueID', // 3
- 'fullName', // 4
- 'version', // 5
- 'postScriptName', // 6
- 'trademark', // 7
- 'manufacturer', // 8
- 'designer', // 9
- 'description', // 10
- 'manufacturerURL', // 11
- 'designerURL', // 12
- 'license', // 13
- 'licenseURL', // 14
- 'reserved', // 15
- 'preferredFamily', // 16
- 'preferredSubfamily', // 17
- 'compatibleFullName', // 18
- 'sampleText', // 19
- 'postScriptFindFontName', // 20
- 'wwsFamily', // 21
- 'wwsSubfamily' // 22
- ];
- var macLanguages = {
- 0: 'en',
- 1: 'fr',
- 2: 'de',
- 3: 'it',
- 4: 'nl',
- 5: 'sv',
- 6: 'es',
- 7: 'da',
- 8: 'pt',
- 9: 'no',
- 10: 'he',
- 11: 'ja',
- 12: 'ar',
- 13: 'fi',
- 14: 'el',
- 15: 'is',
- 16: 'mt',
- 17: 'tr',
- 18: 'hr',
- 19: 'zh-Hant',
- 20: 'ur',
- 21: 'hi',
- 22: 'th',
- 23: 'ko',
- 24: 'lt',
- 25: 'pl',
- 26: 'hu',
- 27: 'es',
- 28: 'lv',
- 29: 'se',
- 30: 'fo',
- 31: 'fa',
- 32: 'ru',
- 33: 'zh',
- 34: 'nl-BE',
- 35: 'ga',
- 36: 'sq',
- 37: 'ro',
- 38: 'cz',
- 39: 'sk',
- 40: 'si',
- 41: 'yi',
- 42: 'sr',
- 43: 'mk',
- 44: 'bg',
- 45: 'uk',
- 46: 'be',
- 47: 'uz',
- 48: 'kk',
- 49: 'az-Cyrl',
- 50: 'az-Arab',
- 51: 'hy',
- 52: 'ka',
- 53: 'mo',
- 54: 'ky',
- 55: 'tg',
- 56: 'tk',
- 57: 'mn-CN',
- 58: 'mn',
- 59: 'ps',
- 60: 'ks',
- 61: 'ku',
- 62: 'sd',
- 63: 'bo',
- 64: 'ne',
- 65: 'sa',
- 66: 'mr',
- 67: 'bn',
- 68: 'as',
- 69: 'gu',
- 70: 'pa',
- 71: 'or',
- 72: 'ml',
- 73: 'kn',
- 74: 'ta',
- 75: 'te',
- 76: 'si',
- 77: 'my',
- 78: 'km',
- 79: 'lo',
- 80: 'vi',
- 81: 'id',
- 82: 'tl',
- 83: 'ms',
- 84: 'ms-Arab',
- 85: 'am',
- 86: 'ti',
- 87: 'om',
- 88: 'so',
- 89: 'sw',
- 90: 'rw',
- 91: 'rn',
- 92: 'ny',
- 93: 'mg',
- 94: 'eo',
- 128: 'cy',
- 129: 'eu',
- 130: 'ca',
- 131: 'la',
- 132: 'qu',
- 133: 'gn',
- 134: 'ay',
- 135: 'tt',
- 136: 'ug',
- 137: 'dz',
- 138: 'jv',
- 139: 'su',
- 140: 'gl',
- 141: 'af',
- 142: 'br',
- 143: 'iu',
- 144: 'gd',
- 145: 'gv',
- 146: 'ga',
- 147: 'to',
- 148: 'el-polyton',
- 149: 'kl',
- 150: 'az',
- 151: 'nn'
- };
- // MacOS language ID → MacOS script ID
- //
- // Note that the script ID is not sufficient to determine what encoding
- // to use in TrueType files. For some languages, MacOS used a modification
- // of a mainstream script. For example, an Icelandic name would be stored
- // with smRoman in the TrueType naming table, but the actual encoding
- // is a special Icelandic version of the normal Macintosh Roman encoding.
- // As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal
- // Syllables but MacOS had run out of available script codes, so this was
- // done as a (pretty radical) "modification" of Ethiopic.
- //
- // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt
- var macLanguageToScript = {
- 0: 0, // langEnglish → smRoman
- 1: 0, // langFrench → smRoman
- 2: 0, // langGerman → smRoman
- 3: 0, // langItalian → smRoman
- 4: 0, // langDutch → smRoman
- 5: 0, // langSwedish → smRoman
- 6: 0, // langSpanish → smRoman
- 7: 0, // langDanish → smRoman
- 8: 0, // langPortuguese → smRoman
- 9: 0, // langNorwegian → smRoman
- 10: 5, // langHebrew → smHebrew
- 11: 1, // langJapanese → smJapanese
- 12: 4, // langArabic → smArabic
- 13: 0, // langFinnish → smRoman
- 14: 6, // langGreek → smGreek
- 15: 0, // langIcelandic → smRoman (modified)
- 16: 0, // langMaltese → smRoman
- 17: 0, // langTurkish → smRoman (modified)
- 18: 0, // langCroatian → smRoman (modified)
- 19: 2, // langTradChinese → smTradChinese
- 20: 4, // langUrdu → smArabic
- 21: 9, // langHindi → smDevanagari
- 22: 21, // langThai → smThai
- 23: 3, // langKorean → smKorean
- 24: 29, // langLithuanian → smCentralEuroRoman
- 25: 29, // langPolish → smCentralEuroRoman
- 26: 29, // langHungarian → smCentralEuroRoman
- 27: 29, // langEstonian → smCentralEuroRoman
- 28: 29, // langLatvian → smCentralEuroRoman
- 29: 0, // langSami → smRoman
- 30: 0, // langFaroese → smRoman (modified)
- 31: 4, // langFarsi → smArabic (modified)
- 32: 7, // langRussian → smCyrillic
- 33: 25, // langSimpChinese → smSimpChinese
- 34: 0, // langFlemish → smRoman
- 35: 0, // langIrishGaelic → smRoman (modified)
- 36: 0, // langAlbanian → smRoman
- 37: 0, // langRomanian → smRoman (modified)
- 38: 29, // langCzech → smCentralEuroRoman
- 39: 29, // langSlovak → smCentralEuroRoman
- 40: 0, // langSlovenian → smRoman (modified)
- 41: 5, // langYiddish → smHebrew
- 42: 7, // langSerbian → smCyrillic
- 43: 7, // langMacedonian → smCyrillic
- 44: 7, // langBulgarian → smCyrillic
- 45: 7, // langUkrainian → smCyrillic (modified)
- 46: 7, // langByelorussian → smCyrillic
- 47: 7, // langUzbek → smCyrillic
- 48: 7, // langKazakh → smCyrillic
- 49: 7, // langAzerbaijani → smCyrillic
- 50: 4, // langAzerbaijanAr → smArabic
- 51: 24, // langArmenian → smArmenian
- 52: 23, // langGeorgian → smGeorgian
- 53: 7, // langMoldavian → smCyrillic
- 54: 7, // langKirghiz → smCyrillic
- 55: 7, // langTajiki → smCyrillic
- 56: 7, // langTurkmen → smCyrillic
- 57: 27, // langMongolian → smMongolian
- 58: 7, // langMongolianCyr → smCyrillic
- 59: 4, // langPashto → smArabic
- 60: 4, // langKurdish → smArabic
- 61: 4, // langKashmiri → smArabic
- 62: 4, // langSindhi → smArabic
- 63: 26, // langTibetan → smTibetan
- 64: 9, // langNepali → smDevanagari
- 65: 9, // langSanskrit → smDevanagari
- 66: 9, // langMarathi → smDevanagari
- 67: 13, // langBengali → smBengali
- 68: 13, // langAssamese → smBengali
- 69: 11, // langGujarati → smGujarati
- 70: 10, // langPunjabi → smGurmukhi
- 71: 12, // langOriya → smOriya
- 72: 17, // langMalayalam → smMalayalam
- 73: 16, // langKannada → smKannada
- 74: 14, // langTamil → smTamil
- 75: 15, // langTelugu → smTelugu
- 76: 18, // langSinhalese → smSinhalese
- 77: 19, // langBurmese → smBurmese
- 78: 20, // langKhmer → smKhmer
- 79: 22, // langLao → smLao
- 80: 30, // langVietnamese → smVietnamese
- 81: 0, // langIndonesian → smRoman
- 82: 0, // langTagalog → smRoman
- 83: 0, // langMalayRoman → smRoman
- 84: 4, // langMalayArabic → smArabic
- 85: 28, // langAmharic → smEthiopic
- 86: 28, // langTigrinya → smEthiopic
- 87: 28, // langOromo → smEthiopic
- 88: 0, // langSomali → smRoman
- 89: 0, // langSwahili → smRoman
- 90: 0, // langKinyarwanda → smRoman
- 91: 0, // langRundi → smRoman
- 92: 0, // langNyanja → smRoman
- 93: 0, // langMalagasy → smRoman
- 94: 0, // langEsperanto → smRoman
- 128: 0, // langWelsh → smRoman (modified)
- 129: 0, // langBasque → smRoman
- 130: 0, // langCatalan → smRoman
- 131: 0, // langLatin → smRoman
- 132: 0, // langQuechua → smRoman
- 133: 0, // langGuarani → smRoman
- 134: 0, // langAymara → smRoman
- 135: 7, // langTatar → smCyrillic
- 136: 4, // langUighur → smArabic
- 137: 26, // langDzongkha → smTibetan
- 138: 0, // langJavaneseRom → smRoman
- 139: 0, // langSundaneseRom → smRoman
- 140: 0, // langGalician → smRoman
- 141: 0, // langAfrikaans → smRoman
- 142: 0, // langBreton → smRoman (modified)
- 143: 28, // langInuktitut → smEthiopic (modified)
- 144: 0, // langScottishGaelic → smRoman (modified)
- 145: 0, // langManxGaelic → smRoman (modified)
- 146: 0, // langIrishGaelicScript → smRoman (modified)
- 147: 0, // langTongan → smRoman
- 148: 6, // langGreekAncient → smRoman
- 149: 0, // langGreenlandic → smRoman
- 150: 0, // langAzerbaijanRoman → smRoman
- 151: 0 // langNynorsk → smRoman
- };
- // While Microsoft indicates a region/country for all its language
- // IDs, we omit the region code if it's equal to the "most likely
- // region subtag" according to Unicode CLDR. For scripts, we omit
- // the subtag if it is equal to the Suppress-Script entry in the
- // IANA language subtag registry for IETF BCP 47.
- //
- // For example, Microsoft states that its language code 0x041A is
- // Croatian in Croatia. We transform this to the BCP 47 language code 'hr'
- // and not 'hr-HR' because Croatia is the default country for Croatian,
- // according to Unicode CLDR. As another example, Microsoft states
- // that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform
- // this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script
- // for the Croatian language, according to IANA.
- //
- // http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html
- // http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
- var windowsLanguages = {
- 0x0436: 'af',
- 0x041C: 'sq',
- 0x0484: 'gsw',
- 0x045E: 'am',
- 0x1401: 'ar-DZ',
- 0x3C01: 'ar-BH',
- 0x0C01: 'ar',
- 0x0801: 'ar-IQ',
- 0x2C01: 'ar-JO',
- 0x3401: 'ar-KW',
- 0x3001: 'ar-LB',
- 0x1001: 'ar-LY',
- 0x1801: 'ary',
- 0x2001: 'ar-OM',
- 0x4001: 'ar-QA',
- 0x0401: 'ar-SA',
- 0x2801: 'ar-SY',
- 0x1C01: 'aeb',
- 0x3801: 'ar-AE',
- 0x2401: 'ar-YE',
- 0x042B: 'hy',
- 0x044D: 'as',
- 0x082C: 'az-Cyrl',
- 0x042C: 'az',
- 0x046D: 'ba',
- 0x042D: 'eu',
- 0x0423: 'be',
- 0x0845: 'bn',
- 0x0445: 'bn-IN',
- 0x201A: 'bs-Cyrl',
- 0x141A: 'bs',
- 0x047E: 'br',
- 0x0402: 'bg',
- 0x0403: 'ca',
- 0x0C04: 'zh-HK',
- 0x1404: 'zh-MO',
- 0x0804: 'zh',
- 0x1004: 'zh-SG',
- 0x0404: 'zh-TW',
- 0x0483: 'co',
- 0x041A: 'hr',
- 0x101A: 'hr-BA',
- 0x0405: 'cs',
- 0x0406: 'da',
- 0x048C: 'prs',
- 0x0465: 'dv',
- 0x0813: 'nl-BE',
- 0x0413: 'nl',
- 0x0C09: 'en-AU',
- 0x2809: 'en-BZ',
- 0x1009: 'en-CA',
- 0x2409: 'en-029',
- 0x4009: 'en-IN',
- 0x1809: 'en-IE',
- 0x2009: 'en-JM',
- 0x4409: 'en-MY',
- 0x1409: 'en-NZ',
- 0x3409: 'en-PH',
- 0x4809: 'en-SG',
- 0x1C09: 'en-ZA',
- 0x2C09: 'en-TT',
- 0x0809: 'en-GB',
- 0x0409: 'en',
- 0x3009: 'en-ZW',
- 0x0425: 'et',
- 0x0438: 'fo',
- 0x0464: 'fil',
- 0x040B: 'fi',
- 0x080C: 'fr-BE',
- 0x0C0C: 'fr-CA',
- 0x040C: 'fr',
- 0x140C: 'fr-LU',
- 0x180C: 'fr-MC',
- 0x100C: 'fr-CH',
- 0x0462: 'fy',
- 0x0456: 'gl',
- 0x0437: 'ka',
- 0x0C07: 'de-AT',
- 0x0407: 'de',
- 0x1407: 'de-LI',
- 0x1007: 'de-LU',
- 0x0807: 'de-CH',
- 0x0408: 'el',
- 0x046F: 'kl',
- 0x0447: 'gu',
- 0x0468: 'ha',
- 0x040D: 'he',
- 0x0439: 'hi',
- 0x040E: 'hu',
- 0x040F: 'is',
- 0x0470: 'ig',
- 0x0421: 'id',
- 0x045D: 'iu',
- 0x085D: 'iu-Latn',
- 0x083C: 'ga',
- 0x0434: 'xh',
- 0x0435: 'zu',
- 0x0410: 'it',
- 0x0810: 'it-CH',
- 0x0411: 'ja',
- 0x044B: 'kn',
- 0x043F: 'kk',
- 0x0453: 'km',
- 0x0486: 'quc',
- 0x0487: 'rw',
- 0x0441: 'sw',
- 0x0457: 'kok',
- 0x0412: 'ko',
- 0x0440: 'ky',
- 0x0454: 'lo',
- 0x0426: 'lv',
- 0x0427: 'lt',
- 0x082E: 'dsb',
- 0x046E: 'lb',
- 0x042F: 'mk',
- 0x083E: 'ms-BN',
- 0x043E: 'ms',
- 0x044C: 'ml',
- 0x043A: 'mt',
- 0x0481: 'mi',
- 0x047A: 'arn',
- 0x044E: 'mr',
- 0x047C: 'moh',
- 0x0450: 'mn',
- 0x0850: 'mn-CN',
- 0x0461: 'ne',
- 0x0414: 'nb',
- 0x0814: 'nn',
- 0x0482: 'oc',
- 0x0448: 'or',
- 0x0463: 'ps',
- 0x0415: 'pl',
- 0x0416: 'pt',
- 0x0816: 'pt-PT',
- 0x0446: 'pa',
- 0x046B: 'qu-BO',
- 0x086B: 'qu-EC',
- 0x0C6B: 'qu',
- 0x0418: 'ro',
- 0x0417: 'rm',
- 0x0419: 'ru',
- 0x243B: 'smn',
- 0x103B: 'smj-NO',
- 0x143B: 'smj',
- 0x0C3B: 'se-FI',
- 0x043B: 'se',
- 0x083B: 'se-SE',
- 0x203B: 'sms',
- 0x183B: 'sma-NO',
- 0x1C3B: 'sms',
- 0x044F: 'sa',
- 0x1C1A: 'sr-Cyrl-BA',
- 0x0C1A: 'sr',
- 0x181A: 'sr-Latn-BA',
- 0x081A: 'sr-Latn',
- 0x046C: 'nso',
- 0x0432: 'tn',
- 0x045B: 'si',
- 0x041B: 'sk',
- 0x0424: 'sl',
- 0x2C0A: 'es-AR',
- 0x400A: 'es-BO',
- 0x340A: 'es-CL',
- 0x240A: 'es-CO',
- 0x140A: 'es-CR',
- 0x1C0A: 'es-DO',
- 0x300A: 'es-EC',
- 0x440A: 'es-SV',
- 0x100A: 'es-GT',
- 0x480A: 'es-HN',
- 0x080A: 'es-MX',
- 0x4C0A: 'es-NI',
- 0x180A: 'es-PA',
- 0x3C0A: 'es-PY',
- 0x280A: 'es-PE',
- 0x500A: 'es-PR',
- // Microsoft has defined two different language codes for
- // “Spanish with modern sorting” and “Spanish with traditional
- // sorting”. This makes sense for collation APIs, and it would be
- // possible to express this in BCP 47 language tags via Unicode
- // extensions (eg., es-u-co-trad is Spanish with traditional
- // sorting). However, for storing names in fonts, the distinction
- // does not make sense, so we give “es” in both cases.
- 0x0C0A: 'es',
- 0x040A: 'es',
- 0x540A: 'es-US',
- 0x380A: 'es-UY',
- 0x200A: 'es-VE',
- 0x081D: 'sv-FI',
- 0x041D: 'sv',
- 0x045A: 'syr',
- 0x0428: 'tg',
- 0x085F: 'tzm',
- 0x0449: 'ta',
- 0x0444: 'tt',
- 0x044A: 'te',
- 0x041E: 'th',
- 0x0451: 'bo',
- 0x041F: 'tr',
- 0x0442: 'tk',
- 0x0480: 'ug',
- 0x0422: 'uk',
- 0x042E: 'hsb',
- 0x0420: 'ur',
- 0x0843: 'uz-Cyrl',
- 0x0443: 'uz',
- 0x042A: 'vi',
- 0x0452: 'cy',
- 0x0488: 'wo',
- 0x0485: 'sah',
- 0x0478: 'ii',
- 0x046A: 'yo'
- };
- // Returns a IETF BCP 47 language code, for example 'zh-Hant'
- // for 'Chinese in the traditional script'.
- function getLanguageCode(platformID, languageID, ltag) {
- switch (platformID) {
- case 0: // Unicode
- if (languageID === 0xFFFF) {
- return 'und';
- } else if (ltag) {
- return ltag[languageID];
- }
- break;
- case 1: // Macintosh
- return macLanguages[languageID];
- case 3: // Windows
- return windowsLanguages[languageID];
- }
- return undefined;
- }
- var utf16 = 'utf-16';
- // MacOS script ID → encoding. This table stores the default case,
- // which can be overridden by macLanguageEncodings.
- var macScriptEncodings = {
- 0: 'macintosh', // smRoman
- 1: 'x-mac-japanese', // smJapanese
- 2: 'x-mac-chinesetrad', // smTradChinese
- 3: 'x-mac-korean', // smKorean
- 6: 'x-mac-greek', // smGreek
- 7: 'x-mac-cyrillic', // smCyrillic
- 9: 'x-mac-devanagai', // smDevanagari
- 10: 'x-mac-gurmukhi', // smGurmukhi
- 11: 'x-mac-gujarati', // smGujarati
- 12: 'x-mac-oriya', // smOriya
- 13: 'x-mac-bengali', // smBengali
- 14: 'x-mac-tamil', // smTamil
- 15: 'x-mac-telugu', // smTelugu
- 16: 'x-mac-kannada', // smKannada
- 17: 'x-mac-malayalam', // smMalayalam
- 18: 'x-mac-sinhalese', // smSinhalese
- 19: 'x-mac-burmese', // smBurmese
- 20: 'x-mac-khmer', // smKhmer
- 21: 'x-mac-thai', // smThai
- 22: 'x-mac-lao', // smLao
- 23: 'x-mac-georgian', // smGeorgian
- 24: 'x-mac-armenian', // smArmenian
- 25: 'x-mac-chinesesimp', // smSimpChinese
- 26: 'x-mac-tibetan', // smTibetan
- 27: 'x-mac-mongolian', // smMongolian
- 28: 'x-mac-ethiopic', // smEthiopic
- 29: 'x-mac-ce', // smCentralEuroRoman
- 30: 'x-mac-vietnamese', // smVietnamese
- 31: 'x-mac-extarabic' // smExtArabic
- };
- // MacOS language ID → encoding. This table stores the exceptional
- // cases, which override macScriptEncodings. For writing MacOS naming
- // tables, we need to emit a MacOS script ID. Therefore, we cannot
- // merge macScriptEncodings into macLanguageEncodings.
- //
- // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt
- var macLanguageEncodings = {
- 15: 'x-mac-icelandic', // langIcelandic
- 17: 'x-mac-turkish', // langTurkish
- 18: 'x-mac-croatian', // langCroatian
- 24: 'x-mac-ce', // langLithuanian
- 25: 'x-mac-ce', // langPolish
- 26: 'x-mac-ce', // langHungarian
- 27: 'x-mac-ce', // langEstonian
- 28: 'x-mac-ce', // langLatvian
- 30: 'x-mac-icelandic', // langFaroese
- 37: 'x-mac-romanian', // langRomanian
- 38: 'x-mac-ce', // langCzech
- 39: 'x-mac-ce', // langSlovak
- 40: 'x-mac-ce', // langSlovenian
- 143: 'x-mac-inuit', // langInuktitut
- 146: 'x-mac-gaelic' // langIrishGaelicScript
- };
- function getEncoding(platformID, encodingID, languageID) {
- switch (platformID) {
- case 0: // Unicode
- return utf16;
- case 1: // Apple Macintosh
- return macLanguageEncodings[languageID] || macScriptEncodings[encodingID];
- case 3: // Microsoft Windows
- if (encodingID === 1 || encodingID === 10) {
- return utf16;
- }
- break;
- }
- return undefined;
- }
- // Parse the naming `name` table.
- // FIXME: Format 1 additional fields are not supported yet.
- // ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904'].
- function parseNameTable(data, start, ltag) {
- var name = {};
- var p = new parse.Parser(data, start);
- var format = p.parseUShort();
- var count = p.parseUShort();
- var stringOffset = p.offset + p.parseUShort();
- for (var i = 0; i < count; i++) {
- var platformID = p.parseUShort();
- var encodingID = p.parseUShort();
- var languageID = p.parseUShort();
- var nameID = p.parseUShort();
- var property = nameTableNames[nameID] || nameID;
- var byteLength = p.parseUShort();
- var offset = p.parseUShort();
- var language = getLanguageCode(platformID, languageID, ltag);
- var encoding = getEncoding(platformID, encodingID, languageID);
- if (encoding !== undefined && language !== undefined) {
- var text = (void 0);
- if (encoding === utf16) {
- text = decode.UTF16(data, stringOffset + offset, byteLength);
- } else {
- text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding);
- }
- if (text) {
- var translations = name[property];
- if (translations === undefined) {
- translations = name[property] = {};
- }
- translations[language] = text;
- }
- }
- }
- var langTagCount = 0;
- if (format === 1) {
- // FIXME: Also handle Microsoft's 'name' table 1.
- langTagCount = p.parseUShort();
- }
- return name;
- }
- // {23: 'foo'} → {'foo': 23}
- // ['bar', 'baz'] → {'bar': 0, 'baz': 1}
- function reverseDict(dict) {
- var result = {};
- for (var key in dict) {
- result[dict[key]] = parseInt(key);
- }
- return result;
- }
- function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) {
- return new table.Record('NameRecord', [
- {name: 'platformID', type: 'USHORT', value: platformID},
- {name: 'encodingID', type: 'USHORT', value: encodingID},
- {name: 'languageID', type: 'USHORT', value: languageID},
- {name: 'nameID', type: 'USHORT', value: nameID},
- {name: 'length', type: 'USHORT', value: length},
- {name: 'offset', type: 'USHORT', value: offset}
- ]);
- }
- // Finds the position of needle in haystack, or -1 if not there.
- // Like String.indexOf(), but for arrays.
- function findSubArray(needle, haystack) {
- var needleLength = needle.length;
- var limit = haystack.length - needleLength + 1;
- loop:
- for (var pos = 0; pos < limit; pos++) {
- for (; pos < limit; pos++) {
- for (var k = 0; k < needleLength; k++) {
- if (haystack[pos + k] !== needle[k]) {
- continue loop;
- }
- }
- return pos;
- }
- }
- return -1;
- }
- function addStringToPool(s, pool) {
- var offset = findSubArray(s, pool);
- if (offset < 0) {
- offset = pool.length;
- var i = 0;
- var len = s.length;
- for (; i < len; ++i) {
- pool.push(s[i]);
- }
- }
- return offset;
- }
- function makeNameTable(names, ltag) {
- var nameID;
- var nameIDs = [];
- var namesWithNumericKeys = {};
- var nameTableIds = reverseDict(nameTableNames);
- for (var key in names) {
- var id = nameTableIds[key];
- if (id === undefined) {
- id = key;
- }
- nameID = parseInt(id);
- if (isNaN(nameID)) {
- throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.');
- }
- namesWithNumericKeys[nameID] = names[key];
- nameIDs.push(nameID);
- }
- var macLanguageIds = reverseDict(macLanguages);
- var windowsLanguageIds = reverseDict(windowsLanguages);
- var nameRecords = [];
- var stringPool = [];
- for (var i = 0; i < nameIDs.length; i++) {
- nameID = nameIDs[i];
- var translations = namesWithNumericKeys[nameID];
- for (var lang in translations) {
- var text = translations[lang];
- // For MacOS, we try to emit the name in the form that was introduced
- // in the initial version of the TrueType spec (in the late 1980s).
- // However, this can fail for various reasons: the requested BCP 47
- // language code might not have an old-style Mac equivalent;
- // we might not have a codec for the needed character encoding;
- // or the name might contain characters that cannot be expressed
- // in the old-style Macintosh encoding. In case of failure, we emit
- // the name in a more modern fashion (Unicode encoding with BCP 47
- // language tags) that is recognized by MacOS 10.5, released in 2009.
- // If fonts were only read by operating systems, we could simply
- // emit all names in the modern form; this would be much easier.
- // However, there are many applications and libraries that read
- // 'name' tables directly, and these will usually only recognize
- // the ancient form (silently skipping the unrecognized names).
- var macPlatform = 1; // Macintosh
- var macLanguage = macLanguageIds[lang];
- var macScript = macLanguageToScript[macLanguage];
- var macEncoding = getEncoding(macPlatform, macScript, macLanguage);
- var macName = encode.MACSTRING(text, macEncoding);
- if (macName === undefined) {
- macPlatform = 0; // Unicode
- macLanguage = ltag.indexOf(lang);
- if (macLanguage < 0) {
- macLanguage = ltag.length;
- ltag.push(lang);
- }
- macScript = 4; // Unicode 2.0 and later
- macName = encode.UTF16(text);
- }
- var macNameOffset = addStringToPool(macName, stringPool);
- nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage,
- nameID, macName.length, macNameOffset));
- var winLanguage = windowsLanguageIds[lang];
- if (winLanguage !== undefined) {
- var winName = encode.UTF16(text);
- var winNameOffset = addStringToPool(winName, stringPool);
- nameRecords.push(makeNameRecord(3, 1, winLanguage,
- nameID, winName.length, winNameOffset));
- }
- }
- }
- nameRecords.sort(function(a, b) {
- return ((a.platformID - b.platformID) ||
- (a.encodingID - b.encodingID) ||
- (a.languageID - b.languageID) ||
- (a.nameID - b.nameID));
- });
- var t = new table.Table('name', [
- {name: 'format', type: 'USHORT', value: 0},
- {name: 'count', type: 'USHORT', value: nameRecords.length},
- {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12}
- ]);
- for (var r = 0; r < nameRecords.length; r++) {
- t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]});
- }
- t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool});
- return t;
- }
- var _name = { parse: parseNameTable, make: makeNameTable };
- // The `OS/2` table contains metrics required in OpenType fonts.
- var unicodeRanges = [
- {begin: 0x0000, end: 0x007F}, // Basic Latin
- {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement
- {begin: 0x0100, end: 0x017F}, // Latin Extended-A
- {begin: 0x0180, end: 0x024F}, // Latin Extended-B
- {begin: 0x0250, end: 0x02AF}, // IPA Extensions
- {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters
- {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks
- {begin: 0x0370, end: 0x03FF}, // Greek and Coptic
- {begin: 0x2C80, end: 0x2CFF}, // Coptic
- {begin: 0x0400, end: 0x04FF}, // Cyrillic
- {begin: 0x0530, end: 0x058F}, // Armenian
- {begin: 0x0590, end: 0x05FF}, // Hebrew
- {begin: 0xA500, end: 0xA63F}, // Vai
- {begin: 0x0600, end: 0x06FF}, // Arabic
- {begin: 0x07C0, end: 0x07FF}, // NKo
- {begin: 0x0900, end: 0x097F}, // Devanagari
- {begin: 0x0980, end: 0x09FF}, // Bengali
- {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi
- {begin: 0x0A80, end: 0x0AFF}, // Gujarati
- {begin: 0x0B00, end: 0x0B7F}, // Oriya
- {begin: 0x0B80, end: 0x0BFF}, // Tamil
- {begin: 0x0C00, end: 0x0C7F}, // Telugu
- {begin: 0x0C80, end: 0x0CFF}, // Kannada
- {begin: 0x0D00, end: 0x0D7F}, // Malayalam
- {begin: 0x0E00, end: 0x0E7F}, // Thai
- {begin: 0x0E80, end: 0x0EFF}, // Lao
- {begin: 0x10A0, end: 0x10FF}, // Georgian
- {begin: 0x1B00, end: 0x1B7F}, // Balinese
- {begin: 0x1100, end: 0x11FF}, // Hangul Jamo
- {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional
- {begin: 0x1F00, end: 0x1FFF}, // Greek Extended
- {begin: 0x2000, end: 0x206F}, // General Punctuation
- {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts
- {begin: 0x20A0, end: 0x20CF}, // Currency Symbol
- {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols
- {begin: 0x2100, end: 0x214F}, // Letterlike Symbols
- {begin: 0x2150, end: 0x218F}, // Number Forms
- {begin: 0x2190, end: 0x21FF}, // Arrows
- {begin: 0x2200, end: 0x22FF}, // Mathematical Operators
- {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical
- {begin: 0x2400, end: 0x243F}, // Control Pictures
- {begin: 0x2440, end: 0x245F}, // Optical Character Recognition
- {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics
- {begin: 0x2500, end: 0x257F}, // Box Drawing
- {begin: 0x2580, end: 0x259F}, // Block Elements
- {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes
- {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols
- {begin: 0x2700, end: 0x27BF}, // Dingbats
- {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation
- {begin: 0x3040, end: 0x309F}, // Hiragana
- {begin: 0x30A0, end: 0x30FF}, // Katakana
- {begin: 0x3100, end: 0x312F}, // Bopomofo
- {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo
- {begin: 0xA840, end: 0xA87F}, // Phags-pa
- {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months
- {begin: 0x3300, end: 0x33FF}, // CJK Compatibility
- {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables
- {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 *
- {begin: 0x10900, end: 0x1091F}, // Phoenicia
- {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs
- {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0)
- {begin: 0x31C0, end: 0x31EF}, // CJK Strokes
- {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms
- {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A
- {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks
- {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms
- {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants
- {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B
- {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms
- {begin: 0xFFF0, end: 0xFFFF}, // Specials
- {begin: 0x0F00, end: 0x0FFF}, // Tibetan
- {begin: 0x0700, end: 0x074F}, // Syriac
- {begin: 0x0780, end: 0x07BF}, // Thaana
- {begin: 0x0D80, end: 0x0DFF}, // Sinhala
- {begin: 0x1000, end: 0x109F}, // Myanmar
- {begin: 0x1200, end: 0x137F}, // Ethiopic
- {begin: 0x13A0, end: 0x13FF}, // Cherokee
- {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics
- {begin: 0x1680, end: 0x169F}, // Ogham
- {begin: 0x16A0, end: 0x16FF}, // Runic
- {begin: 0x1780, end: 0x17FF}, // Khmer
- {begin: 0x1800, end: 0x18AF}, // Mongolian
- {begin: 0x2800, end: 0x28FF}, // Braille Patterns
- {begin: 0xA000, end: 0xA48F}, // Yi Syllables
- {begin: 0x1700, end: 0x171F}, // Tagalog
- {begin: 0x10300, end: 0x1032F}, // Old Italic
- {begin: 0x10330, end: 0x1034F}, // Gothic
- {begin: 0x10400, end: 0x1044F}, // Deseret
- {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols
- {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols
- {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15)
- {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors
- {begin: 0xE0000, end: 0xE007F}, // Tags
- {begin: 0x1900, end: 0x194F}, // Limbu
- {begin: 0x1950, end: 0x197F}, // Tai Le
- {begin: 0x1980, end: 0x19DF}, // New Tai Lue
- {begin: 0x1A00, end: 0x1A1F}, // Buginese
- {begin: 0x2C00, end: 0x2C5F}, // Glagolitic
- {begin: 0x2D30, end: 0x2D7F}, // Tifinagh
- {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols
- {begin: 0xA800, end: 0xA82F}, // Syloti Nagri
- {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary
- {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers
- {begin: 0x10380, end: 0x1039F}, // Ugaritic
- {begin: 0x103A0, end: 0x103DF}, // Old Persian
- {begin: 0x10450, end: 0x1047F}, // Shavian
- {begin: 0x10480, end: 0x104AF}, // Osmanya
- {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary
- {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi
- {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols
- {begin: 0x12000, end: 0x123FF}, // Cuneiform
- {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals
- {begin: 0x1B80, end: 0x1BBF}, // Sundanese
- {begin: 0x1C00, end: 0x1C4F}, // Lepcha
- {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki
- {begin: 0xA880, end: 0xA8DF}, // Saurashtra
- {begin: 0xA900, end: 0xA92F}, // Kayah Li
- {begin: 0xA930, end: 0xA95F}, // Rejang
- {begin: 0xAA00, end: 0xAA5F}, // Cham
- {begin: 0x10190, end: 0x101CF}, // Ancient Symbols
- {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc
- {begin: 0x102A0, end: 0x102DF}, // Carian
- {begin: 0x1F030, end: 0x1F09F} // Domino Tiles
- ];
- function getUnicodeRange(unicode) {
- for (var i = 0; i < unicodeRanges.length; i += 1) {
- var range = unicodeRanges[i];
- if (unicode >= range.begin && unicode < range.end) {
- return i;
- }
- }
- return -1;
- }
- // Parse the OS/2 and Windows metrics `OS/2` table
- function parseOS2Table(data, start) {
- var os2 = {};
- var p = new parse.Parser(data, start);
- os2.version = p.parseUShort();
- os2.xAvgCharWidth = p.parseShort();
- os2.usWeightClass = p.parseUShort();
- os2.usWidthClass = p.parseUShort();
- os2.fsType = p.parseUShort();
- os2.ySubscriptXSize = p.parseShort();
- os2.ySubscriptYSize = p.parseShort();
- os2.ySubscriptXOffset = p.parseShort();
- os2.ySubscriptYOffset = p.parseShort();
- os2.ySuperscriptXSize = p.parseShort();
- os2.ySuperscriptYSize = p.parseShort();
- os2.ySuperscriptXOffset = p.parseShort();
- os2.ySuperscriptYOffset = p.parseShort();
- os2.yStrikeoutSize = p.parseShort();
- os2.yStrikeoutPosition = p.parseShort();
- os2.sFamilyClass = p.parseShort();
- os2.panose = [];
- for (var i = 0; i < 10; i++) {
- os2.panose[i] = p.parseByte();
- }
- os2.ulUnicodeRange1 = p.parseULong();
- os2.ulUnicodeRange2 = p.parseULong();
- os2.ulUnicodeRange3 = p.parseULong();
- os2.ulUnicodeRange4 = p.parseULong();
- os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte());
- os2.fsSelection = p.parseUShort();
- os2.usFirstCharIndex = p.parseUShort();
- os2.usLastCharIndex = p.parseUShort();
- os2.sTypoAscender = p.parseShort();
- os2.sTypoDescender = p.parseShort();
- os2.sTypoLineGap = p.parseShort();
- os2.usWinAscent = p.parseUShort();
- os2.usWinDescent = p.parseUShort();
- if (os2.version >= 1) {
- os2.ulCodePageRange1 = p.parseULong();
- os2.ulCodePageRange2 = p.parseULong();
- }
- if (os2.version >= 2) {
- os2.sxHeight = p.parseShort();
- os2.sCapHeight = p.parseShort();
- os2.usDefaultChar = p.parseUShort();
- os2.usBreakChar = p.parseUShort();
- os2.usMaxContent = p.parseUShort();
- }
- return os2;
- }
- function makeOS2Table(options) {
- return new table.Table('OS/2', [
- {name: 'version', type: 'USHORT', value: 0x0003},
- {name: 'xAvgCharWidth', type: 'SHORT', value: 0},
- {name: 'usWeightClass', type: 'USHORT', value: 0},
- {name: 'usWidthClass', type: 'USHORT', value: 0},
- {name: 'fsType', type: 'USHORT', value: 0},
- {name: 'ySubscriptXSize', type: 'SHORT', value: 650},
- {name: 'ySubscriptYSize', type: 'SHORT', value: 699},
- {name: 'ySubscriptXOffset', type: 'SHORT', value: 0},
- {name: 'ySubscriptYOffset', type: 'SHORT', value: 140},
- {name: 'ySuperscriptXSize', type: 'SHORT', value: 650},
- {name: 'ySuperscriptYSize', type: 'SHORT', value: 699},
- {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0},
- {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479},
- {name: 'yStrikeoutSize', type: 'SHORT', value: 49},
- {name: 'yStrikeoutPosition', type: 'SHORT', value: 258},
- {name: 'sFamilyClass', type: 'SHORT', value: 0},
- {name: 'bFamilyType', type: 'BYTE', value: 0},
- {name: 'bSerifStyle', type: 'BYTE', value: 0},
- {name: 'bWeight', type: 'BYTE', value: 0},
- {name: 'bProportion', type: 'BYTE', value: 0},
- {name: 'bContrast', type: 'BYTE', value: 0},
- {name: 'bStrokeVariation', type: 'BYTE', value: 0},
- {name: 'bArmStyle', type: 'BYTE', value: 0},
- {name: 'bLetterform', type: 'BYTE', value: 0},
- {name: 'bMidline', type: 'BYTE', value: 0},
- {name: 'bXHeight', type: 'BYTE', value: 0},
- {name: 'ulUnicodeRange1', type: 'ULONG', value: 0},
- {name: 'ulUnicodeRange2', type: 'ULONG', value: 0},
- {name: 'ulUnicodeRange3', type: 'ULONG', value: 0},
- {name: 'ulUnicodeRange4', type: 'ULONG', value: 0},
- {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'},
- {name: 'fsSelection', type: 'USHORT', value: 0},
- {name: 'usFirstCharIndex', type: 'USHORT', value: 0},
- {name: 'usLastCharIndex', type: 'USHORT', value: 0},
- {name: 'sTypoAscender', type: 'SHORT', value: 0},
- {name: 'sTypoDescender', type: 'SHORT', value: 0},
- {name: 'sTypoLineGap', type: 'SHORT', value: 0},
- {name: 'usWinAscent', type: 'USHORT', value: 0},
- {name: 'usWinDescent', type: 'USHORT', value: 0},
- {name: 'ulCodePageRange1', type: 'ULONG', value: 0},
- {name: 'ulCodePageRange2', type: 'ULONG', value: 0},
- {name: 'sxHeight', type: 'SHORT', value: 0},
- {name: 'sCapHeight', type: 'SHORT', value: 0},
- {name: 'usDefaultChar', type: 'USHORT', value: 0},
- {name: 'usBreakChar', type: 'USHORT', value: 0},
- {name: 'usMaxContext', type: 'USHORT', value: 0}
- ], options);
- }
- var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange };
- // The `post` table stores additional PostScript information, such as glyph names.
- // Parse the PostScript `post` table
- function parsePostTable(data, start) {
- var post = {};
- var p = new parse.Parser(data, start);
- post.version = p.parseVersion();
- post.italicAngle = p.parseFixed();
- post.underlinePosition = p.parseShort();
- post.underlineThickness = p.parseShort();
- post.isFixedPitch = p.parseULong();
- post.minMemType42 = p.parseULong();
- post.maxMemType42 = p.parseULong();
- post.minMemType1 = p.parseULong();
- post.maxMemType1 = p.parseULong();
- switch (post.version) {
- case 1:
- post.names = standardNames.slice();
- break;
- case 2:
- post.numberOfGlyphs = p.parseUShort();
- post.glyphNameIndex = new Array(post.numberOfGlyphs);
- for (var i = 0; i < post.numberOfGlyphs; i++) {
- post.glyphNameIndex[i] = p.parseUShort();
- }
- post.names = [];
- for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) {
- if (post.glyphNameIndex[i$1] >= standardNames.length) {
- var nameLength = p.parseChar();
- post.names.push(p.parseString(nameLength));
- }
- }
- break;
- case 2.5:
- post.numberOfGlyphs = p.parseUShort();
- post.offset = new Array(post.numberOfGlyphs);
- for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) {
- post.offset[i$2] = p.parseChar();
- }
- break;
- }
- return post;
- }
- function makePostTable() {
- return new table.Table('post', [
- {name: 'version', type: 'FIXED', value: 0x00030000},
- {name: 'italicAngle', type: 'FIXED', value: 0},
- {name: 'underlinePosition', type: 'FWORD', value: 0},
- {name: 'underlineThickness', type: 'FWORD', value: 0},
- {name: 'isFixedPitch', type: 'ULONG', value: 0},
- {name: 'minMemType42', type: 'ULONG', value: 0},
- {name: 'maxMemType42', type: 'ULONG', value: 0},
- {name: 'minMemType1', type: 'ULONG', value: 0},
- {name: 'maxMemType1', type: 'ULONG', value: 0}
- ]);
- }
- var post = { parse: parsePostTable, make: makePostTable };
- // The `GSUB` table contains ligatures, among other things.
- var subtableParsers = new Array(9); // subtableParsers[0] is unused
- // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS
- subtableParsers[1] = function parseLookup1() {
- var start = this.offset + this.relativeOffset;
- var substFormat = this.parseUShort();
- if (substFormat === 1) {
- return {
- substFormat: 1,
- coverage: this.parsePointer(Parser.coverage),
- deltaGlyphId: this.parseUShort()
- };
- } else if (substFormat === 2) {
- return {
- substFormat: 2,
- coverage: this.parsePointer(Parser.coverage),
- substitute: this.parseOffset16List()
- };
- }
- check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.');
- };
- // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS
- subtableParsers[2] = function parseLookup2() {
- var substFormat = this.parseUShort();
- check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1');
- return {
- substFormat: substFormat,
- coverage: this.parsePointer(Parser.coverage),
- sequences: this.parseListOfLists()
- };
- };
- // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS
- subtableParsers[3] = function parseLookup3() {
- var substFormat = this.parseUShort();
- check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1');
- return {
- substFormat: substFormat,
- coverage: this.parsePointer(Parser.coverage),
- alternateSets: this.parseListOfLists()
- };
- };
- // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS
- subtableParsers[4] = function parseLookup4() {
- var substFormat = this.parseUShort();
- check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1');
- return {
- substFormat: substFormat,
- coverage: this.parsePointer(Parser.coverage),
- ligatureSets: this.parseListOfLists(function() {
- return {
- ligGlyph: this.parseUShort(),
- components: this.parseUShortList(this.parseUShort() - 1)
- };
- })
- };
- };
- var lookupRecordDesc = {
- sequenceIndex: Parser.uShort,
- lookupListIndex: Parser.uShort
- };
- // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF
- subtableParsers[5] = function parseLookup5() {
- var start = this.offset + this.relativeOffset;
- var substFormat = this.parseUShort();
- if (substFormat === 1) {
- return {
- substFormat: substFormat,
- coverage: this.parsePointer(Parser.coverage),
- ruleSets: this.parseListOfLists(function() {
- var glyphCount = this.parseUShort();
- var substCount = this.parseUShort();
- return {
- input: this.parseUShortList(glyphCount - 1),
- lookupRecords: this.parseRecordList(substCount, lookupRecordDesc)
- };
- })
- };
- } else if (substFormat === 2) {
- return {
- substFormat: substFormat,
- coverage: this.parsePointer(Parser.coverage),
- classDef: this.parsePointer(Parser.classDef),
- classSets: this.parseListOfLists(function() {
- var glyphCount = this.parseUShort();
- var substCount = this.parseUShort();
- return {
- classes: this.parseUShortList(glyphCount - 1),
- lookupRecords: this.parseRecordList(substCount, lookupRecordDesc)
- };
- })
- };
- } else if (substFormat === 3) {
- var glyphCount = this.parseUShort();
- var substCount = this.parseUShort();
- return {
- substFormat: substFormat,
- coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)),
- lookupRecords: this.parseRecordList(substCount, lookupRecordDesc)
- };
- }
- check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.');
- };
- // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC
- subtableParsers[6] = function parseLookup6() {
- var start = this.offset + this.relativeOffset;
- var substFormat = this.parseUShort();
- if (substFormat === 1) {
- return {
- substFormat: 1,
- coverage: this.parsePointer(Parser.coverage),
- chainRuleSets: this.parseListOfLists(function() {
- return {
- backtrack: this.parseUShortList(),
- input: this.parseUShortList(this.parseShort() - 1),
- lookahead: this.parseUShortList(),
- lookupRecords: this.parseRecordList(lookupRecordDesc)
- };
- })
- };
- } else if (substFormat === 2) {
- return {
- substFormat: 2,
- coverage: this.parsePointer(Parser.coverage),
- backtrackClassDef: this.parsePointer(Parser.classDef),
- inputClassDef: this.parsePointer(Parser.classDef),
- lookaheadClassDef: this.parsePointer(Parser.classDef),
- chainClassSet: this.parseListOfLists(function() {
- return {
- backtrack: this.parseUShortList(),
- input: this.parseUShortList(this.parseShort() - 1),
- lookahead: this.parseUShortList(),
- lookupRecords: this.parseRecordList(lookupRecordDesc)
- };
- })
- };
- } else if (substFormat === 3) {
- return {
- substFormat: 3,
- backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)),
- inputCoverage: this.parseList(Parser.pointer(Parser.coverage)),
- lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)),
- lookupRecords: this.parseRecordList(lookupRecordDesc)
- };
- }
- check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.');
- };
- // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES
- subtableParsers[7] = function parseLookup7() {
- // Extension Substitution subtable
- var substFormat = this.parseUShort();
- check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1');
- var extensionLookupType = this.parseUShort();
- var extensionParser = new Parser(this.data, this.offset + this.parseULong());
- return {
- substFormat: 1,
- lookupType: extensionLookupType,
- extension: subtableParsers[extensionLookupType].call(extensionParser)
- };
- };
- // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS
- subtableParsers[8] = function parseLookup8() {
- var substFormat = this.parseUShort();
- check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1');
- return {
- substFormat: substFormat,
- coverage: this.parsePointer(Parser.coverage),
- backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)),
- lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)),
- substitutes: this.parseUShortList()
- };
- };
- // https://www.microsoft.com/typography/OTSPEC/gsub.htm
- function parseGsubTable(data, start) {
- start = start || 0;
- var p = new Parser(data, start);
- var tableVersion = p.parseVersion(1);
- check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.');
- if (tableVersion === 1) {
- return {
- version: tableVersion,
- scripts: p.parseScriptList(),
- features: p.parseFeatureList(),
- lookups: p.parseLookupList(subtableParsers)
- };
- } else {
- return {
- version: tableVersion,
- scripts: p.parseScriptList(),
- features: p.parseFeatureList(),
- lookups: p.parseLookupList(subtableParsers),
- variations: p.parseFeatureVariationsList()
- };
- }
- }
- // GSUB Writing //////////////////////////////////////////////
- var subtableMakers = new Array(9);
- subtableMakers[1] = function makeLookup1(subtable) {
- if (subtable.substFormat === 1) {
- return new table.Table('substitutionTable', [
- {name: 'substFormat', type: 'USHORT', value: 1},
- {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)},
- {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId}
- ]);
- } else {
- return new table.Table('substitutionTable', [
- {name: 'substFormat', type: 'USHORT', value: 2},
- {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}
- ].concat(table.ushortList('substitute', subtable.substitute)));
- }
- };
- subtableMakers[2] = function makeLookup2(subtable) {
- check.assert(subtable.substFormat === 1, 'Lookup type 2 substFormat must be 1.');
- return new table.Table('substitutionTable', [
- {name: 'substFormat', type: 'USHORT', value: 1},
- {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}
- ].concat(table.tableList('seqSet', subtable.sequences, function(sequenceSet) {
- return new table.Table('sequenceSetTable', table.ushortList('sequence', sequenceSet));
- })));
- };
- subtableMakers[3] = function makeLookup3(subtable) {
- check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.');
- return new table.Table('substitutionTable', [
- {name: 'substFormat', type: 'USHORT', value: 1},
- {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}
- ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) {
- return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet));
- })));
- };
- subtableMakers[4] = function makeLookup4(subtable) {
- check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.');
- return new table.Table('substitutionTable', [
- {name: 'substFormat', type: 'USHORT', value: 1},
- {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}
- ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) {
- return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) {
- return new table.Table('ligatureTable',
- [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}]
- .concat(table.ushortList('component', ligature.components, ligature.components.length + 1))
- );
- }));
- })));
- };
- subtableMakers[6] = function makeLookup6(subtable) {
- if (subtable.substFormat === 1) {
- var returnTable = new table.Table('chainContextTable', [
- {name: 'substFormat', type: 'USHORT', value: subtable.substFormat},
- {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}
- ].concat(table.tableList('chainRuleSet', subtable.chainRuleSets, function(chainRuleSet) {
- return new table.Table('chainRuleSetTable', table.tableList('chainRule', chainRuleSet, function(chainRule) {
- var tableData = table.ushortList('backtrackGlyph', chainRule.backtrack, chainRule.backtrack.length)
- .concat(table.ushortList('inputGlyph', chainRule.input, chainRule.input.length + 1))
- .concat(table.ushortList('lookaheadGlyph', chainRule.lookahead, chainRule.lookahead.length))
- .concat(table.ushortList('substitution', [], chainRule.lookupRecords.length));
- chainRule.lookupRecords.forEach(function (record, i) {
- tableData = tableData
- .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex})
- .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex});
- });
- return new table.Table('chainRuleTable', tableData);
- }));
- })));
- return returnTable;
- } else if (subtable.substFormat === 2) {
- check.assert(false, 'lookup type 6 format 2 is not yet supported.');
- } else if (subtable.substFormat === 3) {
- var tableData = [
- {name: 'substFormat', type: 'USHORT', value: subtable.substFormat} ];
- tableData.push({name: 'backtrackGlyphCount', type: 'USHORT', value: subtable.backtrackCoverage.length});
- subtable.backtrackCoverage.forEach(function (coverage, i) {
- tableData.push({name: 'backtrackCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)});
- });
- tableData.push({name: 'inputGlyphCount', type: 'USHORT', value: subtable.inputCoverage.length});
- subtable.inputCoverage.forEach(function (coverage, i) {
- tableData.push({name: 'inputCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)});
- });
- tableData.push({name: 'lookaheadGlyphCount', type: 'USHORT', value: subtable.lookaheadCoverage.length});
- subtable.lookaheadCoverage.forEach(function (coverage, i) {
- tableData.push({name: 'lookaheadCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)});
- });
- tableData.push({name: 'substitutionCount', type: 'USHORT', value: subtable.lookupRecords.length});
- subtable.lookupRecords.forEach(function (record, i) {
- tableData = tableData
- .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex})
- .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex});
- });
- var returnTable$1 = new table.Table('chainContextTable', tableData);
- return returnTable$1;
- }
- check.assert(false, 'lookup type 6 format must be 1, 2 or 3.');
- };
- function makeGsubTable(gsub) {
- return new table.Table('GSUB', [
- {name: 'version', type: 'ULONG', value: 0x10000},
- {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)},
- {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)},
- {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)}
- ]);
- }
- var gsub = { parse: parseGsubTable, make: makeGsubTable };
- // The `GPOS` table contains kerning pairs, among other things.
- // Parse the metadata `meta` table.
- // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html
- function parseMetaTable(data, start) {
- var p = new parse.Parser(data, start);
- var tableVersion = p.parseULong();
- check.argument(tableVersion === 1, 'Unsupported META table version.');
- p.parseULong(); // flags - currently unused and set to 0
- p.parseULong(); // tableOffset
- var numDataMaps = p.parseULong();
- var tags = {};
- for (var i = 0; i < numDataMaps; i++) {
- var tag = p.parseTag();
- var dataOffset = p.parseULong();
- var dataLength = p.parseULong();
- var text = decode.UTF8(data, start + dataOffset, dataLength);
- tags[tag] = text;
- }
- return tags;
- }
- function makeMetaTable(tags) {
- var numTags = Object.keys(tags).length;
- var stringPool = '';
- var stringPoolOffset = 16 + numTags * 12;
- var result = new table.Table('meta', [
- {name: 'version', type: 'ULONG', value: 1},
- {name: 'flags', type: 'ULONG', value: 0},
- {name: 'offset', type: 'ULONG', value: stringPoolOffset},
- {name: 'numTags', type: 'ULONG', value: numTags}
- ]);
- for (var tag in tags) {
- var pos = stringPool.length;
- stringPool += tags[tag];
- result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag});
- result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos});
- result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length});
- }
- result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool});
- return result;
- }
- var meta = { parse: parseMetaTable, make: makeMetaTable };
- // The `COLR` table adds support for multi-colored glyphs
- function parseColrTable(data, start) {
- var p = new Parser(data, start);
- var version = p.parseUShort();
- check.argument(version === 0x0000, 'Only COLRv0 supported.');
- var numBaseGlyphRecords = p.parseUShort();
- var baseGlyphRecordsOffset = p.parseOffset32();
- var layerRecordsOffset = p.parseOffset32();
- var numLayerRecords = p.parseUShort();
- p.relativeOffset = baseGlyphRecordsOffset;
- var baseGlyphRecords = p.parseRecordList(numBaseGlyphRecords, {
- glyphID: Parser.uShort,
- firstLayerIndex: Parser.uShort,
- numLayers: Parser.uShort,
- });
- p.relativeOffset = layerRecordsOffset;
- var layerRecords = p.parseRecordList(numLayerRecords, {
- glyphID: Parser.uShort,
- paletteIndex: Parser.uShort
- });
- return {
- version: version,
- baseGlyphRecords: baseGlyphRecords,
- layerRecords: layerRecords,
- };
- }
- function makeColrTable(ref) {
- var version = ref.version; if ( version === void 0 ) version = 0x0000;
- var baseGlyphRecords = ref.baseGlyphRecords; if ( baseGlyphRecords === void 0 ) baseGlyphRecords = [];
- var layerRecords = ref.layerRecords; if ( layerRecords === void 0 ) layerRecords = [];
- check.argument(version === 0x0000, 'Only COLRv0 supported.');
- var baseGlyphRecordsOffset = 14;
- var layerRecordsOffset = baseGlyphRecordsOffset + (baseGlyphRecords.length * 6);
- return new table.Table('COLR', [
- { name: 'version', type: 'USHORT', value: version },
- { name: 'numBaseGlyphRecords', type: 'USHORT', value: baseGlyphRecords.length },
- { name: 'baseGlyphRecordsOffset', type: 'ULONG', value: baseGlyphRecordsOffset },
- { name: 'layerRecordsOffset', type: 'ULONG', value: layerRecordsOffset },
- { name: 'numLayerRecords', type: 'USHORT', value: layerRecords.length } ].concat( baseGlyphRecords.map(function (glyph, i) { return [
- { name: 'glyphID_' + i, type: 'USHORT', value: glyph.glyphID },
- { name: 'firstLayerIndex_' + i, type: 'USHORT', value: glyph.firstLayerIndex },
- { name: 'numLayers_' + i, type: 'USHORT', value: glyph.numLayers } ]; }).flat(),
- layerRecords.map(function (layer, i) { return [
- { name: 'LayerGlyphID_' + i, type: 'USHORT', value: layer.glyphID },
- { name: 'paletteIndex_' + i, type: 'USHORT', value: layer.paletteIndex } ]; }).flat() ));
- }
- var colr = { parse: parseColrTable, make: makeColrTable };
- // The `CPAL` define a contiguous list of colors (colorRecords)
- // Parse the header `head` table
- function parseCpalTable(data, start) {
- var p = new Parser(data, start);
- var version = p.parseShort();
- var numPaletteEntries = p.parseShort();
- var numPalettes = p.parseShort();
- var numColorRecords = p.parseShort();
- var colorRecordsArrayOffset = p.parseOffset32();
- var colorRecordIndices = p.parseUShortList(numPalettes);
- p.relativeOffset = colorRecordsArrayOffset;
- var colorRecords = p.parseULongList(numColorRecords);
- return {
- version: version,
- numPaletteEntries: numPaletteEntries,
- colorRecords: colorRecords,
- colorRecordIndices: colorRecordIndices,
- };
- }
- function makeCpalTable(ref) {
- var version = ref.version; if ( version === void 0 ) version = 0;
- var numPaletteEntries = ref.numPaletteEntries; if ( numPaletteEntries === void 0 ) numPaletteEntries = 0;
- var colorRecords = ref.colorRecords; if ( colorRecords === void 0 ) colorRecords = [];
- var colorRecordIndices = ref.colorRecordIndices; if ( colorRecordIndices === void 0 ) colorRecordIndices = [0];
- check.argument(version === 0, 'Only CPALv0 are supported.');
- check.argument(colorRecords.length, 'No colorRecords given.');
- check.argument(colorRecordIndices.length, 'No colorRecordIndices given.');
- check.argument(!numPaletteEntries && colorRecordIndices.length == 1, 'Can\'t infer numPaletteEntries on multiple colorRecordIndices');
- return new table.Table('CPAL', [
- { name: 'version', type: 'USHORT', value: version },
- { name: 'numPaletteEntries', type: 'USHORT', value: numPaletteEntries || colorRecords.length },
- { name: 'numPalettes', type: 'USHORT', value: colorRecordIndices.length },
- { name: 'numColorRecords', type: 'USHORT', value: colorRecords.length },
- { name: 'colorRecordsArrayOffset', type: 'ULONG', value: 12 + 2 * colorRecordIndices.length } ].concat( colorRecordIndices.map(function (palette, i) { return ({ name: 'colorRecordIndices_' + i, type: 'USHORT', value: palette }); }),
- colorRecords.map(function (color, i) { return ({ name: 'colorRecords_' + i, type: 'ULONG', value: color }); }) ));
- }
- var cpal = { parse: parseCpalTable, make: makeCpalTable };
- // The `sfnt` wrapper provides organization for the tables in the font.
- function log2(v) {
- return Math.log(v) / Math.log(2) | 0;
- }
- function computeCheckSum(bytes) {
- while (bytes.length % 4 !== 0) {
- bytes.push(0);
- }
- var sum = 0;
- for (var i = 0; i < bytes.length; i += 4) {
- sum += (bytes[i] << 24) +
- (bytes[i + 1] << 16) +
- (bytes[i + 2] << 8) +
- (bytes[i + 3]);
- }
- sum %= Math.pow(2, 32);
- return sum;
- }
- function makeTableRecord(tag, checkSum, offset, length) {
- return new table.Record('Table Record', [
- {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''},
- {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0},
- {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0},
- {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0}
- ]);
- }
- function makeSfntTable(tables) {
- var sfnt = new table.Table('sfnt', [
- {name: 'version', type: 'TAG', value: 'OTTO'},
- {name: 'numTables', type: 'USHORT', value: 0},
- {name: 'searchRange', type: 'USHORT', value: 0},
- {name: 'entrySelector', type: 'USHORT', value: 0},
- {name: 'rangeShift', type: 'USHORT', value: 0}
- ]);
- sfnt.tables = tables;
- sfnt.numTables = tables.length;
- var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables));
- sfnt.searchRange = 16 * highestPowerOf2;
- sfnt.entrySelector = log2(highestPowerOf2);
- sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange;
- var recordFields = [];
- var tableFields = [];
- var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables);
- while (offset % 4 !== 0) {
- offset += 1;
- tableFields.push({name: 'padding', type: 'BYTE', value: 0});
- }
- for (var i = 0; i < tables.length; i += 1) {
- var t = tables[i];
- check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.');
- var tableLength = t.sizeOf();
- var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength);
- recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord});
- tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t});
- offset += tableLength;
- check.argument(!isNaN(offset), 'Something went wrong calculating the offset.');
- while (offset % 4 !== 0) {
- offset += 1;
- tableFields.push({name: 'padding', type: 'BYTE', value: 0});
- }
- }
- // Table records need to be sorted alphabetically.
- recordFields.sort(function(r1, r2) {
- if (r1.value.tag > r2.value.tag) {
- return 1;
- } else {
- return -1;
- }
- });
- sfnt.fields = sfnt.fields.concat(recordFields);
- sfnt.fields = sfnt.fields.concat(tableFields);
- return sfnt;
- }
- // Get the metrics for a character. If the string has more than one character
- // this function returns metrics for the first available character.
- // You can provide optional fallback metrics if no characters are available.
- function metricsForChar(font, chars, notFoundMetrics) {
- for (var i = 0; i < chars.length; i += 1) {
- var glyphIndex = font.charToGlyphIndex(chars[i]);
- if (glyphIndex > 0) {
- var glyph = font.glyphs.get(glyphIndex);
- return glyph.getMetrics();
- }
- }
- return notFoundMetrics;
- }
- function average(vs) {
- var sum = 0;
- for (var i = 0; i < vs.length; i += 1) {
- sum += vs[i];
- }
- return sum / vs.length;
- }
- // Convert the font object to a SFNT data structure.
- // This structure contains all the necessary tables and metadata to create a binary OTF file.
- function fontToSfntTable(font) {
- var xMins = [];
- var yMins = [];
- var xMaxs = [];
- var yMaxs = [];
- var advanceWidths = [];
- var leftSideBearings = [];
- var rightSideBearings = [];
- var firstCharIndex;
- var lastCharIndex = 0;
- var ulUnicodeRange1 = 0;
- var ulUnicodeRange2 = 0;
- var ulUnicodeRange3 = 0;
- var ulUnicodeRange4 = 0;
- for (var i = 0; i < font.glyphs.length; i += 1) {
- var glyph = font.glyphs.get(i);
- var unicode = glyph.unicode | 0;
- if (isNaN(glyph.advanceWidth)) {
- throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.');
- }
- if (firstCharIndex > unicode || firstCharIndex === undefined) {
- // ignore .notdef char
- if (unicode > 0) {
- firstCharIndex = unicode;
- }
- }
- if (lastCharIndex < unicode) {
- lastCharIndex = unicode;
- }
- var position = os2.getUnicodeRange(unicode);
- if (position < 32) {
- ulUnicodeRange1 |= 1 << position;
- } else if (position < 64) {
- ulUnicodeRange2 |= 1 << position - 32;
- } else if (position < 96) {
- ulUnicodeRange3 |= 1 << position - 64;
- } else if (position < 123) {
- ulUnicodeRange4 |= 1 << position - 96;
- } else {
- throw new Error('Unicode ranges bits > 123 are reserved for internal usage');
- }
- // Skip non-important characters.
- if (glyph.name === '.notdef') { continue; }
- var metrics = glyph.getMetrics();
- xMins.push(metrics.xMin);
- yMins.push(metrics.yMin);
- xMaxs.push(metrics.xMax);
- yMaxs.push(metrics.yMax);
- leftSideBearings.push(metrics.leftSideBearing);
- rightSideBearings.push(metrics.rightSideBearing);
- advanceWidths.push(glyph.advanceWidth);
- }
- var globals = {
- xMin: Math.min.apply(null, xMins),
- yMin: Math.min.apply(null, yMins),
- xMax: Math.max.apply(null, xMaxs),
- yMax: Math.max.apply(null, yMaxs),
- advanceWidthMax: Math.max.apply(null, advanceWidths),
- advanceWidthAvg: average(advanceWidths),
- minLeftSideBearing: Math.min.apply(null, leftSideBearings),
- maxLeftSideBearing: Math.max.apply(null, leftSideBearings),
- minRightSideBearing: Math.min.apply(null, rightSideBearings)
- };
- globals.ascender = font.ascender;
- globals.descender = font.descender;
- var headTable = head.make({
- flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0)
- unitsPerEm: font.unitsPerEm,
- xMin: globals.xMin,
- yMin: globals.yMin,
- xMax: globals.xMax,
- yMax: globals.yMax,
- lowestRecPPEM: 3,
- createdTimestamp: font.createdTimestamp
- });
- var hheaTable = hhea.make({
- ascender: globals.ascender,
- descender: globals.descender,
- advanceWidthMax: globals.advanceWidthMax,
- minLeftSideBearing: globals.minLeftSideBearing,
- minRightSideBearing: globals.minRightSideBearing,
- xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin),
- numberOfHMetrics: font.glyphs.length
- });
- var maxpTable = maxp.make(font.glyphs.length);
- var os2Table = os2.make(Object.assign({
- xAvgCharWidth: Math.round(globals.advanceWidthAvg),
- usFirstCharIndex: firstCharIndex,
- usLastCharIndex: lastCharIndex,
- ulUnicodeRange1: ulUnicodeRange1,
- ulUnicodeRange2: ulUnicodeRange2,
- ulUnicodeRange3: ulUnicodeRange3,
- ulUnicodeRange4: ulUnicodeRange4,
- // See http://typophile.com/node/13081 for more info on vertical metrics.
- // We get metrics for typical characters (such as "x" for xHeight).
- // We provide some fallback characters if characters are unavailable: their
- // ordering was chosen experimentally.
- sTypoAscender: globals.ascender,
- sTypoDescender: globals.descender,
- sTypoLineGap: 0,
- usWinAscent: globals.yMax,
- usWinDescent: Math.abs(globals.yMin),
- ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now
- sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax,
- sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax,
- usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available.
- usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available.
- }, font.tables.os2));
- var hmtxTable = hmtx.make(font.glyphs);
- var cmapTable = cmap.make(font.glyphs);
- var englishFamilyName = font.getEnglishName('fontFamily');
- var englishStyleName = font.getEnglishName('fontSubfamily');
- var englishFullName = englishFamilyName + ' ' + englishStyleName;
- var postScriptName = font.getEnglishName('postScriptName');
- if (!postScriptName) {
- postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName;
- }
- var names = {};
- for (var n in font.names) {
- names[n] = font.names[n];
- }
- if (!names.uniqueID) {
- names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName};
- }
- if (!names.postScriptName) {
- names.postScriptName = {en: postScriptName};
- }
- if (!names.preferredFamily) {
- names.preferredFamily = font.names.fontFamily;
- }
- if (!names.preferredSubfamily) {
- names.preferredSubfamily = font.names.fontSubfamily;
- }
- var languageTags = [];
- var nameTable = _name.make(names, languageTags);
- var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined);
- var postTable = post.make();
- var cffTable = cff.make(font.glyphs, {
- version: font.getEnglishName('version'),
- fullName: englishFullName,
- familyName: englishFamilyName,
- weightName: englishStyleName,
- postScriptName: postScriptName,
- unitsPerEm: font.unitsPerEm,
- fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax]
- });
- var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined;
- // The order does not matter because makeSfntTable() will sort them.
- var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable];
- if (ltagTable) {
- tables.push(ltagTable);
- }
- // Optional tables
- if (font.tables.gsub) {
- tables.push(gsub.make(font.tables.gsub));
- }
- if (font.tables.cpal) {
- tables.push(cpal.make(font.tables.cpal));
- }
- if (font.tables.colr) {
- tables.push(colr.make(font.tables.colr));
- }
- if (metaTable) {
- tables.push(metaTable);
- }
- var sfntTable = makeSfntTable(tables);
- // Compute the font's checkSum and store it in head.checkSumAdjustment.
- var bytes = sfntTable.encode();
- var checkSum = computeCheckSum(bytes);
- var tableFields = sfntTable.fields;
- var checkSumAdjusted = false;
- for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) {
- if (tableFields[i$1].name === 'head table') {
- tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum;
- checkSumAdjusted = true;
- break;
- }
- }
- if (!checkSumAdjusted) {
- throw new Error('Could not find head table with checkSum to adjust.');
- }
- return sfntTable;
- }
- var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum };
- // The Layout object is the prototype of Substitution objects, and provides
- function searchTag(arr, tag) {
- /* jshint bitwise: false */
- var imin = 0;
- var imax = arr.length - 1;
- while (imin <= imax) {
- var imid = (imin + imax) >>> 1;
- var val = arr[imid].tag;
- if (val === tag) {
- return imid;
- } else if (val < tag) {
- imin = imid + 1;
- } else { imax = imid - 1; }
- }
- // Not found: return -1-insertion point
- return -imin - 1;
- }
- function binSearch(arr, value) {
- /* jshint bitwise: false */
- var imin = 0;
- var imax = arr.length - 1;
- while (imin <= imax) {
- var imid = (imin + imax) >>> 1;
- var val = arr[imid];
- if (val === value) {
- return imid;
- } else if (val < value) {
- imin = imid + 1;
- } else { imax = imid - 1; }
- }
- // Not found: return -1-insertion point
- return -imin - 1;
- }
- // binary search in a list of ranges (coverage, class definition)
- function searchRange(ranges, value) {
- // jshint bitwise: false
- var range;
- var imin = 0;
- var imax = ranges.length - 1;
- while (imin <= imax) {
- var imid = (imin + imax) >>> 1;
- range = ranges[imid];
- var start = range.start;
- if (start === value) {
- return range;
- } else if (start < value) {
- imin = imid + 1;
- } else { imax = imid - 1; }
- }
- if (imin > 0) {
- range = ranges[imin - 1];
- if (value > range.end) { return 0; }
- return range;
- }
- }
- /**
- * @exports opentype.Layout
- * @class
- */
- function Layout(font, tableName) {
- this.font = font;
- this.tableName = tableName;
- }
- Layout.prototype = {
- /**
- * Binary search an object by "tag" property
- * @instance
- * @function searchTag
- * @memberof opentype.Layout
- * @param {Array} arr
- * @param {string} tag
- * @return {number}
- */
- searchTag: searchTag,
- /**
- * Binary search in a list of numbers
- * @instance
- * @function binSearch
- * @memberof opentype.Layout
- * @param {Array} arr
- * @param {number} value
- * @return {number}
- */
- binSearch: binSearch,
- /**
- * Get or create the Layout table (GSUB, GPOS etc).
- * @param {boolean} create - Whether to create a new one.
- * @return {Object} The GSUB or GPOS table.
- */
- getTable: function(create) {
- var layout = this.font.tables[this.tableName];
- if (!layout && create) {
- layout = this.font.tables[this.tableName] = this.createDefaultTable();
- }
- return layout;
- },
- /**
- * Returns all scripts in the substitution table.
- * @instance
- * @return {Array}
- */
- getScriptNames: function() {
- var layout = this.getTable();
- if (!layout) { return []; }
- return layout.scripts.map(function(script) {
- return script.tag;
- });
- },
- /**
- * Returns the best bet for a script name.
- * Returns 'DFLT' if it exists.
- * If not, returns 'latn' if it exists.
- * If neither exist, returns undefined.
- */
- getDefaultScriptName: function() {
- var layout = this.getTable();
- if (!layout) { return; }
- var hasLatn = false;
- for (var i = 0; i < layout.scripts.length; i++) {
- var name = layout.scripts[i].tag;
- if (name === 'DFLT') { return name; }
- if (name === 'latn') { hasLatn = true; }
- }
- if (hasLatn) { return 'latn'; }
- },
- /**
- * Returns all LangSysRecords in the given script.
- * @instance
- * @param {string} [script='DFLT']
- * @param {boolean} create - forces the creation of this script table if it doesn't exist.
- * @return {Object} An object with tag and script properties.
- */
- getScriptTable: function(script, create) {
- var layout = this.getTable(create);
- if (layout) {
- script = script || 'DFLT';
- var scripts = layout.scripts;
- var pos = searchTag(layout.scripts, script);
- if (pos >= 0) {
- return scripts[pos].script;
- } else if (create) {
- var scr = {
- tag: script,
- script: {
- defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []},
- langSysRecords: []
- }
- };
- scripts.splice(-1 - pos, 0, scr);
- return scr.script;
- }
- }
- },
- /**
- * Returns a language system table
- * @instance
- * @param {string} [script='DFLT']
- * @param {string} [language='dlft']
- * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist.
- * @return {Object}
- */
- getLangSysTable: function(script, language, create) {
- var scriptTable = this.getScriptTable(script, create);
- if (scriptTable) {
- if (!language || language === 'dflt' || language === 'DFLT') {
- return scriptTable.defaultLangSys;
- }
- var pos = searchTag(scriptTable.langSysRecords, language);
- if (pos >= 0) {
- return scriptTable.langSysRecords[pos].langSys;
- } else if (create) {
- var langSysRecord = {
- tag: language,
- langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}
- };
- scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord);
- return langSysRecord.langSys;
- }
- }
- },
- /**
- * Get a specific feature table.
- * @instance
- * @param {string} [script='DFLT']
- * @param {string} [language='dlft']
- * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm
- * @param {boolean} create - forces the creation of the feature table if it doesn't exist.
- * @return {Object}
- */
- getFeatureTable: function(script, language, feature, create) {
- var langSysTable = this.getLangSysTable(script, language, create);
- if (langSysTable) {
- var featureRecord;
- var featIndexes = langSysTable.featureIndexes;
- var allFeatures = this.font.tables[this.tableName].features;
- // The FeatureIndex array of indices is in arbitrary order,
- // even if allFeatures is sorted alphabetically by feature tag.
- for (var i = 0; i < featIndexes.length; i++) {
- featureRecord = allFeatures[featIndexes[i]];
- if (featureRecord.tag === feature) {
- return featureRecord.feature;
- }
- }
- if (create) {
- var index = allFeatures.length;
- // Automatic ordering of features would require to shift feature indexes in the script list.
- check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.');
- featureRecord = {
- tag: feature,
- feature: { params: 0, lookupListIndexes: [] }
- };
- allFeatures.push(featureRecord);
- featIndexes.push(index);
- return featureRecord.feature;
- }
- }
- },
- /**
- * Get the lookup tables of a given type for a script/language/feature.
- * @instance
- * @param {string} [script='DFLT']
- * @param {string} [language='dlft']
- * @param {string} feature - 4-letter feature code
- * @param {number} lookupType - 1 to 9
- * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables.
- * @return {Object[]}
- */
- getLookupTables: function(script, language, feature, lookupType, create) {
- var featureTable = this.getFeatureTable(script, language, feature, create);
- var tables = [];
- if (featureTable) {
- var lookupTable;
- var lookupListIndexes = featureTable.lookupListIndexes;
- var allLookups = this.font.tables[this.tableName].lookups;
- // lookupListIndexes are in no particular order, so use naive search.
- for (var i = 0; i < lookupListIndexes.length; i++) {
- lookupTable = allLookups[lookupListIndexes[i]];
- if (lookupTable.lookupType === lookupType) {
- tables.push(lookupTable);
- }
- }
- if (tables.length === 0 && create) {
- lookupTable = {
- lookupType: lookupType,
- lookupFlag: 0,
- subtables: [],
- markFilteringSet: undefined
- };
- var index = allLookups.length;
- allLookups.push(lookupTable);
- lookupListIndexes.push(index);
- return [lookupTable];
- }
- }
- return tables;
- },
- /**
- * Find a glyph in a class definition table
- * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table
- * @param {object} classDefTable - an OpenType Layout class definition table
- * @param {number} glyphIndex - the index of the glyph to find
- * @returns {number} -1 if not found
- */
- getGlyphClass: function(classDefTable, glyphIndex) {
- switch (classDefTable.format) {
- case 1:
- if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) {
- return classDefTable.classes[glyphIndex - classDefTable.startGlyph];
- }
- return 0;
- case 2:
- var range = searchRange(classDefTable.ranges, glyphIndex);
- return range ? range.classId : 0;
- }
- },
- /**
- * Find a glyph in a coverage table
- * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table
- * @param {object} coverageTable - an OpenType Layout coverage table
- * @param {number} glyphIndex - the index of the glyph to find
- * @returns {number} -1 if not found
- */
- getCoverageIndex: function(coverageTable, glyphIndex) {
- switch (coverageTable.format) {
- case 1:
- var index = binSearch(coverageTable.glyphs, glyphIndex);
- return index >= 0 ? index : -1;
- case 2:
- var range = searchRange(coverageTable.ranges, glyphIndex);
- return range ? range.index + glyphIndex - range.start : -1;
- }
- },
- /**
- * Returns the list of glyph indexes of a coverage table.
- * Format 1: the list is stored raw
- * Format 2: compact list as range records.
- * @instance
- * @param {Object} coverageTable
- * @return {Array}
- */
- expandCoverage: function(coverageTable) {
- if (coverageTable.format === 1) {
- return coverageTable.glyphs;
- } else {
- var glyphs = [];
- var ranges = coverageTable.ranges;
- for (var i = 0; i < ranges.length; i++) {
- var range = ranges[i];
- var start = range.start;
- var end = range.end;
- for (var j = start; j <= end; j++) {
- glyphs.push(j);
- }
- }
- return glyphs;
- }
- }
- };
- // The Position object provides utility methods to manipulate
- /**
- * @exports opentype.Position
- * @class
- * @extends opentype.Layout
- * @param {opentype.Font}
- * @constructor
- */
- function Position(font) {
- Layout.call(this, font, 'gpos');
- }
- Position.prototype = Layout.prototype;
- /**
- * Init some data for faster and easier access later.
- */
- Position.prototype.init = function() {
- var script = this.getDefaultScriptName();
- this.defaultKerningTables = this.getKerningTables(script);
- };
- /**
- * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value.
- *
- * @param {integer} leftIndex - left glyph index
- * @param {integer} rightIndex - right glyph index
- * @returns {integer}
- */
- Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) {
- for (var i = 0; i < kerningLookups.length; i++) {
- var subtables = kerningLookups[i].subtables;
- for (var j = 0; j < subtables.length; j++) {
- var subtable = subtables[j];
- var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex);
- if (covIndex < 0) { continue; }
- switch (subtable.posFormat) {
- case 1:
- // Search Pair Adjustment Positioning Format 1
- var pairSet = subtable.pairSets[covIndex];
- for (var k = 0; k < pairSet.length; k++) {
- var pair = pairSet[k];
- if (pair.secondGlyph === rightIndex) {
- return pair.value1 && pair.value1.xAdvance || 0;
- }
- }
- break; // left glyph found, not right glyph - try next subtable
- case 2:
- // Search Pair Adjustment Positioning Format 2
- var class1 = this.getGlyphClass(subtable.classDef1, leftIndex);
- var class2 = this.getGlyphClass(subtable.classDef2, rightIndex);
- var pair$1 = subtable.classRecords[class1][class2];
- return pair$1.value1 && pair$1.value1.xAdvance || 0;
- }
- }
- }
- return 0;
- };
- /**
- * List all kerning lookup tables.
- *
- * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value
- * @param {string} [language='dflt']
- * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table)
- */
- Position.prototype.getKerningTables = function(script, language) {
- if (this.font.tables.gpos) {
- return this.getLookupTables(script, language, 'kern', 2);
- }
- };
- // The Substitution object provides utility methods to manipulate
- /**
- * @exports opentype.Substitution
- * @class
- * @extends opentype.Layout
- * @param {opentype.Font}
- * @constructor
- */
- function Substitution(font) {
- Layout.call(this, font, 'gsub');
- }
- // Check if 2 arrays of primitives are equal.
- function arraysEqual(ar1, ar2) {
- var n = ar1.length;
- if (n !== ar2.length) { return false; }
- for (var i = 0; i < n; i++) {
- if (ar1[i] !== ar2[i]) { return false; }
- }
- return true;
- }
- // Find the first subtable of a lookup table in a particular format.
- function getSubstFormat(lookupTable, format, defaultSubtable) {
- var subtables = lookupTable.subtables;
- for (var i = 0; i < subtables.length; i++) {
- var subtable = subtables[i];
- if (subtable.substFormat === format) {
- return subtable;
- }
- }
- if (defaultSubtable) {
- subtables.push(defaultSubtable);
- return defaultSubtable;
- }
- return undefined;
- }
- Substitution.prototype = Layout.prototype;
- /**
- * Create a default GSUB table.
- * @return {Object} gsub - The GSUB table.
- */
- Substitution.prototype.createDefaultTable = function() {
- // Generate a default empty GSUB table with just a DFLT script and dflt lang sys.
- return {
- version: 1,
- scripts: [{
- tag: 'DFLT',
- script: {
- defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] },
- langSysRecords: []
- }
- }],
- features: [],
- lookups: []
- };
- };
- /**
- * List all single substitutions (lookup type 1) for a given script, language, and feature.
- * @param {string} [script='DFLT']
- * @param {string} [language='dflt']
- * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...)
- * @return {Array} substitutions - The list of substitutions.
- */
- Substitution.prototype.getSingle = function(feature, script, language) {
- var substitutions = [];
- var lookupTables = this.getLookupTables(script, language, feature, 1);
- for (var idx = 0; idx < lookupTables.length; idx++) {
- var subtables = lookupTables[idx].subtables;
- for (var i = 0; i < subtables.length; i++) {
- var subtable = subtables[i];
- var glyphs = this.expandCoverage(subtable.coverage);
- var j = (void 0);
- if (subtable.substFormat === 1) {
- var delta = subtable.deltaGlyphId;
- for (j = 0; j < glyphs.length; j++) {
- var glyph = glyphs[j];
- substitutions.push({ sub: glyph, by: glyph + delta });
- }
- } else {
- var substitute = subtable.substitute;
- for (j = 0; j < glyphs.length; j++) {
- substitutions.push({ sub: glyphs[j], by: substitute[j] });
- }
- }
- }
- }
- return substitutions;
- };
- /**
- * List all multiple substitutions (lookup type 2) for a given script, language, and feature.
- * @param {string} [script='DFLT']
- * @param {string} [language='dflt']
- * @param {string} feature - 4-character feature name ('ccmp', 'stch')
- * @return {Array} substitutions - The list of substitutions.
- */
- Substitution.prototype.getMultiple = function(feature, script, language) {
- var substitutions = [];
- var lookupTables = this.getLookupTables(script, language, feature, 2);
- for (var idx = 0; idx < lookupTables.length; idx++) {
- var subtables = lookupTables[idx].subtables;
- for (var i = 0; i < subtables.length; i++) {
- var subtable = subtables[i];
- var glyphs = this.expandCoverage(subtable.coverage);
- var j = (void 0);
- for (j = 0; j < glyphs.length; j++) {
- var glyph = glyphs[j];
- var replacements = subtable.sequences[j];
- substitutions.push({ sub: glyph, by: replacements });
- }
- }
- }
- return substitutions;
- };
- /**
- * List all alternates (lookup type 3) for a given script, language, and feature.
- * @param {string} [script='DFLT']
- * @param {string} [language='dflt']
- * @param {string} feature - 4-character feature name ('aalt', 'salt'...)
- * @return {Array} alternates - The list of alternates
- */
- Substitution.prototype.getAlternates = function(feature, script, language) {
- var alternates = [];
- var lookupTables = this.getLookupTables(script, language, feature, 3);
- for (var idx = 0; idx < lookupTables.length; idx++) {
- var subtables = lookupTables[idx].subtables;
- for (var i = 0; i < subtables.length; i++) {
- var subtable = subtables[i];
- var glyphs = this.expandCoverage(subtable.coverage);
- var alternateSets = subtable.alternateSets;
- for (var j = 0; j < glyphs.length; j++) {
- alternates.push({ sub: glyphs[j], by: alternateSets[j] });
- }
- }
- }
- return alternates;
- };
- /**
- * List all ligatures (lookup type 4) for a given script, language, and feature.
- * The result is an array of ligature objects like { sub: [ids], by: id }
- * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...)
- * @param {string} [script='DFLT']
- * @param {string} [language='dflt']
- * @return {Array} ligatures - The list of ligatures.
- */
- Substitution.prototype.getLigatures = function(feature, script, language) {
- var ligatures = [];
- var lookupTables = this.getLookupTables(script, language, feature, 4);
- for (var idx = 0; idx < lookupTables.length; idx++) {
- var subtables = lookupTables[idx].subtables;
- for (var i = 0; i < subtables.length; i++) {
- var subtable = subtables[i];
- var glyphs = this.expandCoverage(subtable.coverage);
- var ligatureSets = subtable.ligatureSets;
- for (var j = 0; j < glyphs.length; j++) {
- var startGlyph = glyphs[j];
- var ligSet = ligatureSets[j];
- for (var k = 0; k < ligSet.length; k++) {
- var lig = ligSet[k];
- ligatures.push({
- sub: [startGlyph].concat(lig.components),
- by: lig.ligGlyph
- });
- }
- }
- }
- }
- return ligatures;
- };
- /**
- * Add or modify a single substitution (lookup type 1)
- * Format 2, more flexible, is always used.
- * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...)
- * @param {Object} substitution - { sub: id, by: id } (format 1 is not supported)
- * @param {string} [script='DFLT']
- * @param {string} [language='dflt']
- */
- Substitution.prototype.addSingle = function(feature, substitution, script, language) {
- var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0];
- var subtable = getSubstFormat(lookupTable, 2, { // lookup type 1 subtable, format 2, coverage format 1
- substFormat: 2,
- coverage: {format: 1, glyphs: []},
- substitute: []
- });
- check.assert(subtable.coverage.format === 1, 'Single: unable to modify coverage table format ' + subtable.coverage.format);
- var coverageGlyph = substitution.sub;
- var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph);
- if (pos < 0) {
- pos = -1 - pos;
- subtable.coverage.glyphs.splice(pos, 0, coverageGlyph);
- subtable.substitute.splice(pos, 0, 0);
- }
- subtable.substitute[pos] = substitution.by;
- };
- /**
- * Add or modify a multiple substitution (lookup type 2)
- * @param {string} feature - 4-letter feature name ('ccmp', 'stch')
- * @param {Object} substitution - { sub: id, by: [id] } for format 2.
- * @param {string} [script='DFLT']
- * @param {string} [language='dflt']
- */
- Substitution.prototype.addMultiple = function(feature, substitution, script, language) {
- check.assert(substitution.by instanceof Array && substitution.by.length > 1, 'Multiple: "by" must be an array of two or more ids');
- var lookupTable = this.getLookupTables(script, language, feature, 2, true)[0];
- var subtable = getSubstFormat(lookupTable, 1, { // lookup type 2 subtable, format 1, coverage format 1
- substFormat: 1,
- coverage: {format: 1, glyphs: []},
- sequences: []
- });
- check.assert(subtable.coverage.format === 1, 'Multiple: unable to modify coverage table format ' + subtable.coverage.format);
- var coverageGlyph = substitution.sub;
- var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph);
- if (pos < 0) {
- pos = -1 - pos;
- subtable.coverage.glyphs.splice(pos, 0, coverageGlyph);
- subtable.sequences.splice(pos, 0, 0);
- }
- subtable.sequences[pos] = substitution.by;
- };
- /**
- * Add or modify an alternate substitution (lookup type 3)
- * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...)
- * @param {Object} substitution - { sub: id, by: [ids] }
- * @param {string} [script='DFLT']
- * @param {string} [language='dflt']
- */
- Substitution.prototype.addAlternate = function(feature, substitution, script, language) {
- var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0];
- var subtable = getSubstFormat(lookupTable, 1, { // lookup type 3 subtable, format 1, coverage format 1
- substFormat: 1,
- coverage: {format: 1, glyphs: []},
- alternateSets: []
- });
- check.assert(subtable.coverage.format === 1, 'Alternate: unable to modify coverage table format ' + subtable.coverage.format);
- var coverageGlyph = substitution.sub;
- var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph);
- if (pos < 0) {
- pos = -1 - pos;
- subtable.coverage.glyphs.splice(pos, 0, coverageGlyph);
- subtable.alternateSets.splice(pos, 0, 0);
- }
- subtable.alternateSets[pos] = substitution.by;
- };
- /**
- * Add a ligature (lookup type 4)
- * Ligatures with more components must be stored ahead of those with fewer components in order to be found
- * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...)
- * @param {Object} ligature - { sub: [ids], by: id }
- * @param {string} [script='DFLT']
- * @param {string} [language='dflt']
- */
- Substitution.prototype.addLigature = function(feature, ligature, script, language) {
- var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0];
- var subtable = lookupTable.subtables[0];
- if (!subtable) {
- subtable = { // lookup type 4 subtable, format 1, coverage format 1
- substFormat: 1,
- coverage: { format: 1, glyphs: [] },
- ligatureSets: []
- };
- lookupTable.subtables[0] = subtable;
- }
- check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format);
- var coverageGlyph = ligature.sub[0];
- var ligComponents = ligature.sub.slice(1);
- var ligatureTable = {
- ligGlyph: ligature.by,
- components: ligComponents
- };
- var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph);
- if (pos >= 0) {
- // ligatureSet already exists
- var ligatureSet = subtable.ligatureSets[pos];
- for (var i = 0; i < ligatureSet.length; i++) {
- // If ligature already exists, return.
- if (arraysEqual(ligatureSet[i].components, ligComponents)) {
- return;
- }
- }
- // ligature does not exist: add it.
- ligatureSet.push(ligatureTable);
- } else {
- // Create a new ligatureSet and add coverage for the first glyph.
- pos = -1 - pos;
- subtable.coverage.glyphs.splice(pos, 0, coverageGlyph);
- subtable.ligatureSets.splice(pos, 0, [ligatureTable]);
- }
- };
- /**
- * List all feature data for a given script and language.
- * @param {string} feature - 4-letter feature name
- * @param {string} [script='DFLT']
- * @param {string} [language='dflt']
- * @return {Array} substitutions - The list of substitutions.
- */
- Substitution.prototype.getFeature = function(feature, script, language) {
- if (/ss\d\d/.test(feature)) {
- // ss01 - ss20
- return this.getSingle(feature, script, language);
- }
- switch (feature) {
- case 'aalt':
- case 'salt':
- return this.getSingle(feature, script, language)
- .concat(this.getAlternates(feature, script, language));
- case 'dlig':
- case 'liga':
- case 'rlig':
- return this.getLigatures(feature, script, language);
- case 'ccmp':
- return this.getMultiple(feature, script, language)
- .concat(this.getLigatures(feature, script, language));
- case 'stch':
- return this.getMultiple(feature, script, language);
- }
- return undefined;
- };
- /**
- * Add a substitution to a feature for a given script and language.
- * @param {string} feature - 4-letter feature name
- * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] })
- * @param {string} [script='DFLT']
- * @param {string} [language='dflt']
- */
- Substitution.prototype.add = function(feature, sub, script, language) {
- if (/ss\d\d/.test(feature)) {
- // ss01 - ss20
- return this.addSingle(feature, sub, script, language);
- }
- switch (feature) {
- case 'aalt':
- case 'salt':
- if (typeof sub.by === 'number') {
- return this.addSingle(feature, sub, script, language);
- }
- return this.addAlternate(feature, sub, script, language);
- case 'dlig':
- case 'liga':
- case 'rlig':
- return this.addLigature(feature, sub, script, language);
- case 'ccmp':
- if (sub.by instanceof Array) {
- return this.addMultiple(feature, sub, script, language);
- }
- return this.addLigature(feature, sub, script, language);
- }
- return undefined;
- };
- function isBrowser() {
- return typeof window !== 'undefined';
- }
- function nodeBufferToArrayBuffer(buffer) {
- var ab = new ArrayBuffer(buffer.length);
- var view = new Uint8Array(ab);
- for (var i = 0; i < buffer.length; ++i) {
- view[i] = buffer[i];
- }
- return ab;
- }
- function arrayBufferToNodeBuffer(ab) {
- var buffer = new Buffer(ab.byteLength);
- var view = new Uint8Array(ab);
- for (var i = 0; i < buffer.length; ++i) {
- buffer[i] = view[i];
- }
- return buffer;
- }
- function checkArgument(expression, message) {
- if (!expression) {
- throw message;
- }
- }
- // The `glyf` table describes the glyphs in TrueType outline format.
- // Parse the coordinate data for a glyph.
- function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) {
- var v;
- if ((flag & shortVectorBitMask) > 0) {
- // The coordinate is 1 byte long.
- v = p.parseByte();
- // The `same` bit is re-used for short values to signify the sign of the value.
- if ((flag & sameBitMask) === 0) {
- v = -v;
- }
- v = previousValue + v;
- } else {
- // The coordinate is 2 bytes long.
- // If the `same` bit is set, the coordinate is the same as the previous coordinate.
- if ((flag & sameBitMask) > 0) {
- v = previousValue;
- } else {
- // Parse the coordinate as a signed 16-bit delta value.
- v = previousValue + p.parseShort();
- }
- }
- return v;
- }
- // Parse a TrueType glyph.
- function parseGlyph(glyph, data, start) {
- var p = new parse.Parser(data, start);
- glyph.numberOfContours = p.parseShort();
- glyph._xMin = p.parseShort();
- glyph._yMin = p.parseShort();
- glyph._xMax = p.parseShort();
- glyph._yMax = p.parseShort();
- var flags;
- var flag;
- if (glyph.numberOfContours > 0) {
- // This glyph is not a composite.
- var endPointIndices = glyph.endPointIndices = [];
- for (var i = 0; i < glyph.numberOfContours; i += 1) {
- endPointIndices.push(p.parseUShort());
- }
- glyph.instructionLength = p.parseUShort();
- glyph.instructions = [];
- for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) {
- glyph.instructions.push(p.parseByte());
- }
- var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1;
- flags = [];
- for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) {
- flag = p.parseByte();
- flags.push(flag);
- // If bit 3 is set, we repeat this flag n times, where n is the next byte.
- if ((flag & 8) > 0) {
- var repeatCount = p.parseByte();
- for (var j = 0; j < repeatCount; j += 1) {
- flags.push(flag);
- i$2 += 1;
- }
- }
- }
- check.argument(flags.length === numberOfCoordinates, 'Bad flags.');
- if (endPointIndices.length > 0) {
- var points = [];
- var point;
- // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0.
- if (numberOfCoordinates > 0) {
- for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) {
- flag = flags[i$3];
- point = {};
- point.onCurve = !!(flag & 1);
- point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0;
- points.push(point);
- }
- var px = 0;
- for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) {
- flag = flags[i$4];
- point = points[i$4];
- point.x = parseGlyphCoordinate(p, flag, px, 2, 16);
- px = point.x;
- }
- var py = 0;
- for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) {
- flag = flags[i$5];
- point = points[i$5];
- point.y = parseGlyphCoordinate(p, flag, py, 4, 32);
- py = point.y;
- }
- }
- glyph.points = points;
- } else {
- glyph.points = [];
- }
- } else if (glyph.numberOfContours === 0) {
- glyph.points = [];
- } else {
- glyph.isComposite = true;
- glyph.points = [];
- glyph.components = [];
- var moreComponents = true;
- while (moreComponents) {
- flags = p.parseUShort();
- var component = {
- glyphIndex: p.parseUShort(),
- xScale: 1,
- scale01: 0,
- scale10: 0,
- yScale: 1,
- dx: 0,
- dy: 0
- };
- if ((flags & 1) > 0) {
- // The arguments are words
- if ((flags & 2) > 0) {
- // values are offset
- component.dx = p.parseShort();
- component.dy = p.parseShort();
- } else {
- // values are matched points
- component.matchedPoints = [p.parseUShort(), p.parseUShort()];
- }
- } else {
- // The arguments are bytes
- if ((flags & 2) > 0) {
- // values are offset
- component.dx = p.parseChar();
- component.dy = p.parseChar();
- } else {
- // values are matched points
- component.matchedPoints = [p.parseByte(), p.parseByte()];
- }
- }
- if ((flags & 8) > 0) {
- // We have a scale
- component.xScale = component.yScale = p.parseF2Dot14();
- } else if ((flags & 64) > 0) {
- // We have an X / Y scale
- component.xScale = p.parseF2Dot14();
- component.yScale = p.parseF2Dot14();
- } else if ((flags & 128) > 0) {
- // We have a 2x2 transformation
- component.xScale = p.parseF2Dot14();
- component.scale01 = p.parseF2Dot14();
- component.scale10 = p.parseF2Dot14();
- component.yScale = p.parseF2Dot14();
- }
- glyph.components.push(component);
- moreComponents = !!(flags & 32);
- }
- if (flags & 0x100) {
- // We have instructions
- glyph.instructionLength = p.parseUShort();
- glyph.instructions = [];
- for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) {
- glyph.instructions.push(p.parseByte());
- }
- }
- }
- }
- // Transform an array of points and return a new array.
- function transformPoints(points, transform) {
- var newPoints = [];
- for (var i = 0; i < points.length; i += 1) {
- var pt = points[i];
- var newPt = {
- x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx,
- y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy,
- onCurve: pt.onCurve,
- lastPointOfContour: pt.lastPointOfContour
- };
- newPoints.push(newPt);
- }
- return newPoints;
- }
- function getContours(points) {
- var contours = [];
- var currentContour = [];
- for (var i = 0; i < points.length; i += 1) {
- var pt = points[i];
- currentContour.push(pt);
- if (pt.lastPointOfContour) {
- contours.push(currentContour);
- currentContour = [];
- }
- }
- check.argument(currentContour.length === 0, 'There are still points left in the current contour.');
- return contours;
- }
- // Convert the TrueType glyph outline to a Path.
- function getPath(points) {
- var p = new Path();
- if (!points) {
- return p;
- }
- var contours = getContours(points);
- for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) {
- var contour = contours[contourIndex];
- var prev = null;
- var curr = contour[contour.length - 1];
- var next = contour[0];
- if (curr.onCurve) {
- p.moveTo(curr.x, curr.y);
- } else {
- if (next.onCurve) {
- p.moveTo(next.x, next.y);
- } else {
- // If both first and last points are off-curve, start at their middle.
- var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5};
- p.moveTo(start.x, start.y);
- }
- }
- for (var i = 0; i < contour.length; ++i) {
- prev = curr;
- curr = next;
- next = contour[(i + 1) % contour.length];
- if (curr.onCurve) {
- // This is a straight line.
- p.lineTo(curr.x, curr.y);
- } else {
- var prev2 = prev;
- var next2 = next;
- if (!prev.onCurve) {
- prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 };
- }
- if (!next.onCurve) {
- next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 };
- }
- p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y);
- }
- }
- p.closePath();
- }
- return p;
- }
- function buildPath(glyphs, glyph) {
- if (glyph.isComposite) {
- for (var j = 0; j < glyph.components.length; j += 1) {
- var component = glyph.components[j];
- var componentGlyph = glyphs.get(component.glyphIndex);
- // Force the ttfGlyphLoader to parse the glyph.
- componentGlyph.getPath();
- if (componentGlyph.points) {
- var transformedPoints = (void 0);
- if (component.matchedPoints === undefined) {
- // component positioned by offset
- transformedPoints = transformPoints(componentGlyph.points, component);
- } else {
- // component positioned by matched points
- if ((component.matchedPoints[0] > glyph.points.length - 1) ||
- (component.matchedPoints[1] > componentGlyph.points.length - 1)) {
- throw Error('Matched points out of range in ' + glyph.name);
- }
- var firstPt = glyph.points[component.matchedPoints[0]];
- var secondPt = componentGlyph.points[component.matchedPoints[1]];
- var transform = {
- xScale: component.xScale, scale01: component.scale01,
- scale10: component.scale10, yScale: component.yScale,
- dx: 0, dy: 0
- };
- secondPt = transformPoints([secondPt], transform)[0];
- transform.dx = firstPt.x - secondPt.x;
- transform.dy = firstPt.y - secondPt.y;
- transformedPoints = transformPoints(componentGlyph.points, transform);
- }
- glyph.points = glyph.points.concat(transformedPoints);
- }
- }
- }
- return getPath(glyph.points);
- }
- function parseGlyfTableAll(data, start, loca, font) {
- var glyphs = new glyphset.GlyphSet(font);
- // The last element of the loca table is invalid.
- for (var i = 0; i < loca.length - 1; i += 1) {
- var offset = loca[i];
- var nextOffset = loca[i + 1];
- if (offset !== nextOffset) {
- glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath));
- } else {
- glyphs.push(i, glyphset.glyphLoader(font, i));
- }
- }
- return glyphs;
- }
- function parseGlyfTableOnLowMemory(data, start, loca, font) {
- var glyphs = new glyphset.GlyphSet(font);
- font._push = function(i) {
- var offset = loca[i];
- var nextOffset = loca[i + 1];
- if (offset !== nextOffset) {
- glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath));
- } else {
- glyphs.push(i, glyphset.glyphLoader(font, i));
- }
- };
- return glyphs;
- }
- // Parse all the glyphs according to the offsets from the `loca` table.
- function parseGlyfTable(data, start, loca, font, opt) {
- if (opt.lowMemory)
- { return parseGlyfTableOnLowMemory(data, start, loca, font); }
- else
- { return parseGlyfTableAll(data, start, loca, font); }
- }
- var glyf = { getPath: getPath, parse: parseGlyfTable};
- /* A TrueType font hinting interpreter.
- *
- * (c) 2017 Axel Kittenberger
- *
- * This interpreter has been implemented according to this documentation:
- * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html
- *
- * According to the documentation F24DOT6 values are used for pixels.
- * That means calculation is 1/64 pixel accurate and uses integer operations.
- * However, Javascript has floating point operations by default and only
- * those are available. One could make a case to simulate the 1/64 accuracy
- * exactly by truncating after every division operation
- * (for example with << 0) to get pixel exactly results as other TrueType
- * implementations. It may make sense since some fonts are pixel optimized
- * by hand using DELTAP instructions. The current implementation doesn't
- * and rather uses full floating point precision.
- *
- * xScale, yScale and rotation is currently ignored.
- *
- * A few non-trivial instructions are missing as I didn't encounter yet
- * a font that used them to test a possible implementation.
- *
- * Some fonts seem to use undocumented features regarding the twilight zone.
- * Only some of them are implemented as they were encountered.
- *
- * The exports.DEBUG statements are removed on the minified distribution file.
- */
- var instructionTable;
- var exec;
- var execGlyph;
- var execComponent;
- /*
- * Creates a hinting object.
- *
- * There ought to be exactly one
- * for each truetype font that is used for hinting.
- */
- function Hinting(font) {
- // the font this hinting object is for
- this.font = font;
- this.getCommands = function (hPoints) {
- return glyf.getPath(hPoints).commands;
- };
- // cached states
- this._fpgmState =
- this._prepState =
- undefined;
- // errorState
- // 0 ... all okay
- // 1 ... had an error in a glyf,
- // continue working but stop spamming
- // the console
- // 2 ... error at prep, stop hinting at this ppem
- // 3 ... error at fpeg, stop hinting for this font at all
- this._errorState = 0;
- }
- /*
- * Not rounding.
- */
- function roundOff(v) {
- return v;
- }
- /*
- * Rounding to grid.
- */
- function roundToGrid(v) {
- //Rounding in TT is supposed to "symmetrical around zero"
- return Math.sign(v) * Math.round(Math.abs(v));
- }
- /*
- * Rounding to double grid.
- */
- function roundToDoubleGrid(v) {
- return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2;
- }
- /*
- * Rounding to half grid.
- */
- function roundToHalfGrid(v) {
- return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5);
- }
- /*
- * Rounding to up to grid.
- */
- function roundUpToGrid(v) {
- return Math.sign(v) * Math.ceil(Math.abs(v));
- }
- /*
- * Rounding to down to grid.
- */
- function roundDownToGrid(v) {
- return Math.sign(v) * Math.floor(Math.abs(v));
- }
- /*
- * Super rounding.
- */
- var roundSuper = function (v) {
- var period = this.srPeriod;
- var phase = this.srPhase;
- var threshold = this.srThreshold;
- var sign = 1;
- if (v < 0) {
- v = -v;
- sign = -1;
- }
- v += threshold - phase;
- v = Math.trunc(v / period) * period;
- v += phase;
- // according to http://xgridfit.sourceforge.net/round.html
- if (v < 0) { return phase * sign; }
- return v * sign;
- };
- /*
- * Unit vector of x-axis.
- */
- var xUnitVector = {
- x: 1,
- y: 0,
- axis: 'x',
- // Gets the projected distance between two points.
- // o1/o2 ... if true, respective original position is used.
- distance: function (p1, p2, o1, o2) {
- return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x);
- },
- // Moves point p so the moved position has the same relative
- // position to the moved positions of rp1 and rp2 than the
- // original positions had.
- //
- // See APPENDIX on INTERPOLATE at the bottom of this file.
- interpolate: function (p, rp1, rp2, pv) {
- var do1;
- var do2;
- var doa1;
- var doa2;
- var dm1;
- var dm2;
- var dt;
- if (!pv || pv === this) {
- do1 = p.xo - rp1.xo;
- do2 = p.xo - rp2.xo;
- dm1 = rp1.x - rp1.xo;
- dm2 = rp2.x - rp2.xo;
- doa1 = Math.abs(do1);
- doa2 = Math.abs(do2);
- dt = doa1 + doa2;
- if (dt === 0) {
- p.x = p.xo + (dm1 + dm2) / 2;
- return;
- }
- p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt;
- return;
- }
- do1 = pv.distance(p, rp1, true, true);
- do2 = pv.distance(p, rp2, true, true);
- dm1 = pv.distance(rp1, rp1, false, true);
- dm2 = pv.distance(rp2, rp2, false, true);
- doa1 = Math.abs(do1);
- doa2 = Math.abs(do2);
- dt = doa1 + doa2;
- if (dt === 0) {
- xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true);
- return;
- }
- xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true);
- },
- // Slope of line normal to this
- normalSlope: Number.NEGATIVE_INFINITY,
- // Sets the point 'p' relative to point 'rp'
- // by the distance 'd'.
- //
- // See APPENDIX on SETRELATIVE at the bottom of this file.
- //
- // p ... point to set
- // rp ... reference point
- // d ... distance on projection vector
- // pv ... projection vector (undefined = this)
- // org ... if true, uses the original position of rp as reference.
- setRelative: function (p, rp, d, pv, org) {
- if (!pv || pv === this) {
- p.x = (org ? rp.xo : rp.x) + d;
- return;
- }
- var rpx = org ? rp.xo : rp.x;
- var rpy = org ? rp.yo : rp.y;
- var rpdx = rpx + d * pv.x;
- var rpdy = rpy + d * pv.y;
- p.x = rpdx + (p.y - rpdy) / pv.normalSlope;
- },
- // Slope of vector line.
- slope: 0,
- // Touches the point p.
- touch: function (p) {
- p.xTouched = true;
- },
- // Tests if a point p is touched.
- touched: function (p) {
- return p.xTouched;
- },
- // Untouches the point p.
- untouch: function (p) {
- p.xTouched = false;
- }
- };
- /*
- * Unit vector of y-axis.
- */
- var yUnitVector = {
- x: 0,
- y: 1,
- axis: 'y',
- // Gets the projected distance between two points.
- // o1/o2 ... if true, respective original position is used.
- distance: function (p1, p2, o1, o2) {
- return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y);
- },
- // Moves point p so the moved position has the same relative
- // position to the moved positions of rp1 and rp2 than the
- // original positions had.
- //
- // See APPENDIX on INTERPOLATE at the bottom of this file.
- interpolate: function (p, rp1, rp2, pv) {
- var do1;
- var do2;
- var doa1;
- var doa2;
- var dm1;
- var dm2;
- var dt;
- if (!pv || pv === this) {
- do1 = p.yo - rp1.yo;
- do2 = p.yo - rp2.yo;
- dm1 = rp1.y - rp1.yo;
- dm2 = rp2.y - rp2.yo;
- doa1 = Math.abs(do1);
- doa2 = Math.abs(do2);
- dt = doa1 + doa2;
- if (dt === 0) {
- p.y = p.yo + (dm1 + dm2) / 2;
- return;
- }
- p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt;
- return;
- }
- do1 = pv.distance(p, rp1, true, true);
- do2 = pv.distance(p, rp2, true, true);
- dm1 = pv.distance(rp1, rp1, false, true);
- dm2 = pv.distance(rp2, rp2, false, true);
- doa1 = Math.abs(do1);
- doa2 = Math.abs(do2);
- dt = doa1 + doa2;
- if (dt === 0) {
- yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true);
- return;
- }
- yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true);
- },
- // Slope of line normal to this.
- normalSlope: 0,
- // Sets the point 'p' relative to point 'rp'
- // by the distance 'd'
- //
- // See APPENDIX on SETRELATIVE at the bottom of this file.
- //
- // p ... point to set
- // rp ... reference point
- // d ... distance on projection vector
- // pv ... projection vector (undefined = this)
- // org ... if true, uses the original position of rp as reference.
- setRelative: function (p, rp, d, pv, org) {
- if (!pv || pv === this) {
- p.y = (org ? rp.yo : rp.y) + d;
- return;
- }
- var rpx = org ? rp.xo : rp.x;
- var rpy = org ? rp.yo : rp.y;
- var rpdx = rpx + d * pv.x;
- var rpdy = rpy + d * pv.y;
- p.y = rpdy + pv.normalSlope * (p.x - rpdx);
- },
- // Slope of vector line.
- slope: Number.POSITIVE_INFINITY,
- // Touches the point p.
- touch: function (p) {
- p.yTouched = true;
- },
- // Tests if a point p is touched.
- touched: function (p) {
- return p.yTouched;
- },
- // Untouches the point p.
- untouch: function (p) {
- p.yTouched = false;
- }
- };
- Object.freeze(xUnitVector);
- Object.freeze(yUnitVector);
- /*
- * Creates a unit vector that is not x- or y-axis.
- */
- function UnitVector(x, y) {
- this.x = x;
- this.y = y;
- this.axis = undefined;
- this.slope = y / x;
- this.normalSlope = -x / y;
- Object.freeze(this);
- }
- /*
- * Gets the projected distance between two points.
- * o1/o2 ... if true, respective original position is used.
- */
- UnitVector.prototype.distance = function(p1, p2, o1, o2) {
- return (
- this.x * xUnitVector.distance(p1, p2, o1, o2) +
- this.y * yUnitVector.distance(p1, p2, o1, o2)
- );
- };
- /*
- * Moves point p so the moved position has the same relative
- * position to the moved positions of rp1 and rp2 than the
- * original positions had.
- *
- * See APPENDIX on INTERPOLATE at the bottom of this file.
- */
- UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) {
- var dm1;
- var dm2;
- var do1;
- var do2;
- var doa1;
- var doa2;
- var dt;
- do1 = pv.distance(p, rp1, true, true);
- do2 = pv.distance(p, rp2, true, true);
- dm1 = pv.distance(rp1, rp1, false, true);
- dm2 = pv.distance(rp2, rp2, false, true);
- doa1 = Math.abs(do1);
- doa2 = Math.abs(do2);
- dt = doa1 + doa2;
- if (dt === 0) {
- this.setRelative(p, p, (dm1 + dm2) / 2, pv, true);
- return;
- }
- this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true);
- };
- /*
- * Sets the point 'p' relative to point 'rp'
- * by the distance 'd'
- *
- * See APPENDIX on SETRELATIVE at the bottom of this file.
- *
- * p ... point to set
- * rp ... reference point
- * d ... distance on projection vector
- * pv ... projection vector (undefined = this)
- * org ... if true, uses the original position of rp as reference.
- */
- UnitVector.prototype.setRelative = function(p, rp, d, pv, org) {
- pv = pv || this;
- var rpx = org ? rp.xo : rp.x;
- var rpy = org ? rp.yo : rp.y;
- var rpdx = rpx + d * pv.x;
- var rpdy = rpy + d * pv.y;
- var pvns = pv.normalSlope;
- var fvs = this.slope;
- var px = p.x;
- var py = p.y;
- p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns);
- p.y = fvs * (p.x - px) + py;
- };
- /*
- * Touches the point p.
- */
- UnitVector.prototype.touch = function(p) {
- p.xTouched = true;
- p.yTouched = true;
- };
- /*
- * Returns a unit vector with x/y coordinates.
- */
- function getUnitVector(x, y) {
- var d = Math.sqrt(x * x + y * y);
- x /= d;
- y /= d;
- if (x === 1 && y === 0) { return xUnitVector; }
- else if (x === 0 && y === 1) { return yUnitVector; }
- else { return new UnitVector(x, y); }
- }
- /*
- * Creates a point in the hinting engine.
- */
- function HPoint(
- x,
- y,
- lastPointOfContour,
- onCurve
- ) {
- this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value
- this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value
- this.lastPointOfContour = lastPointOfContour;
- this.onCurve = onCurve;
- this.prevPointOnContour = undefined;
- this.nextPointOnContour = undefined;
- this.xTouched = false;
- this.yTouched = false;
- Object.preventExtensions(this);
- }
- /*
- * Returns the next touched point on the contour.
- *
- * v ... unit vector to test touch axis.
- */
- HPoint.prototype.nextTouched = function(v) {
- var p = this.nextPointOnContour;
- while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; }
- return p;
- };
- /*
- * Returns the previous touched point on the contour
- *
- * v ... unit vector to test touch axis.
- */
- HPoint.prototype.prevTouched = function(v) {
- var p = this.prevPointOnContour;
- while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; }
- return p;
- };
- /*
- * The zero point.
- */
- var HPZero = Object.freeze(new HPoint(0, 0));
- /*
- * The default state of the interpreter.
- *
- * Note: Freezing the defaultState and then deriving from it
- * makes the V8 Javascript engine going awkward,
- * so this is avoided, albeit the defaultState shouldn't
- * ever change.
- */
- var defaultState = {
- cvCutIn: 17 / 16, // control value cut in
- deltaBase: 9,
- deltaShift: 0.125,
- loop: 1, // loops some instructions
- minDis: 1, // minimum distance
- autoFlip: true
- };
- /*
- * The current state of the interpreter.
- *
- * env ... 'fpgm' or 'prep' or 'glyf'
- * prog ... the program
- */
- function State(env, prog) {
- this.env = env;
- this.stack = [];
- this.prog = prog;
- switch (env) {
- case 'glyf' :
- this.zp0 = this.zp1 = this.zp2 = 1;
- this.rp0 = this.rp1 = this.rp2 = 0;
- /* fall through */
- case 'prep' :
- this.fv = this.pv = this.dpv = xUnitVector;
- this.round = roundToGrid;
- }
- }
- /*
- * Executes a glyph program.
- *
- * This does the hinting for each glyph.
- *
- * Returns an array of moved points.
- *
- * glyph: the glyph to hint
- * ppem: the size the glyph is rendered for
- */
- Hinting.prototype.exec = function(glyph, ppem) {
- if (typeof ppem !== 'number') {
- throw new Error('Point size is not a number!');
- }
- // Received a fatal error, don't do any hinting anymore.
- if (this._errorState > 2) { return; }
- var font = this.font;
- var prepState = this._prepState;
- if (!prepState || prepState.ppem !== ppem) {
- var fpgmState = this._fpgmState;
- if (!fpgmState) {
- // Executes the fpgm state.
- // This is used by fonts to define functions.
- State.prototype = defaultState;
- fpgmState =
- this._fpgmState =
- new State('fpgm', font.tables.fpgm);
- fpgmState.funcs = [ ];
- fpgmState.font = font;
- if (exports.DEBUG) {
- console.log('---EXEC FPGM---');
- fpgmState.step = -1;
- }
- try {
- exec(fpgmState);
- } catch (e) {
- console.log('Hinting error in FPGM:' + e);
- this._errorState = 3;
- return;
- }
- }
- // Executes the prep program for this ppem setting.
- // This is used by fonts to set cvt values
- // depending on to be rendered font size.
- State.prototype = fpgmState;
- prepState =
- this._prepState =
- new State('prep', font.tables.prep);
- prepState.ppem = ppem;
- // Creates a copy of the cvt table
- // and scales it to the current ppem setting.
- var oCvt = font.tables.cvt;
- if (oCvt) {
- var cvt = prepState.cvt = new Array(oCvt.length);
- var scale = ppem / font.unitsPerEm;
- for (var c = 0; c < oCvt.length; c++) {
- cvt[c] = oCvt[c] * scale;
- }
- } else {
- prepState.cvt = [];
- }
- if (exports.DEBUG) {
- console.log('---EXEC PREP---');
- prepState.step = -1;
- }
- try {
- exec(prepState);
- } catch (e) {
- if (this._errorState < 2) {
- console.log('Hinting error in PREP:' + e);
- }
- this._errorState = 2;
- }
- }
- if (this._errorState > 1) { return; }
- try {
- return execGlyph(glyph, prepState);
- } catch (e) {
- if (this._errorState < 1) {
- console.log('Hinting error:' + e);
- console.log('Note: further hinting errors are silenced');
- }
- this._errorState = 1;
- return undefined;
- }
- };
- /*
- * Executes the hinting program for a glyph.
- */
- execGlyph = function(glyph, prepState) {
- // original point positions
- var xScale = prepState.ppem / prepState.font.unitsPerEm;
- var yScale = xScale;
- var components = glyph.components;
- var contours;
- var gZone;
- var state;
- State.prototype = prepState;
- if (!components) {
- state = new State('glyf', glyph.instructions);
- if (exports.DEBUG) {
- console.log('---EXEC GLYPH---');
- state.step = -1;
- }
- execComponent(glyph, state, xScale, yScale);
- gZone = state.gZone;
- } else {
- var font = prepState.font;
- gZone = [];
- contours = [];
- for (var i = 0; i < components.length; i++) {
- var c = components[i];
- var cg = font.glyphs.get(c.glyphIndex);
- state = new State('glyf', cg.instructions);
- if (exports.DEBUG) {
- console.log('---EXEC COMP ' + i + '---');
- state.step = -1;
- }
- execComponent(cg, state, xScale, yScale);
- // appends the computed points to the result array
- // post processes the component points
- var dx = Math.round(c.dx * xScale);
- var dy = Math.round(c.dy * yScale);
- var gz = state.gZone;
- var cc = state.contours;
- for (var pi = 0; pi < gz.length; pi++) {
- var p = gz[pi];
- p.xTouched = p.yTouched = false;
- p.xo = p.x = p.x + dx;
- p.yo = p.y = p.y + dy;
- }
- var gLen = gZone.length;
- gZone.push.apply(gZone, gz);
- for (var j = 0; j < cc.length; j++) {
- contours.push(cc[j] + gLen);
- }
- }
- if (glyph.instructions && !state.inhibitGridFit) {
- // the composite has instructions on its own
- state = new State('glyf', glyph.instructions);
- state.gZone = state.z0 = state.z1 = state.z2 = gZone;
- state.contours = contours;
- // note: HPZero cannot be used here, since
- // the point might be modified
- gZone.push(
- new HPoint(0, 0),
- new HPoint(Math.round(glyph.advanceWidth * xScale), 0)
- );
- if (exports.DEBUG) {
- console.log('---EXEC COMPOSITE---');
- state.step = -1;
- }
- exec(state);
- gZone.length -= 2;
- }
- }
- return gZone;
- };
- /*
- * Executes the hinting program for a component of a multi-component glyph
- * or of the glyph itself for a non-component glyph.
- */
- execComponent = function(glyph, state, xScale, yScale)
- {
- var points = glyph.points || [];
- var pLen = points.length;
- var gZone = state.gZone = state.z0 = state.z1 = state.z2 = [];
- var contours = state.contours = [];
- // Scales the original points and
- // makes copies for the hinted points.
- var cp; // current point
- for (var i = 0; i < pLen; i++) {
- cp = points[i];
- gZone[i] = new HPoint(
- cp.x * xScale,
- cp.y * yScale,
- cp.lastPointOfContour,
- cp.onCurve
- );
- }
- // Chain links the contours.
- var sp; // start point
- var np; // next point
- for (var i$1 = 0; i$1 < pLen; i$1++) {
- cp = gZone[i$1];
- if (!sp) {
- sp = cp;
- contours.push(i$1);
- }
- if (cp.lastPointOfContour) {
- cp.nextPointOnContour = sp;
- sp.prevPointOnContour = cp;
- sp = undefined;
- } else {
- np = gZone[i$1 + 1];
- cp.nextPointOnContour = np;
- np.prevPointOnContour = cp;
- }
- }
- if (state.inhibitGridFit) { return; }
- if (exports.DEBUG) {
- console.log('PROCESSING GLYPH', state.stack);
- for (var i$2 = 0; i$2 < pLen; i$2++) {
- console.log(i$2, gZone[i$2].x, gZone[i$2].y);
- }
- }
- gZone.push(
- new HPoint(0, 0),
- new HPoint(Math.round(glyph.advanceWidth * xScale), 0)
- );
- exec(state);
- // Removes the extra points.
- gZone.length -= 2;
- if (exports.DEBUG) {
- console.log('FINISHED GLYPH', state.stack);
- for (var i$3 = 0; i$3 < pLen; i$3++) {
- console.log(i$3, gZone[i$3].x, gZone[i$3].y);
- }
- }
- };
- /*
- * Executes the program loaded in state.
- */
- exec = function(state) {
- var prog = state.prog;
- if (!prog) { return; }
- var pLen = prog.length;
- var ins;
- for (state.ip = 0; state.ip < pLen; state.ip++) {
- if (exports.DEBUG) { state.step++; }
- ins = instructionTable[prog[state.ip]];
- if (!ins) {
- throw new Error(
- 'unknown instruction: 0x' +
- Number(prog[state.ip]).toString(16)
- );
- }
- ins(state);
- // very extensive debugging for each step
- /*
- if (exports.DEBUG) {
- var da;
- if (state.gZone) {
- da = [];
- for (let i = 0; i < state.gZone.length; i++)
- {
- da.push(i + ' ' +
- state.gZone[i].x * 64 + ' ' +
- state.gZone[i].y * 64 + ' ' +
- (state.gZone[i].xTouched ? 'x' : '') +
- (state.gZone[i].yTouched ? 'y' : '')
- );
- }
- console.log('GZ', da);
- }
- if (state.tZone) {
- da = [];
- for (let i = 0; i < state.tZone.length; i++) {
- da.push(i + ' ' +
- state.tZone[i].x * 64 + ' ' +
- state.tZone[i].y * 64 + ' ' +
- (state.tZone[i].xTouched ? 'x' : '') +
- (state.tZone[i].yTouched ? 'y' : '')
- );
- }
- console.log('TZ', da);
- }
- if (state.stack.length > 10) {
- console.log(
- state.stack.length,
- '...', state.stack.slice(state.stack.length - 10)
- );
- } else {
- console.log(state.stack.length, state.stack);
- }
- }
- */
- }
- };
- /*
- * Initializes the twilight zone.
- *
- * This is only done if a SZPx instruction
- * refers to the twilight zone.
- */
- function initTZone(state)
- {
- var tZone = state.tZone = new Array(state.gZone.length);
- // no idea if this is actually correct...
- for (var i = 0; i < tZone.length; i++)
- {
- tZone[i] = new HPoint(0, 0);
- }
- }
- /*
- * Skips the instruction pointer ahead over an IF/ELSE block.
- * handleElse .. if true breaks on matching ELSE
- */
- function skip(state, handleElse)
- {
- var prog = state.prog;
- var ip = state.ip;
- var nesting = 1;
- var ins;
- do {
- ins = prog[++ip];
- if (ins === 0x58) // IF
- { nesting++; }
- else if (ins === 0x59) // EIF
- { nesting--; }
- else if (ins === 0x40) // NPUSHB
- { ip += prog[ip + 1] + 1; }
- else if (ins === 0x41) // NPUSHW
- { ip += 2 * prog[ip + 1] + 1; }
- else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB
- { ip += ins - 0xB0 + 1; }
- else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW
- { ip += (ins - 0xB8 + 1) * 2; }
- else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE
- { break; }
- } while (nesting > 0);
- state.ip = ip;
- }
- /*----------------------------------------------------------*
- * And then a lot of instructions... *
- *----------------------------------------------------------*/
- // SVTCA[a] Set freedom and projection Vectors To Coordinate Axis
- // 0x00-0x01
- function SVTCA(v, state) {
- if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); }
- state.fv = state.pv = state.dpv = v;
- }
- // SPVTCA[a] Set Projection Vector to Coordinate Axis
- // 0x02-0x03
- function SPVTCA(v, state) {
- if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); }
- state.pv = state.dpv = v;
- }
- // SFVTCA[a] Set Freedom Vector to Coordinate Axis
- // 0x04-0x05
- function SFVTCA(v, state) {
- if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); }
- state.fv = v;
- }
- // SPVTL[a] Set Projection Vector To Line
- // 0x06-0x07
- function SPVTL(a, state) {
- var stack = state.stack;
- var p2i = stack.pop();
- var p1i = stack.pop();
- var p2 = state.z2[p2i];
- var p1 = state.z1[p1i];
- if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); }
- var dx;
- var dy;
- if (!a) {
- dx = p1.x - p2.x;
- dy = p1.y - p2.y;
- } else {
- dx = p2.y - p1.y;
- dy = p1.x - p2.x;
- }
- state.pv = state.dpv = getUnitVector(dx, dy);
- }
- // SFVTL[a] Set Freedom Vector To Line
- // 0x08-0x09
- function SFVTL(a, state) {
- var stack = state.stack;
- var p2i = stack.pop();
- var p1i = stack.pop();
- var p2 = state.z2[p2i];
- var p1 = state.z1[p1i];
- if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); }
- var dx;
- var dy;
- if (!a) {
- dx = p1.x - p2.x;
- dy = p1.y - p2.y;
- } else {
- dx = p2.y - p1.y;
- dy = p1.x - p2.x;
- }
- state.fv = getUnitVector(dx, dy);
- }
- // SPVFS[] Set Projection Vector From Stack
- // 0x0A
- function SPVFS(state) {
- var stack = state.stack;
- var y = stack.pop();
- var x = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); }
- state.pv = state.dpv = getUnitVector(x, y);
- }
- // SFVFS[] Set Freedom Vector From Stack
- // 0x0B
- function SFVFS(state) {
- var stack = state.stack;
- var y = stack.pop();
- var x = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); }
- state.fv = getUnitVector(x, y);
- }
- // GPV[] Get Projection Vector
- // 0x0C
- function GPV(state) {
- var stack = state.stack;
- var pv = state.pv;
- if (exports.DEBUG) { console.log(state.step, 'GPV[]'); }
- stack.push(pv.x * 0x4000);
- stack.push(pv.y * 0x4000);
- }
- // GFV[] Get Freedom Vector
- // 0x0C
- function GFV(state) {
- var stack = state.stack;
- var fv = state.fv;
- if (exports.DEBUG) { console.log(state.step, 'GFV[]'); }
- stack.push(fv.x * 0x4000);
- stack.push(fv.y * 0x4000);
- }
- // SFVTPV[] Set Freedom Vector To Projection Vector
- // 0x0E
- function SFVTPV(state) {
- state.fv = state.pv;
- if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); }
- }
- // ISECT[] moves point p to the InterSECTion of two lines
- // 0x0F
- function ISECT(state)
- {
- var stack = state.stack;
- var pa0i = stack.pop();
- var pa1i = stack.pop();
- var pb0i = stack.pop();
- var pb1i = stack.pop();
- var pi = stack.pop();
- var z0 = state.z0;
- var z1 = state.z1;
- var pa0 = z0[pa0i];
- var pa1 = z0[pa1i];
- var pb0 = z1[pb0i];
- var pb1 = z1[pb1i];
- var p = state.z2[pi];
- if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); }
- // math from
- // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
- var x1 = pa0.x;
- var y1 = pa0.y;
- var x2 = pa1.x;
- var y2 = pa1.y;
- var x3 = pb0.x;
- var y3 = pb0.y;
- var x4 = pb1.x;
- var y4 = pb1.y;
- var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
- var f1 = x1 * y2 - y1 * x2;
- var f2 = x3 * y4 - y3 * x4;
- p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div;
- p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div;
- }
- // SRP0[] Set Reference Point 0
- // 0x10
- function SRP0(state) {
- state.rp0 = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); }
- }
- // SRP1[] Set Reference Point 1
- // 0x11
- function SRP1(state) {
- state.rp1 = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); }
- }
- // SRP1[] Set Reference Point 2
- // 0x12
- function SRP2(state) {
- state.rp2 = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); }
- }
- // SZP0[] Set Zone Pointer 0
- // 0x13
- function SZP0(state) {
- var n = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); }
- state.zp0 = n;
- switch (n) {
- case 0:
- if (!state.tZone) { initTZone(state); }
- state.z0 = state.tZone;
- break;
- case 1 :
- state.z0 = state.gZone;
- break;
- default :
- throw new Error('Invalid zone pointer');
- }
- }
- // SZP1[] Set Zone Pointer 1
- // 0x14
- function SZP1(state) {
- var n = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); }
- state.zp1 = n;
- switch (n) {
- case 0:
- if (!state.tZone) { initTZone(state); }
- state.z1 = state.tZone;
- break;
- case 1 :
- state.z1 = state.gZone;
- break;
- default :
- throw new Error('Invalid zone pointer');
- }
- }
- // SZP2[] Set Zone Pointer 2
- // 0x15
- function SZP2(state) {
- var n = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); }
- state.zp2 = n;
- switch (n) {
- case 0:
- if (!state.tZone) { initTZone(state); }
- state.z2 = state.tZone;
- break;
- case 1 :
- state.z2 = state.gZone;
- break;
- default :
- throw new Error('Invalid zone pointer');
- }
- }
- // SZPS[] Set Zone PointerS
- // 0x16
- function SZPS(state) {
- var n = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); }
- state.zp0 = state.zp1 = state.zp2 = n;
- switch (n) {
- case 0:
- if (!state.tZone) { initTZone(state); }
- state.z0 = state.z1 = state.z2 = state.tZone;
- break;
- case 1 :
- state.z0 = state.z1 = state.z2 = state.gZone;
- break;
- default :
- throw new Error('Invalid zone pointer');
- }
- }
- // SLOOP[] Set LOOP variable
- // 0x17
- function SLOOP(state) {
- state.loop = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); }
- }
- // RTG[] Round To Grid
- // 0x18
- function RTG(state) {
- if (exports.DEBUG) { console.log(state.step, 'RTG[]'); }
- state.round = roundToGrid;
- }
- // RTHG[] Round To Half Grid
- // 0x19
- function RTHG(state) {
- if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); }
- state.round = roundToHalfGrid;
- }
- // SMD[] Set Minimum Distance
- // 0x1A
- function SMD(state) {
- var d = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); }
- state.minDis = d / 0x40;
- }
- // ELSE[] ELSE clause
- // 0x1B
- function ELSE(state) {
- // This instruction has been reached by executing a then branch
- // so it just skips ahead until matching EIF.
- //
- // In case the IF was negative the IF[] instruction already
- // skipped forward over the ELSE[]
- if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); }
- skip(state, false);
- }
- // JMPR[] JuMP Relative
- // 0x1C
- function JMPR(state) {
- var o = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); }
- // A jump by 1 would do nothing.
- state.ip += o - 1;
- }
- // SCVTCI[] Set Control Value Table Cut-In
- // 0x1D
- function SCVTCI(state) {
- var n = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); }
- state.cvCutIn = n / 0x40;
- }
- // DUP[] DUPlicate top stack element
- // 0x20
- function DUP(state) {
- var stack = state.stack;
- if (exports.DEBUG) { console.log(state.step, 'DUP[]'); }
- stack.push(stack[stack.length - 1]);
- }
- // POP[] POP top stack element
- // 0x21
- function POP(state) {
- if (exports.DEBUG) { console.log(state.step, 'POP[]'); }
- state.stack.pop();
- }
- // CLEAR[] CLEAR the stack
- // 0x22
- function CLEAR(state) {
- if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); }
- state.stack.length = 0;
- }
- // SWAP[] SWAP the top two elements on the stack
- // 0x23
- function SWAP(state) {
- var stack = state.stack;
- var a = stack.pop();
- var b = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); }
- stack.push(a);
- stack.push(b);
- }
- // DEPTH[] DEPTH of the stack
- // 0x24
- function DEPTH(state) {
- var stack = state.stack;
- if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); }
- stack.push(stack.length);
- }
- // LOOPCALL[] LOOPCALL function
- // 0x2A
- function LOOPCALL(state) {
- var stack = state.stack;
- var fn = stack.pop();
- var c = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); }
- // saves callers program
- var cip = state.ip;
- var cprog = state.prog;
- state.prog = state.funcs[fn];
- // executes the function
- for (var i = 0; i < c; i++) {
- exec(state);
- if (exports.DEBUG) { console.log(
- ++state.step,
- i + 1 < c ? 'next loopcall' : 'done loopcall',
- i
- ); }
- }
- // restores the callers program
- state.ip = cip;
- state.prog = cprog;
- }
- // CALL[] CALL function
- // 0x2B
- function CALL(state) {
- var fn = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); }
- // saves callers program
- var cip = state.ip;
- var cprog = state.prog;
- state.prog = state.funcs[fn];
- // executes the function
- exec(state);
- // restores the callers program
- state.ip = cip;
- state.prog = cprog;
- if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); }
- }
- // CINDEX[] Copy the INDEXed element to the top of the stack
- // 0x25
- function CINDEX(state) {
- var stack = state.stack;
- var k = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); }
- // In case of k == 1, it copies the last element after popping
- // thus stack.length - k.
- stack.push(stack[stack.length - k]);
- }
- // MINDEX[] Move the INDEXed element to the top of the stack
- // 0x26
- function MINDEX(state) {
- var stack = state.stack;
- var k = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); }
- stack.push(stack.splice(stack.length - k, 1)[0]);
- }
- // FDEF[] Function DEFinition
- // 0x2C
- function FDEF(state) {
- if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); }
- var stack = state.stack;
- var prog = state.prog;
- var ip = state.ip;
- var fn = stack.pop();
- var ipBegin = ip;
- if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); }
- while (prog[++ip] !== 0x2D){ }
- state.ip = ip;
- state.funcs[fn] = prog.slice(ipBegin + 1, ip);
- }
- // MDAP[a] Move Direct Absolute Point
- // 0x2E-0x2F
- function MDAP(round, state) {
- var pi = state.stack.pop();
- var p = state.z0[pi];
- var fv = state.fv;
- var pv = state.pv;
- if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); }
- var d = pv.distance(p, HPZero);
- if (round) { d = state.round(d); }
- fv.setRelative(p, HPZero, d, pv);
- fv.touch(p);
- state.rp0 = state.rp1 = pi;
- }
- // IUP[a] Interpolate Untouched Points through the outline
- // 0x30
- function IUP(v, state) {
- var z2 = state.z2;
- var pLen = z2.length - 2;
- var cp;
- var pp;
- var np;
- if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); }
- for (var i = 0; i < pLen; i++) {
- cp = z2[i]; // current point
- // if this point has been touched go on
- if (v.touched(cp)) { continue; }
- pp = cp.prevTouched(v);
- // no point on the contour has been touched?
- if (pp === cp) { continue; }
- np = cp.nextTouched(v);
- if (pp === np) {
- // only one point on the contour has been touched
- // so simply moves the point like that
- v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true);
- }
- v.interpolate(cp, pp, np, v);
- }
- }
- // SHP[] SHift Point using reference point
- // 0x32-0x33
- function SHP(a, state) {
- var stack = state.stack;
- var rpi = a ? state.rp1 : state.rp2;
- var rp = (a ? state.z0 : state.z1)[rpi];
- var fv = state.fv;
- var pv = state.pv;
- var loop = state.loop;
- var z2 = state.z2;
- while (loop--)
- {
- var pi = stack.pop();
- var p = z2[pi];
- var d = pv.distance(rp, rp, false, true);
- fv.setRelative(p, p, d, pv);
- fv.touch(p);
- if (exports.DEBUG) {
- console.log(
- state.step,
- (state.loop > 1 ?
- 'loop ' + (state.loop - loop) + ': ' :
- ''
- ) +
- 'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi
- );
- }
- }
- state.loop = 1;
- }
- // SHC[] SHift Contour using reference point
- // 0x36-0x37
- function SHC(a, state) {
- var stack = state.stack;
- var rpi = a ? state.rp1 : state.rp2;
- var rp = (a ? state.z0 : state.z1)[rpi];
- var fv = state.fv;
- var pv = state.pv;
- var ci = stack.pop();
- var sp = state.z2[state.contours[ci]];
- var p = sp;
- if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); }
- var d = pv.distance(rp, rp, false, true);
- do {
- if (p !== rp) { fv.setRelative(p, p, d, pv); }
- p = p.nextPointOnContour;
- } while (p !== sp);
- }
- // SHZ[] SHift Zone using reference point
- // 0x36-0x37
- function SHZ(a, state) {
- var stack = state.stack;
- var rpi = a ? state.rp1 : state.rp2;
- var rp = (a ? state.z0 : state.z1)[rpi];
- var fv = state.fv;
- var pv = state.pv;
- var e = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); }
- var z;
- switch (e) {
- case 0 : z = state.tZone; break;
- case 1 : z = state.gZone; break;
- default : throw new Error('Invalid zone');
- }
- var p;
- var d = pv.distance(rp, rp, false, true);
- var pLen = z.length - 2;
- for (var i = 0; i < pLen; i++)
- {
- p = z[i];
- fv.setRelative(p, p, d, pv);
- //if (p !== rp) fv.setRelative(p, p, d, pv);
- }
- }
- // SHPIX[] SHift point by a PIXel amount
- // 0x38
- function SHPIX(state) {
- var stack = state.stack;
- var loop = state.loop;
- var fv = state.fv;
- var d = stack.pop() / 0x40;
- var z2 = state.z2;
- while (loop--) {
- var pi = stack.pop();
- var p = z2[pi];
- if (exports.DEBUG) {
- console.log(
- state.step,
- (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') +
- 'SHPIX[]', pi, d
- );
- }
- fv.setRelative(p, p, d);
- fv.touch(p);
- }
- state.loop = 1;
- }
- // IP[] Interpolate Point
- // 0x39
- function IP(state) {
- var stack = state.stack;
- var rp1i = state.rp1;
- var rp2i = state.rp2;
- var loop = state.loop;
- var rp1 = state.z0[rp1i];
- var rp2 = state.z1[rp2i];
- var fv = state.fv;
- var pv = state.dpv;
- var z2 = state.z2;
- while (loop--) {
- var pi = stack.pop();
- var p = z2[pi];
- if (exports.DEBUG) {
- console.log(
- state.step,
- (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') +
- 'IP[]', pi, rp1i, '<->', rp2i
- );
- }
- fv.interpolate(p, rp1, rp2, pv);
- fv.touch(p);
- }
- state.loop = 1;
- }
- // MSIRP[a] Move Stack Indirect Relative Point
- // 0x3A-0x3B
- function MSIRP(a, state) {
- var stack = state.stack;
- var d = stack.pop() / 64;
- var pi = stack.pop();
- var p = state.z1[pi];
- var rp0 = state.z0[state.rp0];
- var fv = state.fv;
- var pv = state.pv;
- fv.setRelative(p, rp0, d, pv);
- fv.touch(p);
- if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); }
- state.rp1 = state.rp0;
- state.rp2 = pi;
- if (a) { state.rp0 = pi; }
- }
- // ALIGNRP[] Align to reference point.
- // 0x3C
- function ALIGNRP(state) {
- var stack = state.stack;
- var rp0i = state.rp0;
- var rp0 = state.z0[rp0i];
- var loop = state.loop;
- var fv = state.fv;
- var pv = state.pv;
- var z1 = state.z1;
- while (loop--) {
- var pi = stack.pop();
- var p = z1[pi];
- if (exports.DEBUG) {
- console.log(
- state.step,
- (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') +
- 'ALIGNRP[]', pi
- );
- }
- fv.setRelative(p, rp0, 0, pv);
- fv.touch(p);
- }
- state.loop = 1;
- }
- // RTG[] Round To Double Grid
- // 0x3D
- function RTDG(state) {
- if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); }
- state.round = roundToDoubleGrid;
- }
- // MIAP[a] Move Indirect Absolute Point
- // 0x3E-0x3F
- function MIAP(round, state) {
- var stack = state.stack;
- var n = stack.pop();
- var pi = stack.pop();
- var p = state.z0[pi];
- var fv = state.fv;
- var pv = state.pv;
- var cv = state.cvt[n];
- if (exports.DEBUG) {
- console.log(
- state.step,
- 'MIAP[' + round + ']',
- n, '(', cv, ')', pi
- );
- }
- var d = pv.distance(p, HPZero);
- if (round) {
- if (Math.abs(d - cv) < state.cvCutIn) { d = cv; }
- d = state.round(d);
- }
- fv.setRelative(p, HPZero, d, pv);
- if (state.zp0 === 0) {
- p.xo = p.x;
- p.yo = p.y;
- }
- fv.touch(p);
- state.rp0 = state.rp1 = pi;
- }
- // NPUSB[] PUSH N Bytes
- // 0x40
- function NPUSHB(state) {
- var prog = state.prog;
- var ip = state.ip;
- var stack = state.stack;
- var n = prog[++ip];
- if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); }
- for (var i = 0; i < n; i++) { stack.push(prog[++ip]); }
- state.ip = ip;
- }
- // NPUSHW[] PUSH N Words
- // 0x41
- function NPUSHW(state) {
- var ip = state.ip;
- var prog = state.prog;
- var stack = state.stack;
- var n = prog[++ip];
- if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); }
- for (var i = 0; i < n; i++) {
- var w = (prog[++ip] << 8) | prog[++ip];
- if (w & 0x8000) { w = -((w ^ 0xffff) + 1); }
- stack.push(w);
- }
- state.ip = ip;
- }
- // WS[] Write Store
- // 0x42
- function WS(state) {
- var stack = state.stack;
- var store = state.store;
- if (!store) { store = state.store = []; }
- var v = stack.pop();
- var l = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'WS', v, l); }
- store[l] = v;
- }
- // RS[] Read Store
- // 0x43
- function RS(state) {
- var stack = state.stack;
- var store = state.store;
- var l = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'RS', l); }
- var v = (store && store[l]) || 0;
- stack.push(v);
- }
- // WCVTP[] Write Control Value Table in Pixel units
- // 0x44
- function WCVTP(state) {
- var stack = state.stack;
- var v = stack.pop();
- var l = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); }
- state.cvt[l] = v / 0x40;
- }
- // RCVT[] Read Control Value Table entry
- // 0x45
- function RCVT(state) {
- var stack = state.stack;
- var cvte = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); }
- stack.push(state.cvt[cvte] * 0x40);
- }
- // GC[] Get Coordinate projected onto the projection vector
- // 0x46-0x47
- function GC(a, state) {
- var stack = state.stack;
- var pi = stack.pop();
- var p = state.z2[pi];
- if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); }
- stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40);
- }
- // MD[a] Measure Distance
- // 0x49-0x4A
- function MD(a, state) {
- var stack = state.stack;
- var pi2 = stack.pop();
- var pi1 = stack.pop();
- var p2 = state.z1[pi2];
- var p1 = state.z0[pi1];
- var d = state.dpv.distance(p1, p2, a, a);
- if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); }
- state.stack.push(Math.round(d * 64));
- }
- // MPPEM[] Measure Pixels Per EM
- // 0x4B
- function MPPEM(state) {
- if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); }
- state.stack.push(state.ppem);
- }
- // FLIPON[] set the auto FLIP Boolean to ON
- // 0x4D
- function FLIPON(state) {
- if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); }
- state.autoFlip = true;
- }
- // LT[] Less Than
- // 0x50
- function LT(state) {
- var stack = state.stack;
- var e2 = stack.pop();
- var e1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); }
- stack.push(e1 < e2 ? 1 : 0);
- }
- // LTEQ[] Less Than or EQual
- // 0x53
- function LTEQ(state) {
- var stack = state.stack;
- var e2 = stack.pop();
- var e1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); }
- stack.push(e1 <= e2 ? 1 : 0);
- }
- // GTEQ[] Greater Than
- // 0x52
- function GT(state) {
- var stack = state.stack;
- var e2 = stack.pop();
- var e1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); }
- stack.push(e1 > e2 ? 1 : 0);
- }
- // GTEQ[] Greater Than or EQual
- // 0x53
- function GTEQ(state) {
- var stack = state.stack;
- var e2 = stack.pop();
- var e1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); }
- stack.push(e1 >= e2 ? 1 : 0);
- }
- // EQ[] EQual
- // 0x54
- function EQ(state) {
- var stack = state.stack;
- var e2 = stack.pop();
- var e1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); }
- stack.push(e2 === e1 ? 1 : 0);
- }
- // NEQ[] Not EQual
- // 0x55
- function NEQ(state) {
- var stack = state.stack;
- var e2 = stack.pop();
- var e1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); }
- stack.push(e2 !== e1 ? 1 : 0);
- }
- // ODD[] ODD
- // 0x56
- function ODD(state) {
- var stack = state.stack;
- var n = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); }
- stack.push(Math.trunc(n) % 2 ? 1 : 0);
- }
- // EVEN[] EVEN
- // 0x57
- function EVEN(state) {
- var stack = state.stack;
- var n = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); }
- stack.push(Math.trunc(n) % 2 ? 0 : 1);
- }
- // IF[] IF test
- // 0x58
- function IF(state) {
- var test = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'IF[]', test); }
- // if test is true it just continues
- // if not the ip is skipped until matching ELSE or EIF
- if (!test) {
- skip(state, true);
- if (exports.DEBUG) { console.log(state.step, 'EIF[]'); }
- }
- }
- // EIF[] End IF
- // 0x59
- function EIF(state) {
- // this can be reached normally when
- // executing an else branch.
- // -> just ignore it
- if (exports.DEBUG) { console.log(state.step, 'EIF[]'); }
- }
- // AND[] logical AND
- // 0x5A
- function AND(state) {
- var stack = state.stack;
- var e2 = stack.pop();
- var e1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); }
- stack.push(e2 && e1 ? 1 : 0);
- }
- // OR[] logical OR
- // 0x5B
- function OR(state) {
- var stack = state.stack;
- var e2 = stack.pop();
- var e1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); }
- stack.push(e2 || e1 ? 1 : 0);
- }
- // NOT[] logical NOT
- // 0x5C
- function NOT(state) {
- var stack = state.stack;
- var e = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); }
- stack.push(e ? 0 : 1);
- }
- // DELTAP1[] DELTA exception P1
- // DELTAP2[] DELTA exception P2
- // DELTAP3[] DELTA exception P3
- // 0x5D, 0x71, 0x72
- function DELTAP123(b, state) {
- var stack = state.stack;
- var n = stack.pop();
- var fv = state.fv;
- var pv = state.pv;
- var ppem = state.ppem;
- var base = state.deltaBase + (b - 1) * 16;
- var ds = state.deltaShift;
- var z0 = state.z0;
- if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); }
- for (var i = 0; i < n; i++) {
- var pi = stack.pop();
- var arg = stack.pop();
- var appem = base + ((arg & 0xF0) >> 4);
- if (appem !== ppem) { continue; }
- var mag = (arg & 0x0F) - 8;
- if (mag >= 0) { mag++; }
- if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); }
- var p = z0[pi];
- fv.setRelative(p, p, mag * ds, pv);
- }
- }
- // SDB[] Set Delta Base in the graphics state
- // 0x5E
- function SDB(state) {
- var stack = state.stack;
- var n = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); }
- state.deltaBase = n;
- }
- // SDS[] Set Delta Shift in the graphics state
- // 0x5F
- function SDS(state) {
- var stack = state.stack;
- var n = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); }
- state.deltaShift = Math.pow(0.5, n);
- }
- // ADD[] ADD
- // 0x60
- function ADD(state) {
- var stack = state.stack;
- var n2 = stack.pop();
- var n1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); }
- stack.push(n1 + n2);
- }
- // SUB[] SUB
- // 0x61
- function SUB(state) {
- var stack = state.stack;
- var n2 = stack.pop();
- var n1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); }
- stack.push(n1 - n2);
- }
- // DIV[] DIV
- // 0x62
- function DIV(state) {
- var stack = state.stack;
- var n2 = stack.pop();
- var n1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); }
- stack.push(n1 * 64 / n2);
- }
- // MUL[] MUL
- // 0x63
- function MUL(state) {
- var stack = state.stack;
- var n2 = stack.pop();
- var n1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); }
- stack.push(n1 * n2 / 64);
- }
- // ABS[] ABSolute value
- // 0x64
- function ABS(state) {
- var stack = state.stack;
- var n = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); }
- stack.push(Math.abs(n));
- }
- // NEG[] NEGate
- // 0x65
- function NEG(state) {
- var stack = state.stack;
- var n = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); }
- stack.push(-n);
- }
- // FLOOR[] FLOOR
- // 0x66
- function FLOOR(state) {
- var stack = state.stack;
- var n = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); }
- stack.push(Math.floor(n / 0x40) * 0x40);
- }
- // CEILING[] CEILING
- // 0x67
- function CEILING(state) {
- var stack = state.stack;
- var n = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); }
- stack.push(Math.ceil(n / 0x40) * 0x40);
- }
- // ROUND[ab] ROUND value
- // 0x68-0x6B
- function ROUND(dt, state) {
- var stack = state.stack;
- var n = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); }
- stack.push(state.round(n / 0x40) * 0x40);
- }
- // WCVTF[] Write Control Value Table in Funits
- // 0x70
- function WCVTF(state) {
- var stack = state.stack;
- var v = stack.pop();
- var l = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); }
- state.cvt[l] = v * state.ppem / state.font.unitsPerEm;
- }
- // DELTAC1[] DELTA exception C1
- // DELTAC2[] DELTA exception C2
- // DELTAC3[] DELTA exception C3
- // 0x73, 0x74, 0x75
- function DELTAC123(b, state) {
- var stack = state.stack;
- var n = stack.pop();
- var ppem = state.ppem;
- var base = state.deltaBase + (b - 1) * 16;
- var ds = state.deltaShift;
- if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); }
- for (var i = 0; i < n; i++) {
- var c = stack.pop();
- var arg = stack.pop();
- var appem = base + ((arg & 0xF0) >> 4);
- if (appem !== ppem) { continue; }
- var mag = (arg & 0x0F) - 8;
- if (mag >= 0) { mag++; }
- var delta = mag * ds;
- if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); }
- state.cvt[c] += delta;
- }
- }
- // SROUND[] Super ROUND
- // 0x76
- function SROUND(state) {
- var n = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); }
- state.round = roundSuper;
- var period;
- switch (n & 0xC0) {
- case 0x00:
- period = 0.5;
- break;
- case 0x40:
- period = 1;
- break;
- case 0x80:
- period = 2;
- break;
- default:
- throw new Error('invalid SROUND value');
- }
- state.srPeriod = period;
- switch (n & 0x30) {
- case 0x00:
- state.srPhase = 0;
- break;
- case 0x10:
- state.srPhase = 0.25 * period;
- break;
- case 0x20:
- state.srPhase = 0.5 * period;
- break;
- case 0x30:
- state.srPhase = 0.75 * period;
- break;
- default: throw new Error('invalid SROUND value');
- }
- n &= 0x0F;
- if (n === 0) { state.srThreshold = 0; }
- else { state.srThreshold = (n / 8 - 0.5) * period; }
- }
- // S45ROUND[] Super ROUND 45 degrees
- // 0x77
- function S45ROUND(state) {
- var n = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); }
- state.round = roundSuper;
- var period;
- switch (n & 0xC0) {
- case 0x00:
- period = Math.sqrt(2) / 2;
- break;
- case 0x40:
- period = Math.sqrt(2);
- break;
- case 0x80:
- period = 2 * Math.sqrt(2);
- break;
- default:
- throw new Error('invalid S45ROUND value');
- }
- state.srPeriod = period;
- switch (n & 0x30) {
- case 0x00:
- state.srPhase = 0;
- break;
- case 0x10:
- state.srPhase = 0.25 * period;
- break;
- case 0x20:
- state.srPhase = 0.5 * period;
- break;
- case 0x30:
- state.srPhase = 0.75 * period;
- break;
- default:
- throw new Error('invalid S45ROUND value');
- }
- n &= 0x0F;
- if (n === 0) { state.srThreshold = 0; }
- else { state.srThreshold = (n / 8 - 0.5) * period; }
- }
- // ROFF[] Round Off
- // 0x7A
- function ROFF(state) {
- if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); }
- state.round = roundOff;
- }
- // RUTG[] Round Up To Grid
- // 0x7C
- function RUTG(state) {
- if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); }
- state.round = roundUpToGrid;
- }
- // RDTG[] Round Down To Grid
- // 0x7D
- function RDTG(state) {
- if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); }
- state.round = roundDownToGrid;
- }
- // SCANCTRL[] SCAN conversion ConTRoL
- // 0x85
- function SCANCTRL(state) {
- var n = state.stack.pop();
- // ignored by opentype.js
- if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); }
- }
- // SDPVTL[a] Set Dual Projection Vector To Line
- // 0x86-0x87
- function SDPVTL(a, state) {
- var stack = state.stack;
- var p2i = stack.pop();
- var p1i = stack.pop();
- var p2 = state.z2[p2i];
- var p1 = state.z1[p1i];
- if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); }
- var dx;
- var dy;
- if (!a) {
- dx = p1.x - p2.x;
- dy = p1.y - p2.y;
- } else {
- dx = p2.y - p1.y;
- dy = p1.x - p2.x;
- }
- state.dpv = getUnitVector(dx, dy);
- }
- // GETINFO[] GET INFOrmation
- // 0x88
- function GETINFO(state) {
- var stack = state.stack;
- var sel = stack.pop();
- var r = 0;
- if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); }
- // v35 as in no subpixel hinting
- if (sel & 0x01) { r = 35; }
- // TODO rotation and stretch currently not supported
- // and thus those GETINFO are always 0.
- // opentype.js is always gray scaling
- if (sel & 0x20) { r |= 0x1000; }
- stack.push(r);
- }
- // ROLL[] ROLL the top three stack elements
- // 0x8A
- function ROLL(state) {
- var stack = state.stack;
- var a = stack.pop();
- var b = stack.pop();
- var c = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); }
- stack.push(b);
- stack.push(a);
- stack.push(c);
- }
- // MAX[] MAXimum of top two stack elements
- // 0x8B
- function MAX(state) {
- var stack = state.stack;
- var e2 = stack.pop();
- var e1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); }
- stack.push(Math.max(e1, e2));
- }
- // MIN[] MINimum of top two stack elements
- // 0x8C
- function MIN(state) {
- var stack = state.stack;
- var e2 = stack.pop();
- var e1 = stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); }
- stack.push(Math.min(e1, e2));
- }
- // SCANTYPE[] SCANTYPE
- // 0x8D
- function SCANTYPE(state) {
- var n = state.stack.pop();
- // ignored by opentype.js
- if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); }
- }
- // INSTCTRL[] INSTCTRL
- // 0x8D
- function INSTCTRL(state) {
- var s = state.stack.pop();
- var v = state.stack.pop();
- if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); }
- switch (s) {
- case 1 : state.inhibitGridFit = !!v; return;
- case 2 : state.ignoreCvt = !!v; return;
- default: throw new Error('invalid INSTCTRL[] selector');
- }
- }
- // PUSHB[abc] PUSH Bytes
- // 0xB0-0xB7
- function PUSHB(n, state) {
- var stack = state.stack;
- var prog = state.prog;
- var ip = state.ip;
- if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); }
- for (var i = 0; i < n; i++) { stack.push(prog[++ip]); }
- state.ip = ip;
- }
- // PUSHW[abc] PUSH Words
- // 0xB8-0xBF
- function PUSHW(n, state) {
- var ip = state.ip;
- var prog = state.prog;
- var stack = state.stack;
- if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); }
- for (var i = 0; i < n; i++) {
- var w = (prog[++ip] << 8) | prog[++ip];
- if (w & 0x8000) { w = -((w ^ 0xffff) + 1); }
- stack.push(w);
- }
- state.ip = ip;
- }
- // MDRP[abcde] Move Direct Relative Point
- // 0xD0-0xEF
- // (if indirect is 0)
- //
- // and
- //
- // MIRP[abcde] Move Indirect Relative Point
- // 0xE0-0xFF
- // (if indirect is 1)
- function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) {
- var stack = state.stack;
- var cvte = indirect && stack.pop();
- var pi = stack.pop();
- var rp0i = state.rp0;
- var rp = state.z0[rp0i];
- var p = state.z1[pi];
- var md = state.minDis;
- var fv = state.fv;
- var pv = state.dpv;
- var od; // original distance
- var d; // moving distance
- var sign; // sign of distance
- var cv;
- d = od = pv.distance(p, rp, true, true);
- sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0
- // TODO consider autoFlip
- d = Math.abs(d);
- if (indirect) {
- cv = state.cvt[cvte];
- if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; }
- }
- if (keepD && d < md) { d = md; }
- if (ro) { d = state.round(d); }
- fv.setRelative(p, rp, sign * d, pv);
- fv.touch(p);
- if (exports.DEBUG) {
- console.log(
- state.step,
- (indirect ? 'MIRP[' : 'MDRP[') +
- (setRp0 ? 'M' : 'm') +
- (keepD ? '>' : '_') +
- (ro ? 'R' : '_') +
- (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) +
- ']',
- indirect ?
- cvte + '(' + state.cvt[cvte] + ',' + cv + ')' :
- '',
- pi,
- '(d =', od, '->', sign * d, ')'
- );
- }
- state.rp1 = state.rp0;
- state.rp2 = pi;
- if (setRp0) { state.rp0 = pi; }
- }
- /*
- * The instruction table.
- */
- instructionTable = [
- /* 0x00 */ SVTCA.bind(undefined, yUnitVector),
- /* 0x01 */ SVTCA.bind(undefined, xUnitVector),
- /* 0x02 */ SPVTCA.bind(undefined, yUnitVector),
- /* 0x03 */ SPVTCA.bind(undefined, xUnitVector),
- /* 0x04 */ SFVTCA.bind(undefined, yUnitVector),
- /* 0x05 */ SFVTCA.bind(undefined, xUnitVector),
- /* 0x06 */ SPVTL.bind(undefined, 0),
- /* 0x07 */ SPVTL.bind(undefined, 1),
- /* 0x08 */ SFVTL.bind(undefined, 0),
- /* 0x09 */ SFVTL.bind(undefined, 1),
- /* 0x0A */ SPVFS,
- /* 0x0B */ SFVFS,
- /* 0x0C */ GPV,
- /* 0x0D */ GFV,
- /* 0x0E */ SFVTPV,
- /* 0x0F */ ISECT,
- /* 0x10 */ SRP0,
- /* 0x11 */ SRP1,
- /* 0x12 */ SRP2,
- /* 0x13 */ SZP0,
- /* 0x14 */ SZP1,
- /* 0x15 */ SZP2,
- /* 0x16 */ SZPS,
- /* 0x17 */ SLOOP,
- /* 0x18 */ RTG,
- /* 0x19 */ RTHG,
- /* 0x1A */ SMD,
- /* 0x1B */ ELSE,
- /* 0x1C */ JMPR,
- /* 0x1D */ SCVTCI,
- /* 0x1E */ undefined, // TODO SSWCI
- /* 0x1F */ undefined, // TODO SSW
- /* 0x20 */ DUP,
- /* 0x21 */ POP,
- /* 0x22 */ CLEAR,
- /* 0x23 */ SWAP,
- /* 0x24 */ DEPTH,
- /* 0x25 */ CINDEX,
- /* 0x26 */ MINDEX,
- /* 0x27 */ undefined, // TODO ALIGNPTS
- /* 0x28 */ undefined,
- /* 0x29 */ undefined, // TODO UTP
- /* 0x2A */ LOOPCALL,
- /* 0x2B */ CALL,
- /* 0x2C */ FDEF,
- /* 0x2D */ undefined, // ENDF (eaten by FDEF)
- /* 0x2E */ MDAP.bind(undefined, 0),
- /* 0x2F */ MDAP.bind(undefined, 1),
- /* 0x30 */ IUP.bind(undefined, yUnitVector),
- /* 0x31 */ IUP.bind(undefined, xUnitVector),
- /* 0x32 */ SHP.bind(undefined, 0),
- /* 0x33 */ SHP.bind(undefined, 1),
- /* 0x34 */ SHC.bind(undefined, 0),
- /* 0x35 */ SHC.bind(undefined, 1),
- /* 0x36 */ SHZ.bind(undefined, 0),
- /* 0x37 */ SHZ.bind(undefined, 1),
- /* 0x38 */ SHPIX,
- /* 0x39 */ IP,
- /* 0x3A */ MSIRP.bind(undefined, 0),
- /* 0x3B */ MSIRP.bind(undefined, 1),
- /* 0x3C */ ALIGNRP,
- /* 0x3D */ RTDG,
- /* 0x3E */ MIAP.bind(undefined, 0),
- /* 0x3F */ MIAP.bind(undefined, 1),
- /* 0x40 */ NPUSHB,
- /* 0x41 */ NPUSHW,
- /* 0x42 */ WS,
- /* 0x43 */ RS,
- /* 0x44 */ WCVTP,
- /* 0x45 */ RCVT,
- /* 0x46 */ GC.bind(undefined, 0),
- /* 0x47 */ GC.bind(undefined, 1),
- /* 0x48 */ undefined, // TODO SCFS
- /* 0x49 */ MD.bind(undefined, 0),
- /* 0x4A */ MD.bind(undefined, 1),
- /* 0x4B */ MPPEM,
- /* 0x4C */ undefined, // TODO MPS
- /* 0x4D */ FLIPON,
- /* 0x4E */ undefined, // TODO FLIPOFF
- /* 0x4F */ undefined, // TODO DEBUG
- /* 0x50 */ LT,
- /* 0x51 */ LTEQ,
- /* 0x52 */ GT,
- /* 0x53 */ GTEQ,
- /* 0x54 */ EQ,
- /* 0x55 */ NEQ,
- /* 0x56 */ ODD,
- /* 0x57 */ EVEN,
- /* 0x58 */ IF,
- /* 0x59 */ EIF,
- /* 0x5A */ AND,
- /* 0x5B */ OR,
- /* 0x5C */ NOT,
- /* 0x5D */ DELTAP123.bind(undefined, 1),
- /* 0x5E */ SDB,
- /* 0x5F */ SDS,
- /* 0x60 */ ADD,
- /* 0x61 */ SUB,
- /* 0x62 */ DIV,
- /* 0x63 */ MUL,
- /* 0x64 */ ABS,
- /* 0x65 */ NEG,
- /* 0x66 */ FLOOR,
- /* 0x67 */ CEILING,
- /* 0x68 */ ROUND.bind(undefined, 0),
- /* 0x69 */ ROUND.bind(undefined, 1),
- /* 0x6A */ ROUND.bind(undefined, 2),
- /* 0x6B */ ROUND.bind(undefined, 3),
- /* 0x6C */ undefined, // TODO NROUND[ab]
- /* 0x6D */ undefined, // TODO NROUND[ab]
- /* 0x6E */ undefined, // TODO NROUND[ab]
- /* 0x6F */ undefined, // TODO NROUND[ab]
- /* 0x70 */ WCVTF,
- /* 0x71 */ DELTAP123.bind(undefined, 2),
- /* 0x72 */ DELTAP123.bind(undefined, 3),
- /* 0x73 */ DELTAC123.bind(undefined, 1),
- /* 0x74 */ DELTAC123.bind(undefined, 2),
- /* 0x75 */ DELTAC123.bind(undefined, 3),
- /* 0x76 */ SROUND,
- /* 0x77 */ S45ROUND,
- /* 0x78 */ undefined, // TODO JROT[]
- /* 0x79 */ undefined, // TODO JROF[]
- /* 0x7A */ ROFF,
- /* 0x7B */ undefined,
- /* 0x7C */ RUTG,
- /* 0x7D */ RDTG,
- /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though
- /* 0x7F */ POP, // actually AA, supposed to do only a pop though
- /* 0x80 */ undefined, // TODO FLIPPT
- /* 0x81 */ undefined, // TODO FLIPRGON
- /* 0x82 */ undefined, // TODO FLIPRGOFF
- /* 0x83 */ undefined,
- /* 0x84 */ undefined,
- /* 0x85 */ SCANCTRL,
- /* 0x86 */ SDPVTL.bind(undefined, 0),
- /* 0x87 */ SDPVTL.bind(undefined, 1),
- /* 0x88 */ GETINFO,
- /* 0x89 */ undefined, // TODO IDEF
- /* 0x8A */ ROLL,
- /* 0x8B */ MAX,
- /* 0x8C */ MIN,
- /* 0x8D */ SCANTYPE,
- /* 0x8E */ INSTCTRL,
- /* 0x8F */ undefined,
- /* 0x90 */ undefined,
- /* 0x91 */ undefined,
- /* 0x92 */ undefined,
- /* 0x93 */ undefined,
- /* 0x94 */ undefined,
- /* 0x95 */ undefined,
- /* 0x96 */ undefined,
- /* 0x97 */ undefined,
- /* 0x98 */ undefined,
- /* 0x99 */ undefined,
- /* 0x9A */ undefined,
- /* 0x9B */ undefined,
- /* 0x9C */ undefined,
- /* 0x9D */ undefined,
- /* 0x9E */ undefined,
- /* 0x9F */ undefined,
- /* 0xA0 */ undefined,
- /* 0xA1 */ undefined,
- /* 0xA2 */ undefined,
- /* 0xA3 */ undefined,
- /* 0xA4 */ undefined,
- /* 0xA5 */ undefined,
- /* 0xA6 */ undefined,
- /* 0xA7 */ undefined,
- /* 0xA8 */ undefined,
- /* 0xA9 */ undefined,
- /* 0xAA */ undefined,
- /* 0xAB */ undefined,
- /* 0xAC */ undefined,
- /* 0xAD */ undefined,
- /* 0xAE */ undefined,
- /* 0xAF */ undefined,
- /* 0xB0 */ PUSHB.bind(undefined, 1),
- /* 0xB1 */ PUSHB.bind(undefined, 2),
- /* 0xB2 */ PUSHB.bind(undefined, 3),
- /* 0xB3 */ PUSHB.bind(undefined, 4),
- /* 0xB4 */ PUSHB.bind(undefined, 5),
- /* 0xB5 */ PUSHB.bind(undefined, 6),
- /* 0xB6 */ PUSHB.bind(undefined, 7),
- /* 0xB7 */ PUSHB.bind(undefined, 8),
- /* 0xB8 */ PUSHW.bind(undefined, 1),
- /* 0xB9 */ PUSHW.bind(undefined, 2),
- /* 0xBA */ PUSHW.bind(undefined, 3),
- /* 0xBB */ PUSHW.bind(undefined, 4),
- /* 0xBC */ PUSHW.bind(undefined, 5),
- /* 0xBD */ PUSHW.bind(undefined, 6),
- /* 0xBE */ PUSHW.bind(undefined, 7),
- /* 0xBF */ PUSHW.bind(undefined, 8),
- /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0),
- /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1),
- /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2),
- /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3),
- /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0),
- /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1),
- /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2),
- /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3),
- /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0),
- /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1),
- /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2),
- /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3),
- /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0),
- /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1),
- /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2),
- /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3),
- /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0),
- /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1),
- /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2),
- /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3),
- /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0),
- /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1),
- /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2),
- /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3),
- /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0),
- /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1),
- /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2),
- /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3),
- /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0),
- /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1),
- /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2),
- /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3),
- /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0),
- /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1),
- /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2),
- /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3),
- /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0),
- /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1),
- /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2),
- /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3),
- /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0),
- /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1),
- /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2),
- /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3),
- /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0),
- /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1),
- /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2),
- /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3),
- /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0),
- /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1),
- /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2),
- /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3),
- /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0),
- /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1),
- /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2),
- /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3),
- /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0),
- /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1),
- /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2),
- /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3),
- /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0),
- /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1),
- /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2),
- /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3)
- ];
- /*****************************
- Mathematical Considerations
- ******************************
- fv ... refers to freedom vector
- pv ... refers to projection vector
- rp ... refers to reference point
- p ... refers to to point being operated on
- d ... refers to distance
- SETRELATIVE:
- ============
- case freedom vector == x-axis:
- ------------------------------
- (pv)
- .-'
- rpd .-'
- .-*
- d .-'90°'
- .-' '
- .-' '
- *-' ' b
- rp '
- '
- '
- p *----------*-------------- (fv)
- pm
- rpdx = rpx + d * pv.x
- rpdy = rpy + d * pv.y
- equation of line b
- y - rpdy = pvns * (x- rpdx)
- y = p.y
- x = rpdx + ( p.y - rpdy ) / pvns
- case freedom vector == y-axis:
- ------------------------------
- * pm
- |\
- | \
- | \
- | \
- | \
- | \
- | \
- | \
- | \
- | \ b
- | \
- | \
- | \ .-' (pv)
- | 90° \.-'
- | .-'* rpd
- | .-'
- * *-' d
- p rp
- rpdx = rpx + d * pv.x
- rpdy = rpy + d * pv.y
- equation of line b:
- pvns ... normal slope to pv
- y - rpdy = pvns * (x - rpdx)
- x = p.x
- y = rpdy + pvns * (p.x - rpdx)
- generic case:
- -------------
- .'(fv)
- .'
- .* pm
- .' !
- .' .
- .' !
- .' . b
- .' !
- * .
- p !
- 90° . ... (pv)
- ...-*-'''
- ...---''' rpd
- ...---''' d
- *--'''
- rp
- rpdx = rpx + d * pv.x
- rpdy = rpy + d * pv.y
- equation of line b:
- pvns... normal slope to pv
- y - rpdy = pvns * (x - rpdx)
- equation of freedom vector line:
- fvs ... slope of freedom vector (=fy/fx)
- y - py = fvs * (x - px)
- on pm both equations are true for same x/y
- y - rpdy = pvns * (x - rpdx)
- y - py = fvs * (x - px)
- form to y and set equal:
- pvns * (x - rpdx) + rpdy = fvs * (x - px) + py
- expand:
- pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py
- switch:
- fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy
- solve for x:
- fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy
- fvs * px - pvns * rpdx + rpdy - py
- x = -----------------------------------
- fvs - pvns
- and:
- y = fvs * (x - px) + py
- INTERPOLATE:
- ============
- Examples of point interpolation.
- The weight of the movement of the reference point gets bigger
- the further the other reference point is away, thus the safest
- option (that is avoiding 0/0 divisions) is to weight the
- original distance of the other point by the sum of both distances.
- If the sum of both distances is 0, then move the point by the
- arithmetic average of the movement of both reference points.
- (+6)
- rp1o *---->*rp1
- . . (+12)
- . . rp2o *---------->* rp2
- . . . .
- . . . .
- . 10 20 . .
- |.........|...................| .
- . . .
- . . (+8) .
- po *------>*p .
- . . .
- . 12 . 24 .
- |...........|.......................|
- 36
- -------
- (+10)
- rp1o *-------->*rp1
- . . (-10)
- . . rp2 *<---------* rpo2
- . . . .
- . . . .
- . 10 . 30 . .
- |.........|.............................|
- . .
- . (+5) .
- po *--->* p .
- . . .
- . . 20 .
- |....|..............|
- 5 15
- -------
- (+10)
- rp1o *-------->*rp1
- . .
- . .
- rp2o *-------->*rp2
- (+10)
- po *-------->* p
- -------
- (+10)
- rp1o *-------->*rp1
- . .
- . .(+30)
- rp2o *---------------------------->*rp2
- (+25)
- po *----------------------->* p
- vim: set ts=4 sw=4 expandtab:
- *****/
- /**
- * Converts a string into a list of tokens.
- */
- /**
- * Create a new token
- * @param {string} char a single char
- */
- function Token(char) {
- this.char = char;
- this.state = {};
- this.activeState = null;
- }
- /**
- * Create a new context range
- * @param {number} startIndex range start index
- * @param {number} endOffset range end index offset
- * @param {string} contextName owner context name
- */
- function ContextRange(startIndex, endOffset, contextName) {
- this.contextName = contextName;
- this.startIndex = startIndex;
- this.endOffset = endOffset;
- }
- /**
- * Check context start and end
- * @param {string} contextName a unique context name
- * @param {function} checkStart a predicate function the indicates a context's start
- * @param {function} checkEnd a predicate function the indicates a context's end
- */
- function ContextChecker(contextName, checkStart, checkEnd) {
- this.contextName = contextName;
- this.openRange = null;
- this.ranges = [];
- this.checkStart = checkStart;
- this.checkEnd = checkEnd;
- }
- /**
- * @typedef ContextParams
- * @type Object
- * @property {array} context context items
- * @property {number} currentIndex current item index
- */
- /**
- * Create a context params
- * @param {array} context a list of items
- * @param {number} currentIndex current item index
- */
- function ContextParams(context, currentIndex) {
- this.context = context;
- this.index = currentIndex;
- this.length = context.length;
- this.current = context[currentIndex];
- this.backtrack = context.slice(0, currentIndex);
- this.lookahead = context.slice(currentIndex + 1);
- }
- /**
- * Create an event instance
- * @param {string} eventId event unique id
- */
- function Event(eventId) {
- this.eventId = eventId;
- this.subscribers = [];
- }
- /**
- * Initialize a core events and auto subscribe required event handlers
- * @param {any} events an object that enlists core events handlers
- */
- function initializeCoreEvents(events) {
- var this$1 = this;
- var coreEvents = [
- 'start', 'end', 'next', 'newToken', 'contextStart',
- 'contextEnd', 'insertToken', 'removeToken', 'removeRange',
- 'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges'
- ];
- coreEvents.forEach(function (eventId) {
- Object.defineProperty(this$1.events, eventId, {
- value: new Event(eventId)
- });
- });
- if (!!events) {
- coreEvents.forEach(function (eventId) {
- var event = events[eventId];
- if (typeof event === 'function') {
- this$1.events[eventId].subscribe(event);
- }
- });
- }
- var requiresContextUpdate = [
- 'insertToken', 'removeToken', 'removeRange',
- 'replaceToken', 'replaceRange', 'composeRUD'
- ];
- requiresContextUpdate.forEach(function (eventId) {
- this$1.events[eventId].subscribe(
- this$1.updateContextsRanges
- );
- });
- }
- /**
- * Converts a string into a list of tokens
- * @param {any} events tokenizer core events
- */
- function Tokenizer(events) {
- this.tokens = [];
- this.registeredContexts = {};
- this.contextCheckers = [];
- this.events = {};
- this.registeredModifiers = [];
- initializeCoreEvents.call(this, events);
- }
- /**
- * Sets the state of a token, usually called by a state modifier.
- * @param {string} key state item key
- * @param {any} value state item value
- */
- Token.prototype.setState = function(key, value) {
- this.state[key] = value;
- this.activeState = { key: key, value: this.state[key] };
- return this.activeState;
- };
- Token.prototype.getState = function (stateId) {
- return this.state[stateId] || null;
- };
- /**
- * Checks if an index exists in the tokens list.
- * @param {number} index token index
- */
- Tokenizer.prototype.inboundIndex = function(index) {
- return index >= 0 && index < this.tokens.length;
- };
- /**
- * Compose and apply a list of operations (replace, update, delete)
- * @param {array} RUDs replace, update and delete operations
- * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once)
- */
- Tokenizer.prototype.composeRUD = function (RUDs) {
- var this$1 = this;
- var silent = true;
- var state = RUDs.map(function (RUD) { return (
- this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent))
- ); });
- var hasFAILObject = function (obj) { return (
- typeof obj === 'object' &&
- obj.hasOwnProperty('FAIL')
- ); };
- if (state.every(hasFAILObject)) {
- return {
- FAIL: "composeRUD: one or more operations hasn't completed successfully",
- report: state.filter(hasFAILObject)
- };
- }
- this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]);
- };
- /**
- * Replace a range of tokens with a list of tokens
- * @param {number} startIndex range start index
- * @param {number} offset range offset
- * @param {token} tokens a list of tokens to replace
- * @param {boolean} silent dispatch events and update context ranges
- */
- Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) {
- offset = offset !== null ? offset : this.tokens.length;
- var isTokenType = tokens.every(function (token) { return token instanceof Token; });
- if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) {
- var replaced = this.tokens.splice.apply(
- this.tokens, [startIndex, offset].concat(tokens)
- );
- if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); }
- return [replaced, tokens];
- } else {
- return { FAIL: 'replaceRange: invalid tokens or startIndex.' };
- }
- };
- /**
- * Replace a token with another token
- * @param {number} index token index
- * @param {token} token a token to replace
- * @param {boolean} silent dispatch events and update context ranges
- */
- Tokenizer.prototype.replaceToken = function (index, token, silent) {
- if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) {
- var replaced = this.tokens.splice(index, 1, token);
- if (!silent) { this.dispatch('replaceToken', [index, token]); }
- return [replaced[0], token];
- } else {
- return { FAIL: 'replaceToken: invalid token or index.' };
- }
- };
- /**
- * Removes a range of tokens
- * @param {number} startIndex range start index
- * @param {number} offset range offset
- * @param {boolean} silent dispatch events and update context ranges
- */
- Tokenizer.prototype.removeRange = function(startIndex, offset, silent) {
- offset = !isNaN(offset) ? offset : this.tokens.length;
- var tokens = this.tokens.splice(startIndex, offset);
- if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); }
- return tokens;
- };
- /**
- * Remove a token at a certain index
- * @param {number} index token index
- * @param {boolean} silent dispatch events and update context ranges
- */
- Tokenizer.prototype.removeToken = function(index, silent) {
- if (!isNaN(index) && this.inboundIndex(index)) {
- var token = this.tokens.splice(index, 1);
- if (!silent) { this.dispatch('removeToken', [token, index]); }
- return token;
- } else {
- return { FAIL: 'removeToken: invalid token index.' };
- }
- };
- /**
- * Insert a list of tokens at a certain index
- * @param {array} tokens a list of tokens to insert
- * @param {number} index insert the list of tokens at index
- * @param {boolean} silent dispatch events and update context ranges
- */
- Tokenizer.prototype.insertToken = function (tokens, index, silent) {
- var tokenType = tokens.every(
- function (token) { return token instanceof Token; }
- );
- if (tokenType) {
- this.tokens.splice.apply(
- this.tokens, [index, 0].concat(tokens)
- );
- if (!silent) { this.dispatch('insertToken', [tokens, index]); }
- return tokens;
- } else {
- return { FAIL: 'insertToken: invalid token(s).' };
- }
- };
- /**
- * A state modifier that is called on 'newToken' event
- * @param {string} modifierId state modifier id
- * @param {function} condition a predicate function that returns true or false
- * @param {function} modifier a function to update token state
- */
- Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) {
- this.events.newToken.subscribe(function(token, contextParams) {
- var conditionParams = [token, contextParams];
- var canApplyModifier = (
- condition === null ||
- condition.apply(this, conditionParams) === true
- );
- var modifierParams = [token, contextParams];
- if (canApplyModifier) {
- var newStateValue = modifier.apply(this, modifierParams);
- token.setState(modifierId, newStateValue);
- }
- });
- this.registeredModifiers.push(modifierId);
- };
- /**
- * Subscribe a handler to an event
- * @param {function} eventHandler an event handler function
- */
- Event.prototype.subscribe = function (eventHandler) {
- if (typeof eventHandler === 'function') {
- return ((this.subscribers.push(eventHandler)) - 1);
- } else {
- return { FAIL: ("invalid '" + (this.eventId) + "' event handler")};
- }
- };
- /**
- * Unsubscribe an event handler
- * @param {string} subsId subscription id
- */
- Event.prototype.unsubscribe = function (subsId) {
- this.subscribers.splice(subsId, 1);
- };
- /**
- * Sets context params current value index
- * @param {number} index context params current value index
- */
- ContextParams.prototype.setCurrentIndex = function(index) {
- this.index = index;
- this.current = this.context[index];
- this.backtrack = this.context.slice(0, index);
- this.lookahead = this.context.slice(index + 1);
- };
- /**
- * Get an item at an offset from the current value
- * example (current value is 3):
- * 1 2 [3] 4 5 | items values
- * -2 -1 0 1 2 | offset values
- * @param {number} offset an offset from current value index
- */
- ContextParams.prototype.get = function (offset) {
- switch (true) {
- case (offset === 0):
- return this.current;
- case (offset < 0 && Math.abs(offset) <= this.backtrack.length):
- return this.backtrack.slice(offset)[0];
- case (offset > 0 && offset <= this.lookahead.length):
- return this.lookahead[offset - 1];
- default:
- return null;
- }
- };
- /**
- * Converts a context range into a string value
- * @param {contextRange} range a context range
- */
- Tokenizer.prototype.rangeToText = function (range) {
- if (range instanceof ContextRange) {
- return (
- this.getRangeTokens(range)
- .map(function (token) { return token.char; }).join('')
- );
- }
- };
- /**
- * Converts all tokens into a string
- */
- Tokenizer.prototype.getText = function () {
- return this.tokens.map(function (token) { return token.char; }).join('');
- };
- /**
- * Get a context by name
- * @param {string} contextName context name to get
- */
- Tokenizer.prototype.getContext = function (contextName) {
- var context = this.registeredContexts[contextName];
- return !!context ? context : null;
- };
- /**
- * Subscribes a new event handler to an event
- * @param {string} eventName event name to subscribe to
- * @param {function} eventHandler a function to be invoked on event
- */
- Tokenizer.prototype.on = function(eventName, eventHandler) {
- var event = this.events[eventName];
- if (!!event) {
- return event.subscribe(eventHandler);
- } else {
- return null;
- }
- };
- /**
- * Dispatches an event
- * @param {string} eventName event name
- * @param {any} args event handler arguments
- */
- Tokenizer.prototype.dispatch = function(eventName, args) {
- var this$1 = this;
- var event = this.events[eventName];
- if (event instanceof Event) {
- event.subscribers.forEach(function (subscriber) {
- subscriber.apply(this$1, args || []);
- });
- }
- };
- /**
- * Register a new context checker
- * @param {string} contextName a unique context name
- * @param {function} contextStartCheck a predicate function that returns true on context start
- * @param {function} contextEndCheck a predicate function that returns true on context end
- * TODO: call tokenize on registration to update context ranges with the new context.
- */
- Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) {
- if (!!this.getContext(contextName)) { return {
- FAIL:
- ("context name '" + contextName + "' is already registered.")
- }; }
- if (typeof contextStartCheck !== 'function') { return {
- FAIL:
- "missing context start check."
- }; }
- if (typeof contextEndCheck !== 'function') { return {
- FAIL:
- "missing context end check."
- }; }
- var contextCheckers = new ContextChecker(
- contextName, contextStartCheck, contextEndCheck
- );
- this.registeredContexts[contextName] = contextCheckers;
- this.contextCheckers.push(contextCheckers);
- return contextCheckers;
- };
- /**
- * Gets a context range tokens
- * @param {contextRange} range a context range
- */
- Tokenizer.prototype.getRangeTokens = function(range) {
- var endIndex = range.startIndex + range.endOffset;
- return [].concat(
- this.tokens
- .slice(range.startIndex, endIndex)
- );
- };
- /**
- * Gets the ranges of a context
- * @param {string} contextName context name
- */
- Tokenizer.prototype.getContextRanges = function(contextName) {
- var context = this.getContext(contextName);
- if (!!context) {
- return context.ranges;
- } else {
- return { FAIL: ("context checker '" + contextName + "' is not registered.") };
- }
- };
- /**
- * Resets context ranges to run context update
- */
- Tokenizer.prototype.resetContextsRanges = function () {
- var registeredContexts = this.registeredContexts;
- for (var contextName in registeredContexts) {
- if (registeredContexts.hasOwnProperty(contextName)) {
- var context = registeredContexts[contextName];
- context.ranges = [];
- }
- }
- };
- /**
- * Updates context ranges
- */
- Tokenizer.prototype.updateContextsRanges = function () {
- this.resetContextsRanges();
- var chars = this.tokens.map(function (token) { return token.char; });
- for (var i = 0; i < chars.length; i++) {
- var contextParams = new ContextParams(chars, i);
- this.runContextCheck(contextParams);
- }
- this.dispatch('updateContextsRanges', [this.registeredContexts]);
- };
- /**
- * Sets the end offset of an open range
- * @param {number} offset range end offset
- * @param {string} contextName context name
- */
- Tokenizer.prototype.setEndOffset = function (offset, contextName) {
- var startIndex = this.getContext(contextName).openRange.startIndex;
- var range = new ContextRange(startIndex, offset, contextName);
- var ranges = this.getContext(contextName).ranges;
- range.rangeId = contextName + "." + (ranges.length);
- ranges.push(range);
- this.getContext(contextName).openRange = null;
- return range;
- };
- /**
- * Runs a context check on the current context
- * @param {contextParams} contextParams current context params
- */
- Tokenizer.prototype.runContextCheck = function(contextParams) {
- var this$1 = this;
- var index = contextParams.index;
- this.contextCheckers.forEach(function (contextChecker) {
- var contextName = contextChecker.contextName;
- var openRange = this$1.getContext(contextName).openRange;
- if (!openRange && contextChecker.checkStart(contextParams)) {
- openRange = new ContextRange(index, null, contextName);
- this$1.getContext(contextName).openRange = openRange;
- this$1.dispatch('contextStart', [contextName, index]);
- }
- if (!!openRange && contextChecker.checkEnd(contextParams)) {
- var offset = (index - openRange.startIndex) + 1;
- var range = this$1.setEndOffset(offset, contextName);
- this$1.dispatch('contextEnd', [contextName, range]);
- }
- });
- };
- /**
- * Converts a text into a list of tokens
- * @param {string} text a text to tokenize
- */
- Tokenizer.prototype.tokenize = function (text) {
- this.tokens = [];
- this.resetContextsRanges();
- var chars = Array.from(text);
- this.dispatch('start');
- for (var i = 0; i < chars.length; i++) {
- var char = chars[i];
- var contextParams = new ContextParams(chars, i);
- this.dispatch('next', [contextParams]);
- this.runContextCheck(contextParams);
- var token = new Token(char);
- this.tokens.push(token);
- this.dispatch('newToken', [token, contextParams]);
- }
- this.dispatch('end', [this.tokens]);
- return this.tokens;
- };
- // ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮
- // ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊
- // ╰─╾──────────────────────────┄─────────────────────────────────────────────╯
- // jscs:disable maximumLineLength
- /**
- * Check if a char is Arabic
- * @param {string} c a single char
- */
- function isArabicChar(c) {
- return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c);
- }
- /**
- * Check if a char is an isolated arabic char
- * @param {string} c a single char
- */
- function isIsolatedArabicChar(char) {
- return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char);
- }
- /**
- * Check if a char is an Arabic Tashkeel char
- * @param {string} c a single char
- */
- function isTashkeelArabicChar(char) {
- return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char);
- }
- /**
- * Check if a char is Latin
- * @param {string} c a single char
- */
- function isLatinChar(c) {
- return /[A-z]/.test(c);
- }
- /**
- * Check if a char is whitespace char
- * @param {string} c a single char
- */
- function isWhiteSpace(c) {
- return /\s/.test(c);
- }
- /**
- * Query a feature by some of it's properties to lookup a glyph substitution.
- */
- /**
- * Create feature query instance
- * @param {Font} font opentype font instance
- */
- function FeatureQuery(font) {
- this.font = font;
- this.features = {};
- }
- /**
- * @typedef SubstitutionAction
- * @type Object
- * @property {number} id substitution type
- * @property {string} tag feature tag
- * @property {any} substitution substitution value(s)
- */
- /**
- * Create a substitution action instance
- * @param {SubstitutionAction} action
- */
- function SubstitutionAction(action) {
- this.id = action.id;
- this.tag = action.tag;
- this.substitution = action.substitution;
- }
- /**
- * Lookup a coverage table
- * @param {number} glyphIndex glyph index
- * @param {CoverageTable} coverage coverage table
- */
- function lookupCoverage(glyphIndex, coverage) {
- if (!glyphIndex) { return -1; }
- switch (coverage.format) {
- case 1:
- return coverage.glyphs.indexOf(glyphIndex);
- case 2:
- var ranges = coverage.ranges;
- for (var i = 0; i < ranges.length; i++) {
- var range = ranges[i];
- if (glyphIndex >= range.start && glyphIndex <= range.end) {
- var offset = glyphIndex - range.start;
- return range.index + offset;
- }
- }
- break;
- default:
- return -1; // not found
- }
- return -1;
- }
- /**
- * Handle a single substitution - format 1
- * @param {ContextParams} contextParams context params to lookup
- */
- function singleSubstitutionFormat1(glyphIndex, subtable) {
- var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage);
- if (substituteIndex === -1) { return null; }
- return glyphIndex + subtable.deltaGlyphId;
- }
- /**
- * Handle a single substitution - format 2
- * @param {ContextParams} contextParams context params to lookup
- */
- function singleSubstitutionFormat2(glyphIndex, subtable) {
- var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage);
- if (substituteIndex === -1) { return null; }
- return subtable.substitute[substituteIndex];
- }
- /**
- * Lookup a list of coverage tables
- * @param {any} coverageList a list of coverage tables
- * @param {ContextParams} contextParams context params to lookup
- */
- function lookupCoverageList(coverageList, contextParams) {
- var lookupList = [];
- for (var i = 0; i < coverageList.length; i++) {
- var coverage = coverageList[i];
- var glyphIndex = contextParams.current;
- glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex;
- var lookupIndex = lookupCoverage(glyphIndex, coverage);
- if (lookupIndex !== -1) {
- lookupList.push(lookupIndex);
- }
- }
- if (lookupList.length !== coverageList.length) { return -1; }
- return lookupList;
- }
- /**
- * Handle chaining context substitution - format 3
- * @param {ContextParams} contextParams context params to lookup
- */
- function chainingSubstitutionFormat3(contextParams, subtable) {
- var lookupsCount = (
- subtable.inputCoverage.length +
- subtable.lookaheadCoverage.length +
- subtable.backtrackCoverage.length
- );
- if (contextParams.context.length < lookupsCount) { return []; }
- // INPUT LOOKUP //
- var inputLookups = lookupCoverageList(
- subtable.inputCoverage, contextParams
- );
- if (inputLookups === -1) { return []; }
- // LOOKAHEAD LOOKUP //
- var lookaheadOffset = subtable.inputCoverage.length - 1;
- if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; }
- var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset);
- while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) {
- lookaheadContext.shift();
- }
- var lookaheadParams = new ContextParams(lookaheadContext, 0);
- var lookaheadLookups = lookupCoverageList(
- subtable.lookaheadCoverage, lookaheadParams
- );
- // BACKTRACK LOOKUP //
- var backtrackContext = [].concat(contextParams.backtrack);
- backtrackContext.reverse();
- while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) {
- backtrackContext.shift();
- }
- if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; }
- var backtrackParams = new ContextParams(backtrackContext, 0);
- var backtrackLookups = lookupCoverageList(
- subtable.backtrackCoverage, backtrackParams
- );
- var contextRulesMatch = (
- inputLookups.length === subtable.inputCoverage.length &&
- lookaheadLookups.length === subtable.lookaheadCoverage.length &&
- backtrackLookups.length === subtable.backtrackCoverage.length
- );
- var substitutions = [];
- if (contextRulesMatch) {
- for (var i = 0; i < subtable.lookupRecords.length; i++) {
- var lookupRecord = subtable.lookupRecords[i];
- var lookupListIndex = lookupRecord.lookupListIndex;
- var lookupTable = this.getLookupByIndex(lookupListIndex);
- for (var s = 0; s < lookupTable.subtables.length; s++) {
- var subtable$1 = lookupTable.subtables[s];
- var lookup = this.getLookupMethod(lookupTable, subtable$1);
- var substitutionType = this.getSubstitutionType(lookupTable, subtable$1);
- if (substitutionType === '12') {
- for (var n = 0; n < inputLookups.length; n++) {
- var glyphIndex = contextParams.get(n);
- var substitution = lookup(glyphIndex);
- if (substitution) { substitutions.push(substitution); }
- }
- }
- }
- }
- }
- return substitutions;
- }
- /**
- * Handle ligature substitution - format 1
- * @param {ContextParams} contextParams context params to lookup
- */
- function ligatureSubstitutionFormat1(contextParams, subtable) {
- // COVERAGE LOOKUP //
- var glyphIndex = contextParams.current;
- var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage);
- if (ligSetIndex === -1) { return null; }
- // COMPONENTS LOOKUP
- // (!) note, components are ordered in the written direction.
- var ligature;
- var ligatureSet = subtable.ligatureSets[ligSetIndex];
- for (var s = 0; s < ligatureSet.length; s++) {
- ligature = ligatureSet[s];
- for (var l = 0; l < ligature.components.length; l++) {
- var lookaheadItem = contextParams.lookahead[l];
- var component = ligature.components[l];
- if (lookaheadItem !== component) { break; }
- if (l === ligature.components.length - 1) { return ligature; }
- }
- }
- return null;
- }
- /**
- * Handle decomposition substitution - format 1
- * @param {number} glyphIndex glyph index
- * @param {any} subtable subtable
- */
- function decompositionSubstitutionFormat1(glyphIndex, subtable) {
- var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage);
- if (substituteIndex === -1) { return null; }
- return subtable.sequences[substituteIndex];
- }
- /**
- * Get default script features indexes
- */
- FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () {
- var scripts = this.font.tables.gsub.scripts;
- for (var s = 0; s < scripts.length; s++) {
- var script = scripts[s];
- if (script.tag === 'DFLT') { return (
- script.script.defaultLangSys.featureIndexes
- ); }
- }
- return [];
- };
- /**
- * Get feature indexes of a specific script
- * @param {string} scriptTag script tag
- */
- FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) {
- var tables = this.font.tables;
- if (!tables.gsub) { return []; }
- if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); }
- var scripts = this.font.tables.gsub.scripts;
- for (var i = 0; i < scripts.length; i++) {
- var script = scripts[i];
- if (script.tag === scriptTag && script.script.defaultLangSys) {
- return script.script.defaultLangSys.featureIndexes;
- } else {
- var langSysRecords = script.langSysRecords;
- if (!!langSysRecords) {
- for (var j = 0; j < langSysRecords.length; j++) {
- var langSysRecord = langSysRecords[j];
- if (langSysRecord.tag === scriptTag) {
- var langSys = langSysRecord.langSys;
- return langSys.featureIndexes;
- }
- }
- }
- }
- }
- return this.getDefaultScriptFeaturesIndexes();
- };
- /**
- * Map a feature tag to a gsub feature
- * @param {any} features gsub features
- * @param {string} scriptTag script tag
- */
- FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) {
- var tags = {};
- for (var i = 0; i < features.length; i++) {
- var tag = features[i].tag;
- var feature = features[i].feature;
- tags[tag] = feature;
- }
- this.features[scriptTag].tags = tags;
- };
- /**
- * Get features of a specific script
- * @param {string} scriptTag script tag
- */
- FeatureQuery.prototype.getScriptFeatures = function (scriptTag) {
- var features = this.features[scriptTag];
- if (this.features.hasOwnProperty(scriptTag)) { return features; }
- var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag);
- if (!featuresIndexes) { return null; }
- var gsub = this.font.tables.gsub;
- features = featuresIndexes.map(function (index) { return gsub.features[index]; });
- this.features[scriptTag] = features;
- this.mapTagsToFeatures(features, scriptTag);
- return features;
- };
- /**
- * Get substitution type
- * @param {any} lookupTable lookup table
- * @param {any} subtable subtable
- */
- FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) {
- var lookupType = lookupTable.lookupType.toString();
- var substFormat = subtable.substFormat.toString();
- return lookupType + substFormat;
- };
- /**
- * Get lookup method
- * @param {any} lookupTable lookup table
- * @param {any} subtable subtable
- */
- FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) {
- var this$1 = this;
- var substitutionType = this.getSubstitutionType(lookupTable, subtable);
- switch (substitutionType) {
- case '11':
- return function (glyphIndex) { return singleSubstitutionFormat1.apply(
- this$1, [glyphIndex, subtable]
- ); };
- case '12':
- return function (glyphIndex) { return singleSubstitutionFormat2.apply(
- this$1, [glyphIndex, subtable]
- ); };
- case '63':
- return function (contextParams) { return chainingSubstitutionFormat3.apply(
- this$1, [contextParams, subtable]
- ); };
- case '41':
- return function (contextParams) { return ligatureSubstitutionFormat1.apply(
- this$1, [contextParams, subtable]
- ); };
- case '21':
- return function (glyphIndex) { return decompositionSubstitutionFormat1.apply(
- this$1, [glyphIndex, subtable]
- ); };
- default:
- throw new Error(
- "lookupType: " + (lookupTable.lookupType) + " - " +
- "substFormat: " + (subtable.substFormat) + " " +
- "is not yet supported"
- );
- }
- };
- /**
- * [ LOOKUP TYPES ]
- * -------------------------------
- * Single 1;
- * Multiple 2;
- * Alternate 3;
- * Ligature 4;
- * Context 5;
- * ChainingContext 6;
- * ExtensionSubstitution 7;
- * ReverseChainingContext 8;
- * -------------------------------
- *
- */
- /**
- * @typedef FQuery
- * @type Object
- * @param {string} tag feature tag
- * @param {string} script feature script
- * @param {ContextParams} contextParams context params
- */
- /**
- * Lookup a feature using a query parameters
- * @param {FQuery} query feature query
- */
- FeatureQuery.prototype.lookupFeature = function (query) {
- var contextParams = query.contextParams;
- var currentIndex = contextParams.index;
- var feature = this.getFeature({
- tag: query.tag, script: query.script
- });
- if (!feature) { return new Error(
- "font '" + (this.font.names.fullName.en) + "' " +
- "doesn't support feature '" + (query.tag) + "' " +
- "for script '" + (query.script) + "'."
- ); }
- var lookups = this.getFeatureLookups(feature);
- var substitutions = [].concat(contextParams.context);
- for (var l = 0; l < lookups.length; l++) {
- var lookupTable = lookups[l];
- var subtables = this.getLookupSubtables(lookupTable);
- for (var s = 0; s < subtables.length; s++) {
- var subtable = subtables[s];
- var substType = this.getSubstitutionType(lookupTable, subtable);
- var lookup = this.getLookupMethod(lookupTable, subtable);
- var substitution = (void 0);
- switch (substType) {
- case '11':
- substitution = lookup(contextParams.current);
- if (substitution) {
- substitutions.splice(currentIndex, 1, new SubstitutionAction({
- id: 11, tag: query.tag, substitution: substitution
- }));
- }
- break;
- case '12':
- substitution = lookup(contextParams.current);
- if (substitution) {
- substitutions.splice(currentIndex, 1, new SubstitutionAction({
- id: 12, tag: query.tag, substitution: substitution
- }));
- }
- break;
- case '63':
- substitution = lookup(contextParams);
- if (Array.isArray(substitution) && substitution.length) {
- substitutions.splice(currentIndex, 1, new SubstitutionAction({
- id: 63, tag: query.tag, substitution: substitution
- }));
- }
- break;
- case '41':
- substitution = lookup(contextParams);
- if (substitution) {
- substitutions.splice(currentIndex, 1, new SubstitutionAction({
- id: 41, tag: query.tag, substitution: substitution
- }));
- }
- break;
- case '21':
- substitution = lookup(contextParams.current);
- if (substitution) {
- substitutions.splice(currentIndex, 1, new SubstitutionAction({
- id: 21, tag: query.tag, substitution: substitution
- }));
- }
- break;
- }
- contextParams = new ContextParams(substitutions, currentIndex);
- if (Array.isArray(substitution) && !substitution.length) { continue; }
- substitution = null;
- }
- }
- return substitutions.length ? substitutions : null;
- };
- /**
- * Checks if a font supports a specific features
- * @param {FQuery} query feature query object
- */
- FeatureQuery.prototype.supports = function (query) {
- if (!query.script) { return false; }
- this.getScriptFeatures(query.script);
- var supportedScript = this.features.hasOwnProperty(query.script);
- if (!query.tag) { return supportedScript; }
- var supportedFeature = (
- this.features[query.script].some(function (feature) { return feature.tag === query.tag; })
- );
- return supportedScript && supportedFeature;
- };
- /**
- * Get lookup table subtables
- * @param {any} lookupTable lookup table
- */
- FeatureQuery.prototype.getLookupSubtables = function (lookupTable) {
- return lookupTable.subtables || null;
- };
- /**
- * Get lookup table by index
- * @param {number} index lookup table index
- */
- FeatureQuery.prototype.getLookupByIndex = function (index) {
- var lookups = this.font.tables.gsub.lookups;
- return lookups[index] || null;
- };
- /**
- * Get lookup tables for a feature
- * @param {string} feature
- */
- FeatureQuery.prototype.getFeatureLookups = function (feature) {
- // TODO: memoize
- return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this));
- };
- /**
- * Query a feature by it's properties
- * @param {any} query an object that describes the properties of a query
- */
- FeatureQuery.prototype.getFeature = function getFeature(query) {
- if (!this.font) { return { FAIL: "No font was found"}; }
- if (!this.features.hasOwnProperty(query.script)) {
- this.getScriptFeatures(query.script);
- }
- var scriptFeatures = this.features[query.script];
- if (!scriptFeatures) { return (
- { FAIL: ("No feature for script " + (query.script))}
- ); }
- if (!scriptFeatures.tags[query.tag]) { return null; }
- return this.features[query.script].tags[query.tag];
- };
- /**
- * Arabic word context checkers
- */
- function arabicWordStartCheck(contextParams) {
- var char = contextParams.current;
- var prevChar = contextParams.get(-1);
- return (
- // ? arabic first char
- (prevChar === null && isArabicChar(char)) ||
- // ? arabic char preceded with a non arabic char
- (!isArabicChar(prevChar) && isArabicChar(char))
- );
- }
- function arabicWordEndCheck(contextParams) {
- var nextChar = contextParams.get(1);
- return (
- // ? last arabic char
- (nextChar === null) ||
- // ? next char is not arabic
- (!isArabicChar(nextChar))
- );
- }
- var arabicWordCheck = {
- startCheck: arabicWordStartCheck,
- endCheck: arabicWordEndCheck
- };
- /**
- * Arabic sentence context checkers
- */
- function arabicSentenceStartCheck(contextParams) {
- var char = contextParams.current;
- var prevChar = contextParams.get(-1);
- return (
- // ? an arabic char preceded with a non arabic char
- (isArabicChar(char) || isTashkeelArabicChar(char)) &&
- !isArabicChar(prevChar)
- );
- }
- function arabicSentenceEndCheck(contextParams) {
- var nextChar = contextParams.get(1);
- switch (true) {
- case nextChar === null:
- return true;
- case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)):
- var nextIsWhitespace = isWhiteSpace(nextChar);
- if (!nextIsWhitespace) { return true; }
- if (nextIsWhitespace) {
- var arabicCharAhead = false;
- arabicCharAhead = (
- contextParams.lookahead.some(
- function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); }
- )
- );
- if (!arabicCharAhead) { return true; }
- }
- break;
- default:
- return false;
- }
- }
- var arabicSentenceCheck = {
- startCheck: arabicSentenceStartCheck,
- endCheck: arabicSentenceEndCheck
- };
- /**
- * Apply single substitution format 1
- * @param {Array} substitutions substitutions
- * @param {any} tokens a list of tokens
- * @param {number} index token index
- */
- function singleSubstitutionFormat1$1(action, tokens, index) {
- tokens[index].setState(action.tag, action.substitution);
- }
- /**
- * Apply single substitution format 2
- * @param {Array} substitutions substitutions
- * @param {any} tokens a list of tokens
- * @param {number} index token index
- */
- function singleSubstitutionFormat2$1(action, tokens, index) {
- tokens[index].setState(action.tag, action.substitution);
- }
- /**
- * Apply chaining context substitution format 3
- * @param {Array} substitutions substitutions
- * @param {any} tokens a list of tokens
- * @param {number} index token index
- */
- function chainingSubstitutionFormat3$1(action, tokens, index) {
- action.substitution.forEach(function (subst, offset) {
- var token = tokens[index + offset];
- token.setState(action.tag, subst);
- });
- }
- /**
- * Apply ligature substitution format 1
- * @param {Array} substitutions substitutions
- * @param {any} tokens a list of tokens
- * @param {number} index token index
- */
- function ligatureSubstitutionFormat1$1(action, tokens, index) {
- var token = tokens[index];
- token.setState(action.tag, action.substitution.ligGlyph);
- var compsCount = action.substitution.components.length;
- for (var i = 0; i < compsCount; i++) {
- token = tokens[index + i + 1];
- token.setState('deleted', true);
- }
- }
- /**
- * Supported substitutions
- */
- var SUBSTITUTIONS = {
- 11: singleSubstitutionFormat1$1,
- 12: singleSubstitutionFormat2$1,
- 63: chainingSubstitutionFormat3$1,
- 41: ligatureSubstitutionFormat1$1
- };
- /**
- * Apply substitutions to a list of tokens
- * @param {Array} substitutions substitutions
- * @param {any} tokens a list of tokens
- * @param {number} index token index
- */
- function applySubstitution(action, tokens, index) {
- if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) {
- SUBSTITUTIONS[action.id](action, tokens, index);
- }
- }
- /**
- * Apply Arabic presentation forms to a range of tokens
- */
- /**
- * Check if a char can be connected to it's preceding char
- * @param {ContextParams} charContextParams context params of a char
- */
- function willConnectPrev(charContextParams) {
- var backtrack = [].concat(charContextParams.backtrack);
- for (var i = backtrack.length - 1; i >= 0; i--) {
- var prevChar = backtrack[i];
- var isolated = isIsolatedArabicChar(prevChar);
- var tashkeel = isTashkeelArabicChar(prevChar);
- if (!isolated && !tashkeel) { return true; }
- if (isolated) { return false; }
- }
- return false;
- }
- /**
- * Check if a char can be connected to it's proceeding char
- * @param {ContextParams} charContextParams context params of a char
- */
- function willConnectNext(charContextParams) {
- if (isIsolatedArabicChar(charContextParams.current)) { return false; }
- for (var i = 0; i < charContextParams.lookahead.length; i++) {
- var nextChar = charContextParams.lookahead[i];
- var tashkeel = isTashkeelArabicChar(nextChar);
- if (!tashkeel) { return true; }
- }
- return false;
- }
- /**
- * Apply arabic presentation forms to a list of tokens
- * @param {ContextRange} range a range of tokens
- */
- function arabicPresentationForms(range) {
- var this$1 = this;
- var script = 'arab';
- var tags = this.featuresTags[script];
- var tokens = this.tokenizer.getRangeTokens(range);
- if (tokens.length === 1) { return; }
- var contextParams = new ContextParams(
- tokens.map(function (token) { return token.getState('glyphIndex'); }
- ), 0);
- var charContextParams = new ContextParams(
- tokens.map(function (token) { return token.char; }
- ), 0);
- tokens.forEach(function (token, index) {
- if (isTashkeelArabicChar(token.char)) { return; }
- contextParams.setCurrentIndex(index);
- charContextParams.setCurrentIndex(index);
- var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev)
- if (willConnectPrev(charContextParams)) { CONNECT |= 1; }
- if (willConnectNext(charContextParams)) { CONNECT |= 2; }
- var tag;
- switch (CONNECT) {
- case 1: (tag = 'fina'); break;
- case 2: (tag = 'init'); break;
- case 3: (tag = 'medi'); break;
- }
- if (tags.indexOf(tag) === -1) { return; }
- var substitutions = this$1.query.lookupFeature({
- tag: tag, script: script, contextParams: contextParams
- });
- if (substitutions instanceof Error) { return console.info(substitutions.message); }
- substitutions.forEach(function (action, index) {
- if (action instanceof SubstitutionAction) {
- applySubstitution(action, tokens, index);
- contextParams.context[index] = action.substitution;
- }
- });
- });
- }
- /**
- * Apply Arabic required ligatures feature to a range of tokens
- */
- /**
- * Update context params
- * @param {any} tokens a list of tokens
- * @param {number} index current item index
- */
- function getContextParams(tokens, index) {
- var context = tokens.map(function (token) { return token.activeState.value; });
- return new ContextParams(context, index || 0);
- }
- /**
- * Apply Arabic required ligatures to a context range
- * @param {ContextRange} range a range of tokens
- */
- function arabicRequiredLigatures(range) {
- var this$1 = this;
- var script = 'arab';
- var tokens = this.tokenizer.getRangeTokens(range);
- var contextParams = getContextParams(tokens);
- contextParams.context.forEach(function (glyphIndex, index) {
- contextParams.setCurrentIndex(index);
- var substitutions = this$1.query.lookupFeature({
- tag: 'rlig', script: script, contextParams: contextParams
- });
- if (substitutions.length) {
- substitutions.forEach(
- function (action) { return applySubstitution(action, tokens, index); }
- );
- contextParams = getContextParams(tokens);
- }
- });
- }
- /**
- * Latin word context checkers
- */
- function latinWordStartCheck(contextParams) {
- var char = contextParams.current;
- var prevChar = contextParams.get(-1);
- return (
- // ? latin first char
- (prevChar === null && isLatinChar(char)) ||
- // ? latin char preceded with a non latin char
- (!isLatinChar(prevChar) && isLatinChar(char))
- );
- }
- function latinWordEndCheck(contextParams) {
- var nextChar = contextParams.get(1);
- return (
- // ? last latin char
- (nextChar === null) ||
- // ? next char is not latin
- (!isLatinChar(nextChar))
- );
- }
- var latinWordCheck = {
- startCheck: latinWordStartCheck,
- endCheck: latinWordEndCheck
- };
- /**
- * Apply Latin ligature feature to a range of tokens
- */
- /**
- * Update context params
- * @param {any} tokens a list of tokens
- * @param {number} index current item index
- */
- function getContextParams$1(tokens, index) {
- var context = tokens.map(function (token) { return token.activeState.value; });
- return new ContextParams(context, index || 0);
- }
- /**
- * Apply Arabic required ligatures to a context range
- * @param {ContextRange} range a range of tokens
- */
- function latinLigature(range) {
- var this$1 = this;
- var script = 'latn';
- var tokens = this.tokenizer.getRangeTokens(range);
- var contextParams = getContextParams$1(tokens);
- contextParams.context.forEach(function (glyphIndex, index) {
- contextParams.setCurrentIndex(index);
- var substitutions = this$1.query.lookupFeature({
- tag: 'liga', script: script, contextParams: contextParams
- });
- if (substitutions.length) {
- substitutions.forEach(
- function (action) { return applySubstitution(action, tokens, index); }
- );
- contextParams = getContextParams$1(tokens);
- }
- });
- }
- /**
- * Infer bidirectional properties for a given text and apply
- * the corresponding layout rules.
- */
- /**
- * Create Bidi. features
- * @param {string} baseDir text base direction. value either 'ltr' or 'rtl'
- */
- function Bidi(baseDir) {
- this.baseDir = baseDir || 'ltr';
- this.tokenizer = new Tokenizer();
- this.featuresTags = {};
- }
- /**
- * Sets Bidi text
- * @param {string} text a text input
- */
- Bidi.prototype.setText = function (text) {
- this.text = text;
- };
- /**
- * Store essential context checks:
- * arabic word check for applying gsub features
- * arabic sentence check for adjusting arabic layout
- */
- Bidi.prototype.contextChecks = ({
- latinWordCheck: latinWordCheck,
- arabicWordCheck: arabicWordCheck,
- arabicSentenceCheck: arabicSentenceCheck
- });
- /**
- * Register arabic word check
- */
- function registerContextChecker(checkId) {
- var check = this.contextChecks[(checkId + "Check")];
- return this.tokenizer.registerContextChecker(
- checkId, check.startCheck, check.endCheck
- );
- }
- /**
- * Perform pre tokenization procedure then
- * tokenize text input
- */
- function tokenizeText() {
- registerContextChecker.call(this, 'latinWord');
- registerContextChecker.call(this, 'arabicWord');
- registerContextChecker.call(this, 'arabicSentence');
- return this.tokenizer.tokenize(this.text);
- }
- /**
- * Reverse arabic sentence layout
- * TODO: check base dir before applying adjustments - priority low
- */
- function reverseArabicSentences() {
- var this$1 = this;
- var ranges = this.tokenizer.getContextRanges('arabicSentence');
- ranges.forEach(function (range) {
- var rangeTokens = this$1.tokenizer.getRangeTokens(range);
- this$1.tokenizer.replaceRange(
- range.startIndex,
- range.endOffset,
- rangeTokens.reverse()
- );
- });
- }
- /**
- * Register supported features tags
- * @param {script} script script tag
- * @param {Array} tags features tags list
- */
- Bidi.prototype.registerFeatures = function (script, tags) {
- var this$1 = this;
- var supportedTags = tags.filter(
- function (tag) { return this$1.query.supports({script: script, tag: tag}); }
- );
- if (!this.featuresTags.hasOwnProperty(script)) {
- this.featuresTags[script] = supportedTags;
- } else {
- this.featuresTags[script] =
- this.featuresTags[script].concat(supportedTags);
- }
- };
- /**
- * Apply GSUB features
- * @param {Array} tagsList a list of features tags
- * @param {string} script a script tag
- * @param {Font} font opentype font instance
- */
- Bidi.prototype.applyFeatures = function (font, features) {
- if (!font) { throw new Error(
- 'No valid font was provided to apply features'
- ); }
- if (!this.query) { this.query = new FeatureQuery(font); }
- for (var f = 0; f < features.length; f++) {
- var feature = features[f];
- if (!this.query.supports({script: feature.script})) { continue; }
- this.registerFeatures(feature.script, feature.tags);
- }
- };
- /**
- * Register a state modifier
- * @param {string} modifierId state modifier id
- * @param {function} condition a predicate function that returns true or false
- * @param {function} modifier a modifier function to set token state
- */
- Bidi.prototype.registerModifier = function (modifierId, condition, modifier) {
- this.tokenizer.registerModifier(modifierId, condition, modifier);
- };
- /**
- * Check if 'glyphIndex' is registered
- */
- function checkGlyphIndexStatus() {
- if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) {
- throw new Error(
- 'glyphIndex modifier is required to apply ' +
- 'arabic presentation features.'
- );
- }
- }
- /**
- * Apply arabic presentation forms features
- */
- function applyArabicPresentationForms() {
- var this$1 = this;
- var script = 'arab';
- if (!this.featuresTags.hasOwnProperty(script)) { return; }
- checkGlyphIndexStatus.call(this);
- var ranges = this.tokenizer.getContextRanges('arabicWord');
- ranges.forEach(function (range) {
- arabicPresentationForms.call(this$1, range);
- });
- }
- /**
- * Apply required arabic ligatures
- */
- function applyArabicRequireLigatures() {
- var this$1 = this;
- var script = 'arab';
- if (!this.featuresTags.hasOwnProperty(script)) { return; }
- var tags = this.featuresTags[script];
- if (tags.indexOf('rlig') === -1) { return; }
- checkGlyphIndexStatus.call(this);
- var ranges = this.tokenizer.getContextRanges('arabicWord');
- ranges.forEach(function (range) {
- arabicRequiredLigatures.call(this$1, range);
- });
- }
- /**
- * Apply required arabic ligatures
- */
- function applyLatinLigatures() {
- var this$1 = this;
- var script = 'latn';
- if (!this.featuresTags.hasOwnProperty(script)) { return; }
- var tags = this.featuresTags[script];
- if (tags.indexOf('liga') === -1) { return; }
- checkGlyphIndexStatus.call(this);
- var ranges = this.tokenizer.getContextRanges('latinWord');
- ranges.forEach(function (range) {
- latinLigature.call(this$1, range);
- });
- }
- /**
- * Check if a context is registered
- * @param {string} contextId context id
- */
- Bidi.prototype.checkContextReady = function (contextId) {
- return !!this.tokenizer.getContext(contextId);
- };
- /**
- * Apply features to registered contexts
- */
- Bidi.prototype.applyFeaturesToContexts = function () {
- if (this.checkContextReady('arabicWord')) {
- applyArabicPresentationForms.call(this);
- applyArabicRequireLigatures.call(this);
- }
- if (this.checkContextReady('latinWord')) {
- applyLatinLigatures.call(this);
- }
- if (this.checkContextReady('arabicSentence')) {
- reverseArabicSentences.call(this);
- }
- };
- /**
- * process text input
- * @param {string} text an input text
- */
- Bidi.prototype.processText = function(text) {
- if (!this.text || this.text !== text) {
- this.setText(text);
- tokenizeText.call(this);
- this.applyFeaturesToContexts();
- }
- };
- /**
- * Process a string of text to identify and adjust
- * bidirectional text entities.
- * @param {string} text input text
- */
- Bidi.prototype.getBidiText = function (text) {
- this.processText(text);
- return this.tokenizer.getText();
- };
- /**
- * Get the current state index of each token
- * @param {text} text an input text
- */
- Bidi.prototype.getTextGlyphs = function (text) {
- this.processText(text);
- var indexes = [];
- for (var i = 0; i < this.tokenizer.tokens.length; i++) {
- var token = this.tokenizer.tokens[i];
- if (token.state.deleted) { continue; }
- var index = token.activeState.value;
- indexes.push(Array.isArray(index) ? index[0] : index);
- }
- return indexes;
- };
- // The Font object
- /**
- * @typedef FontOptions
- * @type Object
- * @property {Boolean} empty - whether to create a new empty font
- * @property {string} familyName
- * @property {string} styleName
- * @property {string=} fullName
- * @property {string=} postScriptName
- * @property {string=} designer
- * @property {string=} designerURL
- * @property {string=} manufacturer
- * @property {string=} manufacturerURL
- * @property {string=} license
- * @property {string=} licenseURL
- * @property {string=} version
- * @property {string=} description
- * @property {string=} copyright
- * @property {string=} trademark
- * @property {Number} unitsPerEm
- * @property {Number} ascender
- * @property {Number} descender
- * @property {Number} createdTimestamp
- * @property {string=} weightClass
- * @property {string=} widthClass
- * @property {string=} fsSelection
- */
- /**
- * A Font represents a loaded OpenType font file.
- * It contains a set of glyphs and methods to draw text on a drawing context,
- * or to get a path representing the text.
- * @exports opentype.Font
- * @class
- * @param {FontOptions}
- * @constructor
- */
- function Font(options) {
- options = options || {};
- options.tables = options.tables || {};
- if (!options.empty) {
- // Check that we've provided the minimum set of names.
- checkArgument(options.familyName, 'When creating a new Font object, familyName is required.');
- checkArgument(options.styleName, 'When creating a new Font object, styleName is required.');
- checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.');
- checkArgument(options.ascender, 'When creating a new Font object, ascender is required.');
- checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.');
- // OS X will complain if the names are empty, so we put a single space everywhere by default.
- this.names = {
- fontFamily: {en: options.familyName || ' '},
- fontSubfamily: {en: options.styleName || ' '},
- fullName: {en: options.fullName || options.familyName + ' ' + options.styleName},
- // postScriptName may not contain any whitespace
- postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')},
- designer: {en: options.designer || ' '},
- designerURL: {en: options.designerURL || ' '},
- manufacturer: {en: options.manufacturer || ' '},
- manufacturerURL: {en: options.manufacturerURL || ' '},
- license: {en: options.license || ' '},
- licenseURL: {en: options.licenseURL || ' '},
- version: {en: options.version || 'Version 0.1'},
- description: {en: options.description || ' '},
- copyright: {en: options.copyright || ' '},
- trademark: {en: options.trademark || ' '}
- };
- this.unitsPerEm = options.unitsPerEm || 1000;
- this.ascender = options.ascender;
- this.descender = options.descender;
- this.createdTimestamp = options.createdTimestamp;
- this.tables = Object.assign(options.tables, {
- os2: Object.assign({
- usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM,
- usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM,
- fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR,
- }, options.tables.os2)
- });
- }
- this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported.
- this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []);
- this.encoding = new DefaultEncoding(this);
- this.position = new Position(this);
- this.substitution = new Substitution(this);
- this.tables = this.tables || {};
- // needed for low memory mode only.
- this._push = null;
- this._hmtxTableData = {};
- Object.defineProperty(this, 'hinting', {
- get: function() {
- if (this._hinting) { return this._hinting; }
- if (this.outlinesFormat === 'truetype') {
- return (this._hinting = new Hinting(this));
- }
- }
- });
- }
- /**
- * Check if the font has a glyph for the given character.
- * @param {string}
- * @return {Boolean}
- */
- Font.prototype.hasChar = function(c) {
- return this.encoding.charToGlyphIndex(c) !== null;
- };
- /**
- * Convert the given character to a single glyph index.
- * Note that this function assumes that there is a one-to-one mapping between
- * the given character and a glyph; for complex scripts this might not be the case.
- * @param {string}
- * @return {Number}
- */
- Font.prototype.charToGlyphIndex = function(s) {
- return this.encoding.charToGlyphIndex(s);
- };
- /**
- * Convert the given character to a single Glyph object.
- * Note that this function assumes that there is a one-to-one mapping between
- * the given character and a glyph; for complex scripts this might not be the case.
- * @param {string}
- * @return {opentype.Glyph}
- */
- Font.prototype.charToGlyph = function(c) {
- var glyphIndex = this.charToGlyphIndex(c);
- var glyph = this.glyphs.get(glyphIndex);
- if (!glyph) {
- // .notdef
- glyph = this.glyphs.get(0);
- }
- return glyph;
- };
- /**
- * Update features
- * @param {any} options features options
- */
- Font.prototype.updateFeatures = function (options) {
- // TODO: update all features options not only 'latn'.
- return this.defaultRenderOptions.features.map(function (feature) {
- if (feature.script === 'latn') {
- return {
- script: 'latn',
- tags: feature.tags.filter(function (tag) { return options[tag]; })
- };
- } else {
- return feature;
- }
- });
- };
- /**
- * Convert the given text to a list of Glyph objects.
- * Note that there is no strict one-to-one mapping between characters and
- * glyphs, so the list of returned glyphs can be larger or smaller than the
- * length of the given string.
- * @param {string}
- * @param {GlyphRenderOptions} [options]
- * @return {opentype.Glyph[]}
- */
- Font.prototype.stringToGlyphs = function(s, options) {
- var this$1 = this;
- var bidi = new Bidi();
- // Create and register 'glyphIndex' state modifier
- var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); };
- bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod);
- // roll-back to default features
- var features = options ?
- this.updateFeatures(options.features) :
- this.defaultRenderOptions.features;
- bidi.applyFeatures(this, features);
- var indexes = bidi.getTextGlyphs(s);
- var length = indexes.length;
- // convert glyph indexes to glyph objects
- var glyphs = new Array(length);
- var notdef = this.glyphs.get(0);
- for (var i = 0; i < length; i += 1) {
- glyphs[i] = this.glyphs.get(indexes[i]) || notdef;
- }
- return glyphs;
- };
- /**
- * @param {string}
- * @return {Number}
- */
- Font.prototype.nameToGlyphIndex = function(name) {
- return this.glyphNames.nameToGlyphIndex(name);
- };
- /**
- * @param {string}
- * @return {opentype.Glyph}
- */
- Font.prototype.nameToGlyph = function(name) {
- var glyphIndex = this.nameToGlyphIndex(name);
- var glyph = this.glyphs.get(glyphIndex);
- if (!glyph) {
- // .notdef
- glyph = this.glyphs.get(0);
- }
- return glyph;
- };
- /**
- * @param {Number}
- * @return {String}
- */
- Font.prototype.glyphIndexToName = function(gid) {
- if (!this.glyphNames.glyphIndexToName) {
- return '';
- }
- return this.glyphNames.glyphIndexToName(gid);
- };
- /**
- * Retrieve the value of the kerning pair between the left glyph (or its index)
- * and the right glyph (or its index). If no kerning pair is found, return 0.
- * The kerning value gets added to the advance width when calculating the spacing
- * between glyphs.
- * For GPOS kerning, this method uses the default script and language, which covers
- * most use cases. To have greater control, use font.position.getKerningValue .
- * @param {opentype.Glyph} leftGlyph
- * @param {opentype.Glyph} rightGlyph
- * @return {Number}
- */
- Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) {
- leftGlyph = leftGlyph.index || leftGlyph;
- rightGlyph = rightGlyph.index || rightGlyph;
- var gposKerning = this.position.defaultKerningTables;
- if (gposKerning) {
- return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph);
- }
- // "kern" table
- return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0;
- };
- /**
- * @typedef GlyphRenderOptions
- * @type Object
- * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used.
- * See https://www.microsoft.com/typography/otspec/scripttags.htm
- * @property {string} [language='dflt'] - language system used to determine which features to apply.
- * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx
- * @property {boolean} [kerning=true] - whether to include kerning values
- * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system.
- * See https://www.microsoft.com/typography/otspec/featuretags.htm
- */
- Font.prototype.defaultRenderOptions = {
- kerning: true,
- features: [
- /**
- * these 4 features are required to render Arabic text properly
- * and shouldn't be turned off when rendering arabic text.
- */
- { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] },
- { script: 'latn', tags: ['liga', 'rlig'] }
- ]
- };
- /**
- * Helper function that invokes the given callback for each glyph in the given text.
- * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text
- * @param {string} text - The text to apply.
- * @param {number} [x=0] - Horizontal position of the beginning of the text.
- * @param {number} [y=0] - Vertical position of the *baseline* of the text.
- * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
- * @param {GlyphRenderOptions=} options
- * @param {Function} callback
- */
- Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) {
- x = x !== undefined ? x : 0;
- y = y !== undefined ? y : 0;
- fontSize = fontSize !== undefined ? fontSize : 72;
- options = Object.assign({}, this.defaultRenderOptions, options);
- var fontScale = 1 / this.unitsPerEm * fontSize;
- var glyphs = this.stringToGlyphs(text, options);
- var kerningLookups;
- if (options.kerning) {
- var script = options.script || this.position.getDefaultScriptName();
- kerningLookups = this.position.getKerningTables(script, options.language);
- }
- for (var i = 0; i < glyphs.length; i += 1) {
- var glyph = glyphs[i];
- callback.call(this, glyph, x, y, fontSize, options);
- if (glyph.advanceWidth) {
- x += glyph.advanceWidth * fontScale;
- }
- if (options.kerning && i < glyphs.length - 1) {
- // We should apply position adjustment lookups in a more generic way.
- // Here we only use the xAdvance value.
- var kerningValue = kerningLookups ?
- this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) :
- this.getKerningValue(glyph, glyphs[i + 1]);
- x += kerningValue * fontScale;
- }
- if (options.letterSpacing) {
- x += options.letterSpacing * fontSize;
- } else if (options.tracking) {
- x += (options.tracking / 1000) * fontSize;
- }
- }
- return x;
- };
- /**
- * Create a Path object that represents the given text.
- * @param {string} text - The text to create.
- * @param {number} [x=0] - Horizontal position of the beginning of the text.
- * @param {number} [y=0] - Vertical position of the *baseline* of the text.
- * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
- * @param {GlyphRenderOptions=} options
- * @return {opentype.Path}
- */
- Font.prototype.getPath = function(text, x, y, fontSize, options) {
- var fullPath = new Path();
- this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) {
- var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this);
- fullPath.extend(glyphPath);
- });
- return fullPath;
- };
- /**
- * Create an array of Path objects that represent the glyphs of a given text.
- * @param {string} text - The text to create.
- * @param {number} [x=0] - Horizontal position of the beginning of the text.
- * @param {number} [y=0] - Vertical position of the *baseline* of the text.
- * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
- * @param {GlyphRenderOptions=} options
- * @return {opentype.Path[]}
- */
- Font.prototype.getPaths = function(text, x, y, fontSize, options) {
- var glyphPaths = [];
- this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) {
- var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this);
- glyphPaths.push(glyphPath);
- });
- return glyphPaths;
- };
- /**
- * Returns the advance width of a text.
- *
- * This is something different than Path.getBoundingBox() as for example a
- * suffixed whitespace increases the advanceWidth but not the bounding box
- * or an overhanging letter like a calligraphic 'f' might have a quite larger
- * bounding box than its advance width.
- *
- * This corresponds to canvas2dContext.measureText(text).width
- *
- * @param {string} text - The text to create.
- * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
- * @param {GlyphRenderOptions=} options
- * @return advance width
- */
- Font.prototype.getAdvanceWidth = function(text, fontSize, options) {
- return this.forEachGlyph(text, 0, 0, fontSize, options, function() {});
- };
- /**
- * Draw the text on the given drawing context.
- * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.
- * @param {string} text - The text to create.
- * @param {number} [x=0] - Horizontal position of the beginning of the text.
- * @param {number} [y=0] - Vertical position of the *baseline* of the text.
- * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
- * @param {GlyphRenderOptions=} options
- */
- Font.prototype.draw = function(ctx, text, x, y, fontSize, options) {
- this.getPath(text, x, y, fontSize, options).draw(ctx);
- };
- /**
- * Draw the points of all glyphs in the text.
- * On-curve points will be drawn in blue, off-curve points will be drawn in red.
- * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.
- * @param {string} text - The text to create.
- * @param {number} [x=0] - Horizontal position of the beginning of the text.
- * @param {number} [y=0] - Vertical position of the *baseline* of the text.
- * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
- * @param {GlyphRenderOptions=} options
- */
- Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) {
- this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) {
- glyph.drawPoints(ctx, gX, gY, gFontSize);
- });
- };
- /**
- * Draw lines indicating important font measurements for all glyphs in the text.
- * Black lines indicate the origin of the coordinate system (point 0,0).
- * Blue lines indicate the glyph bounding box.
- * Green line indicates the advance width of the glyph.
- * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.
- * @param {string} text - The text to create.
- * @param {number} [x=0] - Horizontal position of the beginning of the text.
- * @param {number} [y=0] - Vertical position of the *baseline* of the text.
- * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
- * @param {GlyphRenderOptions=} options
- */
- Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) {
- this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) {
- glyph.drawMetrics(ctx, gX, gY, gFontSize);
- });
- };
- /**
- * @param {string}
- * @return {string}
- */
- Font.prototype.getEnglishName = function(name) {
- var translations = this.names[name];
- if (translations) {
- return translations.en;
- }
- };
- /**
- * Validate
- */
- Font.prototype.validate = function() {
- var _this = this;
- function assert(predicate, message) {
- }
- function assertNamePresent(name) {
- var englishName = _this.getEnglishName(name);
- assert(englishName && englishName.trim().length > 0);
- }
- // Identification information
- assertNamePresent('fontFamily');
- assertNamePresent('weightName');
- assertNamePresent('manufacturer');
- assertNamePresent('copyright');
- assertNamePresent('version');
- // Dimension information
- assert(this.unitsPerEm > 0);
- };
- /**
- * Convert the font object to a SFNT data structure.
- * This structure contains all the necessary tables and metadata to create a binary OTF file.
- * @return {opentype.Table}
- */
- Font.prototype.toTables = function() {
- return sfnt.fontToTable(this);
- };
- /**
- * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.
- */
- Font.prototype.toBuffer = function() {
- console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.');
- return this.toArrayBuffer();
- };
- /**
- * Converts a `opentype.Font` into an `ArrayBuffer`
- * @return {ArrayBuffer}
- */
- Font.prototype.toArrayBuffer = function() {
- var sfntTable = this.toTables();
- var bytes = sfntTable.encode();
- var buffer = new ArrayBuffer(bytes.length);
- var intArray = new Uint8Array(buffer);
- for (var i = 0; i < bytes.length; i++) {
- intArray[i] = bytes[i];
- }
- return buffer;
- };
- /**
- * Initiate a download of the OpenType font.
- */
- Font.prototype.download = function(fileName) {
- var familyName = this.getEnglishName('fontFamily');
- var styleName = this.getEnglishName('fontSubfamily');
- fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf';
- var arrayBuffer = this.toArrayBuffer();
- if (isBrowser()) {
- window.URL = window.URL || window.webkitURL;
- if (window.URL) {
- var dataView = new DataView(arrayBuffer);
- var blob = new Blob([dataView], {type: 'font/opentype'});
- var link = document.createElement('a');
- link.href = window.URL.createObjectURL(blob);
- link.download = fileName;
- var event = document.createEvent('MouseEvents');
- event.initEvent('click', true, false);
- link.dispatchEvent(event);
- } else {
- console.warn('Font file could not be downloaded. Try using a different browser.');
- }
- } else {
- var fs = require('fs');
- var buffer = arrayBufferToNodeBuffer(arrayBuffer);
- fs.writeFileSync(fileName, buffer);
- }
- };
- /**
- * @private
- */
- Font.prototype.fsSelectionValues = {
- ITALIC: 0x001, //1
- UNDERSCORE: 0x002, //2
- NEGATIVE: 0x004, //4
- OUTLINED: 0x008, //8
- STRIKEOUT: 0x010, //16
- BOLD: 0x020, //32
- REGULAR: 0x040, //64
- USER_TYPO_METRICS: 0x080, //128
- WWS: 0x100, //256
- OBLIQUE: 0x200 //512
- };
- /**
- * @private
- */
- Font.prototype.usWidthClasses = {
- ULTRA_CONDENSED: 1,
- EXTRA_CONDENSED: 2,
- CONDENSED: 3,
- SEMI_CONDENSED: 4,
- MEDIUM: 5,
- SEMI_EXPANDED: 6,
- EXPANDED: 7,
- EXTRA_EXPANDED: 8,
- ULTRA_EXPANDED: 9
- };
- /**
- * @private
- */
- Font.prototype.usWeightClasses = {
- THIN: 100,
- EXTRA_LIGHT: 200,
- LIGHT: 300,
- NORMAL: 400,
- MEDIUM: 500,
- SEMI_BOLD: 600,
- BOLD: 700,
- EXTRA_BOLD: 800,
- BLACK: 900
- };
- // The `fvar` table stores font variation axes and instances.
- function addName(name, names) {
- var nameString = JSON.stringify(name);
- var nameID = 256;
- for (var nameKey in names) {
- var n = parseInt(nameKey);
- if (!n || n < 256) {
- continue;
- }
- if (JSON.stringify(names[nameKey]) === nameString) {
- return n;
- }
- if (nameID <= n) {
- nameID = n + 1;
- }
- }
- names[nameID] = name;
- return nameID;
- }
- function makeFvarAxis(n, axis, names) {
- var nameID = addName(axis.name, names);
- return [
- {name: 'tag_' + n, type: 'TAG', value: axis.tag},
- {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16},
- {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16},
- {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16},
- {name: 'flags_' + n, type: 'USHORT', value: 0},
- {name: 'nameID_' + n, type: 'USHORT', value: nameID}
- ];
- }
- function parseFvarAxis(data, start, names) {
- var axis = {};
- var p = new parse.Parser(data, start);
- axis.tag = p.parseTag();
- axis.minValue = p.parseFixed();
- axis.defaultValue = p.parseFixed();
- axis.maxValue = p.parseFixed();
- p.skip('uShort', 1); // reserved for flags; no values defined
- axis.name = names[p.parseUShort()] || {};
- return axis;
- }
- function makeFvarInstance(n, inst, axes, names) {
- var nameID = addName(inst.name, names);
- var fields = [
- {name: 'nameID_' + n, type: 'USHORT', value: nameID},
- {name: 'flags_' + n, type: 'USHORT', value: 0}
- ];
- for (var i = 0; i < axes.length; ++i) {
- var axisTag = axes[i].tag;
- fields.push({
- name: 'axis_' + n + ' ' + axisTag,
- type: 'FIXED',
- value: inst.coordinates[axisTag] << 16
- });
- }
- return fields;
- }
- function parseFvarInstance(data, start, axes, names) {
- var inst = {};
- var p = new parse.Parser(data, start);
- inst.name = names[p.parseUShort()] || {};
- p.skip('uShort', 1); // reserved for flags; no values defined
- inst.coordinates = {};
- for (var i = 0; i < axes.length; ++i) {
- inst.coordinates[axes[i].tag] = p.parseFixed();
- }
- return inst;
- }
- function makeFvarTable(fvar, names) {
- var result = new table.Table('fvar', [
- {name: 'version', type: 'ULONG', value: 0x10000},
- {name: 'offsetToData', type: 'USHORT', value: 0},
- {name: 'countSizePairs', type: 'USHORT', value: 2},
- {name: 'axisCount', type: 'USHORT', value: fvar.axes.length},
- {name: 'axisSize', type: 'USHORT', value: 20},
- {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length},
- {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4}
- ]);
- result.offsetToData = result.sizeOf();
- for (var i = 0; i < fvar.axes.length; i++) {
- result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names));
- }
- for (var j = 0; j < fvar.instances.length; j++) {
- result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names));
- }
- return result;
- }
- function parseFvarTable(data, start, names) {
- var p = new parse.Parser(data, start);
- var tableVersion = p.parseULong();
- check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.');
- var offsetToData = p.parseOffset16();
- // Skip countSizePairs.
- p.skip('uShort', 1);
- var axisCount = p.parseUShort();
- var axisSize = p.parseUShort();
- var instanceCount = p.parseUShort();
- var instanceSize = p.parseUShort();
- var axes = [];
- for (var i = 0; i < axisCount; i++) {
- axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names));
- }
- var instances = [];
- var instanceStart = start + offsetToData + axisCount * axisSize;
- for (var j = 0; j < instanceCount; j++) {
- instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names));
- }
- return {axes: axes, instances: instances};
- }
- var fvar = { make: makeFvarTable, parse: parseFvarTable };
- // The `GDEF` table contains various glyph properties
- var attachList = function() {
- return {
- coverage: this.parsePointer(Parser.coverage),
- attachPoints: this.parseList(Parser.pointer(Parser.uShortList))
- };
- };
- var caretValue = function() {
- var format = this.parseUShort();
- check.argument(format === 1 || format === 2 || format === 3,
- 'Unsupported CaretValue table version.');
- if (format === 1) {
- return { coordinate: this.parseShort() };
- } else if (format === 2) {
- return { pointindex: this.parseShort() };
- } else if (format === 3) {
- // Device / Variation Index tables unsupported
- return { coordinate: this.parseShort() };
- }
- };
- var ligGlyph = function() {
- return this.parseList(Parser.pointer(caretValue));
- };
- var ligCaretList = function() {
- return {
- coverage: this.parsePointer(Parser.coverage),
- ligGlyphs: this.parseList(Parser.pointer(ligGlyph))
- };
- };
- var markGlyphSets = function() {
- this.parseUShort(); // Version
- return this.parseList(Parser.pointer(Parser.coverage));
- };
- function parseGDEFTable(data, start) {
- start = start || 0;
- var p = new Parser(data, start);
- var tableVersion = p.parseVersion(1);
- check.argument(tableVersion === 1 || tableVersion === 1.2 || tableVersion === 1.3,
- 'Unsupported GDEF table version.');
- var gdef = {
- version: tableVersion,
- classDef: p.parsePointer(Parser.classDef),
- attachList: p.parsePointer(attachList),
- ligCaretList: p.parsePointer(ligCaretList),
- markAttachClassDef: p.parsePointer(Parser.classDef)
- };
- if (tableVersion >= 1.2) {
- gdef.markGlyphSets = p.parsePointer(markGlyphSets);
- }
- return gdef;
- }
- var gdef = { parse: parseGDEFTable };
- // The `GPOS` table contains kerning pairs, among other things.
- var subtableParsers$1 = new Array(10); // subtableParsers[0] is unused
- // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable
- // this = Parser instance
- subtableParsers$1[1] = function parseLookup1() {
- var start = this.offset + this.relativeOffset;
- var posformat = this.parseUShort();
- if (posformat === 1) {
- return {
- posFormat: 1,
- coverage: this.parsePointer(Parser.coverage),
- value: this.parseValueRecord()
- };
- } else if (posformat === 2) {
- return {
- posFormat: 2,
- coverage: this.parsePointer(Parser.coverage),
- values: this.parseValueRecordList()
- };
- }
- check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.');
- };
- // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable
- subtableParsers$1[2] = function parseLookup2() {
- var start = this.offset + this.relativeOffset;
- var posFormat = this.parseUShort();
- check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.');
- var coverage = this.parsePointer(Parser.coverage);
- var valueFormat1 = this.parseUShort();
- var valueFormat2 = this.parseUShort();
- if (posFormat === 1) {
- // Adjustments for Glyph Pairs
- return {
- posFormat: posFormat,
- coverage: coverage,
- valueFormat1: valueFormat1,
- valueFormat2: valueFormat2,
- pairSets: this.parseList(Parser.pointer(Parser.list(function() {
- return { // pairValueRecord
- secondGlyph: this.parseUShort(),
- value1: this.parseValueRecord(valueFormat1),
- value2: this.parseValueRecord(valueFormat2)
- };
- })))
- };
- } else if (posFormat === 2) {
- var classDef1 = this.parsePointer(Parser.classDef);
- var classDef2 = this.parsePointer(Parser.classDef);
- var class1Count = this.parseUShort();
- var class2Count = this.parseUShort();
- return {
- // Class Pair Adjustment
- posFormat: posFormat,
- coverage: coverage,
- valueFormat1: valueFormat1,
- valueFormat2: valueFormat2,
- classDef1: classDef1,
- classDef2: classDef2,
- class1Count: class1Count,
- class2Count: class2Count,
- classRecords: this.parseList(class1Count, Parser.list(class2Count, function() {
- return {
- value1: this.parseValueRecord(valueFormat1),
- value2: this.parseValueRecord(valueFormat2)
- };
- }))
- };
- }
- };
- subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; };
- subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; };
- subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; };
- subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; };
- subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; };
- subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; };
- subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; };
- // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos
- function parseGposTable(data, start) {
- start = start || 0;
- var p = new Parser(data, start);
- var tableVersion = p.parseVersion(1);
- check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion);
- if (tableVersion === 1) {
- return {
- version: tableVersion,
- scripts: p.parseScriptList(),
- features: p.parseFeatureList(),
- lookups: p.parseLookupList(subtableParsers$1)
- };
- } else {
- return {
- version: tableVersion,
- scripts: p.parseScriptList(),
- features: p.parseFeatureList(),
- lookups: p.parseLookupList(subtableParsers$1),
- variations: p.parseFeatureVariationsList()
- };
- }
- }
- // GPOS Writing //////////////////////////////////////////////
- // NOT SUPPORTED
- var subtableMakers$1 = new Array(10);
- function makeGposTable(gpos) {
- return new table.Table('GPOS', [
- {name: 'version', type: 'ULONG', value: 0x10000},
- {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)},
- {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)},
- {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)}
- ]);
- }
- var gpos = { parse: parseGposTable, make: makeGposTable };
- // The `kern` table contains kerning pairs.
- function parseWindowsKernTable(p) {
- var pairs = {};
- // Skip nTables.
- p.skip('uShort');
- var subtableVersion = p.parseUShort();
- check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.');
- // Skip subtableLength, subtableCoverage
- p.skip('uShort', 2);
- var nPairs = p.parseUShort();
- // Skip searchRange, entrySelector, rangeShift.
- p.skip('uShort', 3);
- for (var i = 0; i < nPairs; i += 1) {
- var leftIndex = p.parseUShort();
- var rightIndex = p.parseUShort();
- var value = p.parseShort();
- pairs[leftIndex + ',' + rightIndex] = value;
- }
- return pairs;
- }
- function parseMacKernTable(p) {
- var pairs = {};
- // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits.
- // Skip the rest.
- p.skip('uShort');
- var nTables = p.parseULong();
- //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').');
- if (nTables > 1) {
- console.warn('Only the first kern subtable is supported.');
- }
- p.skip('uLong');
- var coverage = p.parseUShort();
- var subtableVersion = coverage & 0xFF;
- p.skip('uShort');
- if (subtableVersion === 0) {
- var nPairs = p.parseUShort();
- // Skip searchRange, entrySelector, rangeShift.
- p.skip('uShort', 3);
- for (var i = 0; i < nPairs; i += 1) {
- var leftIndex = p.parseUShort();
- var rightIndex = p.parseUShort();
- var value = p.parseShort();
- pairs[leftIndex + ',' + rightIndex] = value;
- }
- }
- return pairs;
- }
- // Parse the `kern` table which contains kerning pairs.
- function parseKernTable(data, start) {
- var p = new parse.Parser(data, start);
- var tableVersion = p.parseUShort();
- if (tableVersion === 0) {
- return parseWindowsKernTable(p);
- } else if (tableVersion === 1) {
- return parseMacKernTable(p);
- } else {
- throw new Error('Unsupported kern table version (' + tableVersion + ').');
- }
- }
- var kern = { parse: parseKernTable };
- // The `loca` table stores the offsets to the locations of the glyphs in the font.
- // Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font,
- // relative to the beginning of the glyphData table.
- // The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs)
- // The loca table has two versions: a short version where offsets are stored as uShorts, and a long
- // version where offsets are stored as uLongs. The `head` table specifies which version to use
- // (under indexToLocFormat).
- function parseLocaTable(data, start, numGlyphs, shortVersion) {
- var p = new parse.Parser(data, start);
- var parseFn = shortVersion ? p.parseUShort : p.parseULong;
- // There is an extra entry after the last index element to compute the length of the last glyph.
- // That's why we use numGlyphs + 1.
- var glyphOffsets = [];
- for (var i = 0; i < numGlyphs + 1; i += 1) {
- var glyphOffset = parseFn.call(p);
- if (shortVersion) {
- // The short table version stores the actual offset divided by 2.
- glyphOffset *= 2;
- }
- glyphOffsets.push(glyphOffset);
- }
- return glyphOffsets;
- }
- var loca = { parse: parseLocaTable };
- // opentype.js
- /**
- * The opentype library.
- * @namespace opentype
- */
- // File loaders /////////////////////////////////////////////////////////
- /**
- * Loads a font from a file. The callback throws an error message as the first parameter if it fails
- * and the font as an ArrayBuffer in the second parameter if it succeeds.
- * @param {string} path - The path of the file
- * @param {Function} callback - The function to call when the font load completes
- */
- function loadFromFile(path, callback) {
- var fs = require('fs');
- fs.readFile(path, function(err, buffer) {
- if (err) {
- return callback(err.message);
- }
- callback(null, nodeBufferToArrayBuffer(buffer));
- });
- }
- /**
- * Loads a font from a URL. The callback throws an error message as the first parameter if it fails
- * and the font as an ArrayBuffer in the second parameter if it succeeds.
- * @param {string} url - The URL of the font file.
- * @param {Function} callback - The function to call when the font load completes
- */
- function loadFromUrl(url, callback) {
- var request = new XMLHttpRequest();
- request.open('get', url, true);
- request.responseType = 'arraybuffer';
- request.onload = function() {
- if (request.response) {
- return callback(null, request.response);
- } else {
- return callback('Font could not be loaded: ' + request.statusText);
- }
- };
- request.onerror = function () {
- callback('Font could not be loaded');
- };
- request.send();
- }
- // Table Directory Entries //////////////////////////////////////////////
- /**
- * Parses OpenType table entries.
- * @param {DataView}
- * @param {Number}
- * @return {Object[]}
- */
- function parseOpenTypeTableEntries(data, numTables) {
- var tableEntries = [];
- var p = 12;
- for (var i = 0; i < numTables; i += 1) {
- var tag = parse.getTag(data, p);
- var checksum = parse.getULong(data, p + 4);
- var offset = parse.getULong(data, p + 8);
- var length = parse.getULong(data, p + 12);
- tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false});
- p += 16;
- }
- return tableEntries;
- }
- /**
- * Parses WOFF table entries.
- * @param {DataView}
- * @param {Number}
- * @return {Object[]}
- */
- function parseWOFFTableEntries(data, numTables) {
- var tableEntries = [];
- var p = 44; // offset to the first table directory entry.
- for (var i = 0; i < numTables; i += 1) {
- var tag = parse.getTag(data, p);
- var offset = parse.getULong(data, p + 4);
- var compLength = parse.getULong(data, p + 8);
- var origLength = parse.getULong(data, p + 12);
- var compression = (void 0);
- if (compLength < origLength) {
- compression = 'WOFF';
- } else {
- compression = false;
- }
- tableEntries.push({tag: tag, offset: offset, compression: compression,
- compressedLength: compLength, length: origLength});
- p += 20;
- }
- return tableEntries;
- }
- /**
- * @typedef TableData
- * @type Object
- * @property {DataView} data - The DataView
- * @property {number} offset - The data offset.
- */
- /**
- * @param {DataView}
- * @param {Object}
- * @return {TableData}
- */
- function uncompressTable(data, tableEntry) {
- if (tableEntry.compression === 'WOFF') {
- var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2);
- var outBuffer = new Uint8Array(tableEntry.length);
- tinyInflate(inBuffer, outBuffer);
- if (outBuffer.byteLength !== tableEntry.length) {
- throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length');
- }
- var view = new DataView(outBuffer.buffer, 0);
- return {data: view, offset: 0};
- } else {
- return {data: data, offset: tableEntry.offset};
- }
- }
- // Public API ///////////////////////////////////////////////////////////
- /**
- * Parse the OpenType file data (as an ArrayBuffer) and return a Font object.
- * Throws an error if the font could not be parsed.
- * @param {ArrayBuffer}
- * @param {Object} opt - options for parsing
- * @return {opentype.Font}
- */
- function parseBuffer(buffer, opt) {
- opt = (opt === undefined || opt === null) ? {} : opt;
- var indexToLocFormat;
- var ltagTable;
- // Since the constructor can also be called to create new fonts from scratch, we indicate this
- // should be an empty font that we'll fill with our own data.
- var font = new Font({empty: true});
- // OpenType fonts use big endian byte ordering.
- // We can't rely on typed array view types, because they operate with the endianness of the host computer.
- // Instead we use DataViews where we can specify endianness.
- var data = new DataView(buffer, 0);
- var numTables;
- var tableEntries = [];
- var signature = parse.getTag(data, 0);
- if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') {
- font.outlinesFormat = 'truetype';
- numTables = parse.getUShort(data, 4);
- tableEntries = parseOpenTypeTableEntries(data, numTables);
- } else if (signature === 'OTTO') {
- font.outlinesFormat = 'cff';
- numTables = parse.getUShort(data, 4);
- tableEntries = parseOpenTypeTableEntries(data, numTables);
- } else if (signature === 'wOFF') {
- var flavor = parse.getTag(data, 4);
- if (flavor === String.fromCharCode(0, 1, 0, 0)) {
- font.outlinesFormat = 'truetype';
- } else if (flavor === 'OTTO') {
- font.outlinesFormat = 'cff';
- } else {
- throw new Error('Unsupported OpenType flavor ' + signature);
- }
- numTables = parse.getUShort(data, 12);
- tableEntries = parseWOFFTableEntries(data, numTables);
- } else {
- throw new Error('Unsupported OpenType signature ' + signature);
- }
- var cffTableEntry;
- var fvarTableEntry;
- var glyfTableEntry;
- var gdefTableEntry;
- var gposTableEntry;
- var gsubTableEntry;
- var hmtxTableEntry;
- var kernTableEntry;
- var locaTableEntry;
- var nameTableEntry;
- var metaTableEntry;
- var p;
- for (var i = 0; i < numTables; i += 1) {
- var tableEntry = tableEntries[i];
- var table = (void 0);
- switch (tableEntry.tag) {
- case 'cmap':
- table = uncompressTable(data, tableEntry);
- font.tables.cmap = cmap.parse(table.data, table.offset);
- font.encoding = new CmapEncoding(font.tables.cmap);
- break;
- case 'cvt ' :
- table = uncompressTable(data, tableEntry);
- p = new parse.Parser(table.data, table.offset);
- font.tables.cvt = p.parseShortList(tableEntry.length / 2);
- break;
- case 'fvar':
- fvarTableEntry = tableEntry;
- break;
- case 'fpgm' :
- table = uncompressTable(data, tableEntry);
- p = new parse.Parser(table.data, table.offset);
- font.tables.fpgm = p.parseByteList(tableEntry.length);
- break;
- case 'head':
- table = uncompressTable(data, tableEntry);
- font.tables.head = head.parse(table.data, table.offset);
- font.unitsPerEm = font.tables.head.unitsPerEm;
- indexToLocFormat = font.tables.head.indexToLocFormat;
- break;
- case 'hhea':
- table = uncompressTable(data, tableEntry);
- font.tables.hhea = hhea.parse(table.data, table.offset);
- font.ascender = font.tables.hhea.ascender;
- font.descender = font.tables.hhea.descender;
- font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics;
- break;
- case 'hmtx':
- hmtxTableEntry = tableEntry;
- break;
- case 'ltag':
- table = uncompressTable(data, tableEntry);
- ltagTable = ltag.parse(table.data, table.offset);
- break;
- case 'COLR':
- table = uncompressTable(data, tableEntry);
- font.tables.colr = colr.parse(table.data, table.offset);
- break;
- case 'CPAL':
- table = uncompressTable(data, tableEntry);
- font.tables.cpal = cpal.parse(table.data, table.offset);
- break;
- case 'maxp':
- table = uncompressTable(data, tableEntry);
- font.tables.maxp = maxp.parse(table.data, table.offset);
- font.numGlyphs = font.tables.maxp.numGlyphs;
- break;
- case 'name':
- nameTableEntry = tableEntry;
- break;
- case 'OS/2':
- table = uncompressTable(data, tableEntry);
- font.tables.os2 = os2.parse(table.data, table.offset);
- break;
- case 'post':
- table = uncompressTable(data, tableEntry);
- font.tables.post = post.parse(table.data, table.offset);
- font.glyphNames = new GlyphNames(font.tables.post);
- break;
- case 'prep' :
- table = uncompressTable(data, tableEntry);
- p = new parse.Parser(table.data, table.offset);
- font.tables.prep = p.parseByteList(tableEntry.length);
- break;
- case 'glyf':
- glyfTableEntry = tableEntry;
- break;
- case 'loca':
- locaTableEntry = tableEntry;
- break;
- case 'CFF ':
- cffTableEntry = tableEntry;
- break;
- case 'kern':
- kernTableEntry = tableEntry;
- break;
- case 'GDEF':
- gdefTableEntry = tableEntry;
- break;
- case 'GPOS':
- gposTableEntry = tableEntry;
- break;
- case 'GSUB':
- gsubTableEntry = tableEntry;
- break;
- case 'meta':
- metaTableEntry = tableEntry;
- break;
- }
- }
- var nameTable = uncompressTable(data, nameTableEntry);
- font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable);
- font.names = font.tables.name;
- if (glyfTableEntry && locaTableEntry) {
- var shortVersion = indexToLocFormat === 0;
- var locaTable = uncompressTable(data, locaTableEntry);
- var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion);
- var glyfTable = uncompressTable(data, glyfTableEntry);
- font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt);
- } else if (cffTableEntry) {
- var cffTable = uncompressTable(data, cffTableEntry);
- cff.parse(cffTable.data, cffTable.offset, font, opt);
- } else {
- throw new Error('Font doesn\'t contain TrueType or CFF outlines.');
- }
- var hmtxTable = uncompressTable(data, hmtxTableEntry);
- hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt);
- addGlyphNames(font, opt);
- if (kernTableEntry) {
- var kernTable = uncompressTable(data, kernTableEntry);
- font.kerningPairs = kern.parse(kernTable.data, kernTable.offset);
- } else {
- font.kerningPairs = {};
- }
- if (gdefTableEntry) {
- var gdefTable = uncompressTable(data, gdefTableEntry);
- font.tables.gdef = gdef.parse(gdefTable.data, gdefTable.offset);
- }
- if (gposTableEntry) {
- var gposTable = uncompressTable(data, gposTableEntry);
- font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset);
- font.position.init();
- }
- if (gsubTableEntry) {
- var gsubTable = uncompressTable(data, gsubTableEntry);
- font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset);
- }
- if (fvarTableEntry) {
- var fvarTable = uncompressTable(data, fvarTableEntry);
- font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names);
- }
- if (metaTableEntry) {
- var metaTable = uncompressTable(data, metaTableEntry);
- font.tables.meta = meta.parse(metaTable.data, metaTable.offset);
- font.metas = font.tables.meta;
- }
- return font;
- }
- /**
- * Asynchronously load the font from a URL or a filesystem. When done, call the callback
- * with two arguments `(err, font)`. The `err` will be null on success,
- * the `font` is a Font object.
- * We use the node.js callback convention so that
- * opentype.js can integrate with frameworks like async.js.
- * @alias opentype.load
- * @param {string} url - The URL of the font to load.
- * @param {Function} callback - The callback.
- */
- function load(url, callback, opt) {
- opt = (opt === undefined || opt === null) ? {} : opt;
- var isNode = typeof window === 'undefined';
- var loadFn = isNode && !opt.isUrl ? loadFromFile : loadFromUrl;
- return new Promise(function (resolve, reject) {
- loadFn(url, function(err, arrayBuffer) {
- if (err) {
- if (callback) {
- return callback(err);
- } else {
- reject(err);
- }
- }
- var font;
- try {
- font = parseBuffer(arrayBuffer, opt);
- } catch (e) {
- if (callback) {
- return callback(e, null);
- } else {
- reject(e);
- }
- }
- if (callback) {
- return callback(null, font);
- } else {
- resolve(font);
- }
- });
- });
- }
- /**
- * Synchronously load the font from a URL or file.
- * When done, returns the font object or throws an error.
- * @alias opentype.loadSync
- * @param {string} url - The URL of the font to load.
- * @param {Object} opt - opt.lowMemory
- * @return {opentype.Font}
- */
- function loadSync(url, opt) {
- var fs = require('fs');
- var buffer = fs.readFileSync(url);
- return parseBuffer(nodeBufferToArrayBuffer(buffer), opt);
- }
- var opentype = /*#__PURE__*/Object.freeze({
- __proto__: null,
- Font: Font,
- Glyph: Glyph,
- Path: Path,
- BoundingBox: BoundingBox,
- _parse: parse,
- parse: parseBuffer,
- load: load,
- loadSync: loadSync
- });
- export default opentype;
- export { BoundingBox, Font, Glyph, Path, parse as _parse, load, loadSync, parseBuffer as parse };
|