scripts.js 1.1 MB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981209822098320984209852098620987209882098920990209912099220993209942099520996209972099820999210002100121002210032100421005210062100721008210092101021011210122101321014210152101621017210182101921020210212102221023210242102521026210272102821029210302103121032210332103421035210362103721038210392104021041210422104321044210452104621047210482104921050210512105221053210542105521056210572105821059210602106121062210632106421065210662106721068210692107021071210722107321074210752107621077210782107921080210812108221083210842108521086210872108821089210902109121092210932109421095210962109721098210992110021101211022110321104211052110621107211082110921110211112111221113211142111521116211172111821119211202112121122211232112421125211262112721128211292113021131211322113321134211352113621137211382113921140211412114221143211442114521146211472114821149211502115121152211532115421155211562115721158211592116021161211622116321164211652116621167211682116921170211712117221173211742117521176211772117821179211802118121182211832118421185211862118721188211892119021191211922119321194211952119621197211982119921200212012120221203212042120521206212072120821209212102121121212212132121421215212162121721218212192122021221212222122321224212252122621227212282122921230212312123221233212342123521236212372123821239212402124121242212432124421245212462124721248212492125021251212522125321254212552125621257212582125921260212612126221263212642126521266212672126821269212702127121272212732127421275212762127721278212792128021281212822128321284212852128621287212882128921290212912129221293212942129521296212972129821299213002130121302213032130421305213062130721308213092131021311213122131321314213152131621317213182131921320213212132221323213242132521326213272132821329213302133121332213332133421335213362133721338213392134021341213422134321344213452134621347213482134921350213512135221353213542135521356213572135821359213602136121362213632136421365213662136721368213692137021371213722137321374213752137621377213782137921380213812138221383213842138521386213872138821389213902139121392213932139421395213962139721398213992140021401214022140321404214052140621407214082140921410214112141221413214142141521416214172141821419214202142121422214232142421425214262142721428214292143021431214322143321434214352143621437214382143921440214412144221443214442144521446214472144821449214502145121452214532145421455214562145721458214592146021461214622146321464214652146621467214682146921470214712147221473214742147521476214772147821479214802148121482214832148421485214862148721488214892149021491214922149321494214952149621497214982149921500215012150221503215042150521506215072150821509215102151121512215132151421515215162151721518215192152021521215222152321524215252152621527215282152921530215312153221533215342153521536215372153821539215402154121542215432154421545215462154721548215492155021551215522155321554215552155621557215582155921560215612156221563215642156521566215672156821569215702157121572215732157421575215762157721578215792158021581215822158321584215852158621587215882158921590215912159221593215942159521596215972159821599216002160121602216032160421605216062160721608216092161021611216122161321614216152161621617216182161921620216212162221623216242162521626216272162821629216302163121632216332163421635216362163721638216392164021641216422164321644216452164621647216482164921650216512165221653216542165521656216572165821659216602166121662216632166421665216662166721668216692167021671216722167321674216752167621677216782167921680216812168221683216842168521686216872168821689216902169121692216932169421695216962169721698216992170021701217022170321704217052170621707217082170921710217112171221713217142171521716217172171821719217202172121722217232172421725217262172721728217292173021731217322173321734217352173621737217382173921740217412174221743217442174521746217472174821749217502175121752217532175421755217562175721758217592176021761217622176321764217652176621767217682176921770217712177221773217742177521776217772177821779217802178121782217832178421785217862178721788217892179021791217922179321794217952179621797217982179921800218012180221803218042180521806218072180821809218102181121812218132181421815218162181721818218192182021821218222182321824218252182621827218282182921830218312183221833218342183521836218372183821839218402184121842218432184421845218462184721848218492185021851218522185321854218552185621857218582185921860218612186221863218642186521866218672186821869218702187121872218732187421875218762187721878218792188021881218822188321884218852188621887218882188921890218912189221893218942189521896218972189821899219002190121902219032190421905219062190721908219092191021911219122191321914219152191621917219182191921920219212192221923219242192521926219272192821929219302193121932219332193421935219362193721938219392194021941219422194321944219452194621947219482194921950219512195221953219542195521956219572195821959219602196121962219632196421965219662196721968219692197021971219722197321974219752197621977219782197921980219812198221983219842198521986219872198821989219902199121992219932199421995219962199721998219992200022001220022200322004220052200622007220082200922010220112201222013220142201522016220172201822019220202202122022220232202422025220262202722028220292203022031220322203322034220352203622037220382203922040220412204222043220442204522046220472204822049220502205122052220532205422055220562205722058220592206022061220622206322064220652206622067220682206922070220712207222073220742207522076220772207822079220802208122082220832208422085220862208722088220892209022091220922209322094220952209622097220982209922100221012210222103221042210522106221072210822109221102211122112221132211422115221162211722118221192212022121221222212322124221252212622127221282212922130221312213222133221342213522136221372213822139221402214122142221432214422145221462214722148221492215022151221522215322154221552215622157221582215922160221612216222163221642216522166221672216822169221702217122172221732217422175221762217722178221792218022181221822218322184221852218622187221882218922190221912219222193221942219522196221972219822199222002220122202222032220422205222062220722208222092221022211222122221322214222152221622217222182221922220222212222222223222242222522226222272222822229222302223122232222332223422235222362223722238222392224022241222422224322244222452224622247222482224922250222512225222253222542225522256222572225822259222602226122262222632226422265222662226722268222692227022271222722227322274222752227622277222782227922280222812228222283222842228522286222872228822289222902229122292222932229422295222962229722298222992230022301223022230322304223052230622307223082230922310223112231222313223142231522316223172231822319223202232122322223232232422325223262232722328223292233022331223322233322334223352233622337223382233922340223412234222343223442234522346223472234822349223502235122352223532235422355223562235722358223592236022361223622236322364223652236622367223682236922370223712237222373223742237522376223772237822379223802238122382223832238422385223862238722388223892239022391223922239322394223952239622397223982239922400224012240222403224042240522406224072240822409224102241122412224132241422415224162241722418224192242022421224222242322424224252242622427224282242922430224312243222433224342243522436224372243822439224402244122442224432244422445224462244722448224492245022451224522245322454224552245622457224582245922460224612246222463224642246522466224672246822469224702247122472224732247422475224762247722478224792248022481224822248322484224852248622487224882248922490224912249222493224942249522496224972249822499225002250122502225032250422505225062250722508225092251022511225122251322514225152251622517225182251922520225212252222523225242252522526225272252822529225302253122532225332253422535225362253722538225392254022541225422254322544225452254622547225482254922550225512255222553225542255522556225572255822559225602256122562225632256422565225662256722568225692257022571225722257322574225752257622577225782257922580225812258222583225842258522586225872258822589225902259122592225932259422595225962259722598225992260022601226022260322604226052260622607226082260922610226112261222613226142261522616226172261822619226202262122622226232262422625226262262722628226292263022631226322263322634226352263622637226382263922640226412264222643226442264522646226472264822649226502265122652226532265422655226562265722658226592266022661226622266322664226652266622667226682266922670226712267222673226742267522676226772267822679226802268122682226832268422685226862268722688226892269022691226922269322694226952269622697226982269922700227012270222703227042270522706227072270822709227102271122712227132271422715227162271722718227192272022721227222272322724227252272622727227282272922730227312273222733227342273522736227372273822739227402274122742227432274422745227462274722748227492275022751227522275322754227552275622757227582275922760227612276222763227642276522766227672276822769227702277122772227732277422775227762277722778227792278022781227822278322784227852278622787227882278922790227912279222793227942279522796227972279822799228002280122802228032280422805228062280722808228092281022811228122281322814228152281622817228182281922820228212282222823228242282522826228272282822829228302283122832228332283422835228362283722838228392284022841228422284322844228452284622847228482284922850228512285222853228542285522856228572285822859228602286122862228632286422865228662286722868228692287022871228722287322874228752287622877228782287922880228812288222883228842288522886228872288822889228902289122892228932289422895228962289722898228992290022901229022290322904229052290622907229082290922910229112291222913229142291522916229172291822919229202292122922229232292422925229262292722928229292293022931229322293322934229352293622937229382293922940229412294222943229442294522946229472294822949229502295122952229532295422955229562295722958229592296022961229622296322964229652296622967229682296922970229712297222973229742297522976229772297822979229802298122982229832298422985229862298722988229892299022991229922299322994229952299622997229982299923000230012300223003230042300523006230072300823009230102301123012230132301423015230162301723018230192302023021230222302323024230252302623027230282302923030230312303223033230342303523036230372303823039230402304123042230432304423045230462304723048230492305023051230522305323054230552305623057230582305923060230612306223063230642306523066230672306823069230702307123072230732307423075230762307723078230792308023081230822308323084230852308623087230882308923090230912309223093230942309523096230972309823099231002310123102231032310423105231062310723108231092311023111231122311323114231152311623117231182311923120231212312223123231242312523126231272312823129231302313123132231332313423135231362313723138231392314023141231422314323144231452314623147231482314923150231512315223153231542315523156231572315823159231602316123162231632316423165231662316723168231692317023171231722317323174231752317623177231782317923180231812318223183231842318523186231872318823189231902319123192231932319423195231962319723198231992320023201232022320323204232052320623207232082320923210232112321223213232142321523216232172321823219232202322123222232232322423225232262322723228232292323023231232322323323234232352323623237232382323923240232412324223243232442324523246232472324823249232502325123252232532325423255232562325723258232592326023261232622326323264232652326623267232682326923270232712327223273232742327523276232772327823279232802328123282232832328423285232862328723288232892329023291232922329323294232952329623297232982329923300233012330223303233042330523306233072330823309233102331123312233132331423315233162331723318233192332023321233222332323324233252332623327233282332923330233312333223333233342333523336233372333823339233402334123342233432334423345233462334723348233492335023351233522335323354233552335623357233582335923360233612336223363233642336523366233672336823369233702337123372233732337423375233762337723378233792338023381233822338323384233852338623387233882338923390233912339223393233942339523396233972339823399234002340123402234032340423405234062340723408234092341023411234122341323414234152341623417234182341923420234212342223423234242342523426234272342823429234302343123432234332343423435234362343723438234392344023441234422344323444234452344623447234482344923450234512345223453234542345523456234572345823459234602346123462234632346423465234662346723468234692347023471234722347323474234752347623477234782347923480234812348223483234842348523486234872348823489234902349123492234932349423495234962349723498234992350023501235022350323504235052350623507235082350923510235112351223513235142351523516235172351823519235202352123522235232352423525235262352723528235292353023531235322353323534235352353623537235382353923540235412354223543235442354523546235472354823549235502355123552235532355423555235562355723558235592356023561235622356323564235652356623567235682356923570235712357223573235742357523576235772357823579235802358123582235832358423585235862358723588235892359023591235922359323594235952359623597235982359923600236012360223603236042360523606236072360823609236102361123612236132361423615236162361723618236192362023621236222362323624236252362623627236282362923630236312363223633236342363523636236372363823639236402364123642236432364423645236462364723648236492365023651236522365323654236552365623657236582365923660236612366223663236642366523666236672366823669236702367123672236732367423675236762367723678236792368023681236822368323684236852368623687236882368923690236912369223693236942369523696236972369823699237002370123702237032370423705237062370723708237092371023711237122371323714237152371623717237182371923720237212372223723237242372523726237272372823729237302373123732237332373423735237362373723738237392374023741237422374323744237452374623747237482374923750237512375223753237542375523756237572375823759237602376123762237632376423765237662376723768237692377023771237722377323774237752377623777237782377923780237812378223783237842378523786237872378823789237902379123792237932379423795237962379723798237992380023801238022380323804238052380623807238082380923810238112381223813238142381523816238172381823819238202382123822238232382423825238262382723828238292383023831238322383323834238352383623837238382383923840238412384223843238442384523846238472384823849238502385123852238532385423855238562385723858238592386023861238622386323864238652386623867238682386923870238712387223873238742387523876238772387823879238802388123882238832388423885238862388723888238892389023891238922389323894238952389623897238982389923900239012390223903239042390523906239072390823909239102391123912239132391423915239162391723918239192392023921239222392323924239252392623927239282392923930239312393223933239342393523936239372393823939239402394123942239432394423945239462394723948239492395023951239522395323954239552395623957239582395923960239612396223963239642396523966239672396823969239702397123972239732397423975239762397723978239792398023981239822398323984239852398623987239882398923990239912399223993239942399523996239972399823999240002400124002240032400424005240062400724008240092401024011240122401324014240152401624017240182401924020240212402224023240242402524026240272402824029240302403124032240332403424035240362403724038240392404024041240422404324044240452404624047240482404924050240512405224053240542405524056240572405824059240602406124062240632406424065240662406724068240692407024071240722407324074240752407624077240782407924080240812408224083240842408524086240872408824089240902409124092240932409424095240962409724098240992410024101241022410324104241052410624107241082410924110241112411224113241142411524116241172411824119241202412124122241232412424125241262412724128241292413024131241322413324134241352413624137241382413924140241412414224143241442414524146241472414824149241502415124152241532415424155241562415724158241592416024161241622416324164241652416624167241682416924170241712417224173241742417524176241772417824179241802418124182241832418424185241862418724188241892419024191241922419324194241952419624197241982419924200242012420224203242042420524206242072420824209242102421124212242132421424215242162421724218242192422024221242222422324224242252422624227242282422924230242312423224233242342423524236242372423824239242402424124242242432424424245242462424724248242492425024251242522425324254242552425624257242582425924260242612426224263242642426524266242672426824269242702427124272242732427424275242762427724278242792428024281242822428324284242852428624287242882428924290242912429224293242942429524296242972429824299243002430124302243032430424305243062430724308243092431024311243122431324314243152431624317243182431924320243212432224323243242432524326243272432824329243302433124332243332433424335243362433724338243392434024341243422434324344243452434624347243482434924350243512435224353243542435524356243572435824359243602436124362243632436424365243662436724368243692437024371243722437324374243752437624377243782437924380243812438224383243842438524386243872438824389243902439124392243932439424395243962439724398243992440024401244022440324404244052440624407244082440924410244112441224413244142441524416244172441824419244202442124422244232442424425244262442724428244292443024431244322443324434244352443624437244382443924440244412444224443244442444524446244472444824449244502445124452244532445424455244562445724458244592446024461244622446324464244652446624467244682446924470244712447224473244742447524476244772447824479244802448124482244832448424485244862448724488244892449024491244922449324494244952449624497244982449924500245012450224503245042450524506245072450824509245102451124512245132451424515245162451724518245192452024521245222452324524245252452624527245282452924530245312453224533245342453524536245372453824539245402454124542245432454424545245462454724548245492455024551245522455324554245552455624557245582455924560245612456224563245642456524566245672456824569245702457124572245732457424575245762457724578245792458024581245822458324584245852458624587245882458924590245912459224593245942459524596245972459824599246002460124602246032460424605246062460724608246092461024611246122461324614246152461624617246182461924620246212462224623246242462524626246272462824629246302463124632246332463424635246362463724638246392464024641246422464324644246452464624647246482464924650246512465224653246542465524656246572465824659246602466124662246632466424665246662466724668246692467024671246722467324674246752467624677246782467924680246812468224683246842468524686246872468824689246902469124692246932469424695246962469724698246992470024701247022470324704247052470624707247082470924710247112471224713247142471524716247172471824719247202472124722247232472424725247262472724728247292473024731247322473324734247352473624737247382473924740247412474224743247442474524746247472474824749247502475124752247532475424755247562475724758247592476024761247622476324764247652476624767247682476924770247712477224773247742477524776247772477824779247802478124782247832478424785247862478724788247892479024791247922479324794247952479624797247982479924800248012480224803248042480524806248072480824809248102481124812248132481424815248162481724818248192482024821248222482324824248252482624827248282482924830248312483224833248342483524836248372483824839248402484124842248432484424845248462484724848248492485024851248522485324854248552485624857248582485924860248612486224863248642486524866248672486824869248702487124872248732487424875248762487724878248792488024881248822488324884248852488624887248882488924890248912489224893248942489524896248972489824899249002490124902249032490424905249062490724908249092491024911249122491324914249152491624917249182491924920249212492224923249242492524926249272492824929249302493124932249332493424935249362493724938249392494024941249422494324944249452494624947249482494924950249512495224953249542495524956249572495824959249602496124962249632496424965249662496724968249692497024971249722497324974249752497624977249782497924980249812498224983249842498524986249872498824989249902499124992249932499424995249962499724998249992500025001250022500325004250052500625007250082500925010250112501225013250142501525016250172501825019250202502125022250232502425025250262502725028250292503025031250322503325034250352503625037250382503925040250412504225043250442504525046250472504825049250502505125052250532505425055250562505725058250592506025061250622506325064250652506625067250682506925070250712507225073250742507525076250772507825079250802508125082250832508425085250862508725088250892509025091250922509325094250952509625097250982509925100251012510225103251042510525106251072510825109251102511125112251132511425115251162511725118251192512025121251222512325124251252512625127251282512925130251312513225133251342513525136251372513825139251402514125142251432514425145251462514725148251492515025151251522515325154251552515625157251582515925160251612516225163251642516525166251672516825169251702517125172251732517425175251762517725178251792518025181251822518325184251852518625187251882518925190251912519225193251942519525196251972519825199252002520125202252032520425205252062520725208252092521025211252122521325214252152521625217252182521925220252212522225223252242522525226252272522825229252302523125232252332523425235252362523725238252392524025241252422524325244252452524625247252482524925250252512525225253252542525525256252572525825259252602526125262252632526425265252662526725268252692527025271252722527325274252752527625277252782527925280252812528225283252842528525286252872528825289252902529125292252932529425295252962529725298252992530025301253022530325304253052530625307253082530925310253112531225313253142531525316253172531825319253202532125322253232532425325253262532725328253292533025331253322533325334253352533625337253382533925340253412534225343253442534525346253472534825349253502535125352253532535425355253562535725358253592536025361253622536325364253652536625367253682536925370253712537225373253742537525376253772537825379253802538125382253832538425385253862538725388253892539025391253922539325394253952539625397253982539925400254012540225403254042540525406254072540825409254102541125412254132541425415254162541725418254192542025421254222542325424254252542625427254282542925430254312543225433254342543525436254372543825439254402544125442254432544425445254462544725448254492545025451254522545325454254552545625457254582545925460254612546225463254642546525466254672546825469254702547125472254732547425475254762547725478254792548025481254822548325484254852548625487254882548925490254912549225493254942549525496254972549825499255002550125502255032550425505255062550725508255092551025511255122551325514255152551625517255182551925520255212552225523255242552525526255272552825529255302553125532255332553425535255362553725538255392554025541255422554325544255452554625547255482554925550255512555225553255542555525556255572555825559255602556125562255632556425565255662556725568255692557025571255722557325574255752557625577255782557925580255812558225583255842558525586255872558825589255902559125592255932559425595255962559725598255992560025601256022560325604256052560625607256082560925610256112561225613256142561525616256172561825619256202562125622256232562425625256262562725628256292563025631256322563325634256352563625637256382563925640256412564225643256442564525646256472564825649256502565125652256532565425655256562565725658256592566025661256622566325664256652566625667256682566925670256712567225673256742567525676256772567825679256802568125682256832568425685256862568725688256892569025691256922569325694256952569625697256982569925700257012570225703257042570525706257072570825709257102571125712257132571425715257162571725718257192572025721257222572325724257252572625727257282572925730257312573225733257342573525736257372573825739257402574125742257432574425745257462574725748257492575025751257522575325754257552575625757257582575925760257612576225763257642576525766257672576825769257702577125772257732577425775257762577725778257792578025781257822578325784257852578625787257882578925790257912579225793257942579525796257972579825799258002580125802258032580425805258062580725808258092581025811258122581325814258152581625817258182581925820258212582225823258242582525826258272582825829258302583125832258332583425835258362583725838258392584025841258422584325844258452584625847258482584925850258512585225853258542585525856258572585825859258602586125862258632586425865258662586725868258692587025871258722587325874258752587625877258782587925880258812588225883258842588525886258872588825889258902589125892258932589425895258962589725898258992590025901259022590325904259052590625907259082590925910259112591225913259142591525916259172591825919259202592125922259232592425925259262592725928259292593025931259322593325934259352593625937259382593925940259412594225943259442594525946259472594825949259502595125952259532595425955259562595725958259592596025961259622596325964259652596625967259682596925970259712597225973259742597525976259772597825979259802598125982259832598425985259862598725988259892599025991259922599325994259952599625997259982599926000260012600226003260042600526006260072600826009260102601126012260132601426015260162601726018260192602026021260222602326024260252602626027260282602926030260312603226033260342603526036260372603826039260402604126042260432604426045260462604726048260492605026051260522605326054260552605626057260582605926060260612606226063260642606526066260672606826069260702607126072260732607426075260762607726078260792608026081260822608326084260852608626087260882608926090260912609226093260942609526096260972609826099261002610126102261032610426105261062610726108261092611026111261122611326114261152611626117261182611926120261212612226123261242612526126261272612826129261302613126132261332613426135261362613726138261392614026141261422614326144261452614626147261482614926150261512615226153261542615526156261572615826159261602616126162261632616426165261662616726168261692617026171261722617326174261752617626177261782617926180261812618226183261842618526186261872618826189261902619126192261932619426195261962619726198261992620026201262022620326204262052620626207262082620926210262112621226213262142621526216262172621826219262202622126222262232622426225262262622726228262292623026231262322623326234262352623626237262382623926240262412624226243262442624526246262472624826249262502625126252262532625426255262562625726258262592626026261262622626326264262652626626267262682626926270262712627226273262742627526276262772627826279262802628126282262832628426285262862628726288262892629026291262922629326294262952629626297262982629926300263012630226303263042630526306263072630826309263102631126312263132631426315263162631726318263192632026321263222632326324263252632626327263282632926330263312633226333263342633526336263372633826339263402634126342263432634426345263462634726348263492635026351263522635326354263552635626357263582635926360263612636226363263642636526366263672636826369263702637126372263732637426375263762637726378263792638026381263822638326384263852638626387263882638926390263912639226393263942639526396263972639826399264002640126402264032640426405264062640726408264092641026411264122641326414264152641626417264182641926420264212642226423264242642526426264272642826429264302643126432264332643426435264362643726438264392644026441264422644326444264452644626447264482644926450264512645226453264542645526456264572645826459264602646126462264632646426465264662646726468264692647026471264722647326474264752647626477264782647926480264812648226483264842648526486264872648826489264902649126492264932649426495264962649726498264992650026501265022650326504265052650626507265082650926510265112651226513265142651526516265172651826519265202652126522265232652426525265262652726528265292653026531265322653326534265352653626537265382653926540265412654226543265442654526546265472654826549265502655126552265532655426555265562655726558265592656026561265622656326564265652656626567265682656926570265712657226573265742657526576265772657826579265802658126582265832658426585265862658726588265892659026591265922659326594265952659626597265982659926600266012660226603266042660526606266072660826609266102661126612266132661426615266162661726618266192662026621266222662326624266252662626627266282662926630266312663226633266342663526636266372663826639266402664126642266432664426645266462664726648266492665026651266522665326654266552665626657266582665926660266612666226663266642666526666266672666826669266702667126672266732667426675266762667726678266792668026681266822668326684266852668626687266882668926690266912669226693266942669526696266972669826699267002670126702267032670426705267062670726708267092671026711267122671326714267152671626717267182671926720267212672226723267242672526726267272672826729267302673126732267332673426735267362673726738267392674026741267422674326744267452674626747267482674926750267512675226753267542675526756267572675826759267602676126762267632676426765267662676726768267692677026771267722677326774267752677626777267782677926780267812678226783267842678526786267872678826789267902679126792267932679426795267962679726798267992680026801268022680326804268052680626807268082680926810268112681226813268142681526816268172681826819268202682126822268232682426825268262682726828268292683026831268322683326834268352683626837268382683926840268412684226843268442684526846268472684826849268502685126852268532685426855268562685726858268592686026861268622686326864268652686626867268682686926870268712687226873268742687526876268772687826879268802688126882268832688426885268862688726888268892689026891268922689326894268952689626897268982689926900269012690226903269042690526906269072690826909269102691126912269132691426915269162691726918269192692026921269222692326924269252692626927269282692926930269312693226933269342693526936269372693826939269402694126942269432694426945269462694726948269492695026951269522695326954269552695626957269582695926960269612696226963269642696526966269672696826969269702697126972269732697426975269762697726978269792698026981269822698326984269852698626987269882698926990269912699226993269942699526996269972699826999270002700127002270032700427005270062700727008270092701027011270122701327014270152701627017270182701927020270212702227023270242702527026270272702827029270302703127032270332703427035270362703727038270392704027041270422704327044270452704627047270482704927050270512705227053270542705527056270572705827059270602706127062270632706427065270662706727068270692707027071270722707327074270752707627077270782707927080270812708227083270842708527086270872708827089270902709127092270932709427095270962709727098270992710027101271022710327104271052710627107271082710927110271112711227113271142711527116271172711827119271202712127122271232712427125271262712727128271292713027131271322713327134271352713627137271382713927140271412714227143271442714527146271472714827149271502715127152271532715427155271562715727158271592716027161271622716327164271652716627167271682716927170271712717227173271742717527176271772717827179271802718127182271832718427185271862718727188271892719027191271922719327194271952719627197271982719927200272012720227203272042720527206272072720827209272102721127212272132721427215272162721727218272192722027221272222722327224272252722627227272282722927230272312723227233272342723527236272372723827239272402724127242272432724427245272462724727248272492725027251272522725327254272552725627257272582725927260272612726227263272642726527266272672726827269272702727127272272732727427275272762727727278272792728027281272822728327284272852728627287272882728927290272912729227293272942729527296272972729827299273002730127302273032730427305273062730727308273092731027311273122731327314273152731627317273182731927320273212732227323273242732527326273272732827329273302733127332273332733427335273362733727338273392734027341273422734327344273452734627347273482734927350273512735227353273542735527356273572735827359273602736127362273632736427365273662736727368273692737027371273722737327374273752737627377273782737927380273812738227383273842738527386273872738827389273902739127392273932739427395273962739727398273992740027401274022740327404274052740627407274082740927410274112741227413274142741527416274172741827419274202742127422274232742427425274262742727428274292743027431274322743327434274352743627437274382743927440274412744227443274442744527446274472744827449274502745127452274532745427455274562745727458274592746027461274622746327464274652746627467274682746927470274712747227473274742747527476274772747827479274802748127482274832748427485274862748727488274892749027491274922749327494274952749627497274982749927500275012750227503275042750527506275072750827509275102751127512275132751427515275162751727518275192752027521275222752327524275252752627527275282752927530275312753227533275342753527536275372753827539275402754127542275432754427545275462754727548275492755027551275522755327554275552755627557275582755927560275612756227563275642756527566275672756827569275702757127572275732757427575275762757727578275792758027581275822758327584275852758627587275882758927590275912759227593275942759527596275972759827599276002760127602276032760427605276062760727608276092761027611276122761327614276152761627617276182761927620276212762227623276242762527626276272762827629276302763127632276332763427635276362763727638276392764027641276422764327644276452764627647276482764927650276512765227653276542765527656276572765827659276602766127662276632766427665276662766727668276692767027671276722767327674276752767627677276782767927680276812768227683276842768527686276872768827689276902769127692276932769427695276962769727698276992770027701277022770327704277052770627707277082770927710277112771227713277142771527716277172771827719277202772127722277232772427725277262772727728277292773027731277322773327734277352773627737277382773927740277412774227743277442774527746277472774827749277502775127752277532775427755277562775727758277592776027761277622776327764277652776627767277682776927770277712777227773277742777527776277772777827779277802778127782277832778427785277862778727788277892779027791277922779327794277952779627797277982779927800278012780227803278042780527806278072780827809278102781127812278132781427815278162781727818278192782027821278222782327824278252782627827278282782927830278312783227833278342783527836278372783827839278402784127842278432784427845278462784727848278492785027851278522785327854278552785627857278582785927860278612786227863278642786527866278672786827869278702787127872278732787427875278762787727878278792788027881278822788327884278852788627887278882788927890278912789227893278942789527896278972789827899279002790127902279032790427905279062790727908279092791027911279122791327914279152791627917279182791927920279212792227923279242792527926279272792827929279302793127932279332793427935279362793727938279392794027941279422794327944279452794627947279482794927950279512795227953279542795527956279572795827959279602796127962279632796427965279662796727968279692797027971279722797327974279752797627977279782797927980279812798227983279842798527986279872798827989279902799127992279932799427995279962799727998279992800028001280022800328004280052800628007280082800928010280112801228013280142801528016280172801828019280202802128022280232802428025280262802728028280292803028031280322803328034280352803628037280382803928040280412804228043280442804528046280472804828049280502805128052280532805428055280562805728058280592806028061280622806328064280652806628067280682806928070280712807228073280742807528076280772807828079280802808128082280832808428085280862808728088280892809028091280922809328094280952809628097280982809928100281012810228103281042810528106281072810828109281102811128112281132811428115281162811728118281192812028121281222812328124281252812628127281282812928130281312813228133281342813528136281372813828139281402814128142281432814428145281462814728148
  1. /*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */
  2. !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}S.fn=S.prototype={jquery:f,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(n){return this.pushStack(S.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},S.extend=S.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(S.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||S.isPlainObject(n)?n:{},i=!1,a[t]=S.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},S.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=v.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){b(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(p(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(p(Object(e))?S.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(p(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:y}),"function"==typeof Symbol&&(S.fn[Symbol.iterator]=t[Symbol.iterator]),S.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var d=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S="sizzle"+1*new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),D=function(e,t){return e===t&&(l=!0),0},j={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",I="(?:\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",W="\\["+M+"*("+I+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+I+"))|)"+M+"*\\]",F=":("+I+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+W+")*)|.*)\\)|)",B=new RegExp(M+"+","g"),$=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=new RegExp("^"+M+"*,"+M+"*"),z=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="<a id='"+S+"'></a><select id='"+S+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0<se(t,C,null,[e]).length},se.contains=function(e,t){return(e.ownerDocument||e)!=C&&T(e),y(e,t)},se.attr=function(e,t){(e.ownerDocument||e)!=C&&T(e);var n=b.attrHandle[t.toLowerCase()],r=n&&j.call(b.attrHandle,t.toLowerCase())?n(e,t,!E):void 0;return void 0!==r?r:d.attributes||!E?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},se.escape=function(e){return(e+"").replace(re,ie)},se.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},se.uniqueSort=function(e){var t,n=[],r=0,i=0;if(l=!d.detectDuplicates,u=!d.sortStable&&e.slice(0),e.sort(D),l){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return u=null,e},o=se.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else while(t=e[r++])n+=o(t);return n},(b=se.selectors={cacheLength:50,createPseudo:le,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1<t.indexOf(i):"$="===r?i&&t.slice(-i.length)===i:"~="===r?-1<(" "+t.replace(B," ")+" ").indexOf(i):"|="===r&&(t===i||t.slice(0,i.length+1)===i+"-"))}},CHILD:function(h,e,t,g,v){var y="nth"!==h.slice(0,3),m="last"!==h.slice(-4),x="of-type"===e;return 1===g&&0===v?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u,l=y!==m?"nextSibling":"previousSibling",c=e.parentNode,f=x&&e.nodeName.toLowerCase(),p=!n&&!x,d=!1;if(c){if(y){while(l){a=e;while(a=a[l])if(x?a.nodeName.toLowerCase()===f:1===a.nodeType)return!1;u=l="only"===h&&!u&&"nextSibling"}return!0}if(u=[m?c.firstChild:c.lastChild],m&&p){d=(s=(r=(i=(o=(a=c)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1])&&r[2],a=s&&c.childNodes[s];while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if(1===a.nodeType&&++d&&a===e){i[h]=[k,s,d];break}}else if(p&&(d=s=(r=(i=(o=(a=e)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1]),!1===d)while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if((x?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++d&&(p&&((i=(o=a[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[k,d]),a===e))break;return(d-=v)===g||d%g==0&&0<=d/g}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||se.error("unsupported pseudo: "+e);return a[S]?a(o):1<a.length?(t=[e,e,"",o],b.setFilters.hasOwnProperty(e.toLowerCase())?le(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=P(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:le(function(e){var r=[],i=[],s=f(e.replace($,"$1"));return s[S]?le(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:le(function(t){return function(e){return 0<se(t,e).length}}),contains:le(function(t){return t=t.replace(te,ne),function(e){return-1<(e.textContent||o(e)).indexOf(t)}}),lang:le(function(n){return V.test(n||"")||se.error("unsupported lang: "+n),n=n.replace(te,ne).toLowerCase(),function(e){var t;do{if(t=E?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(t=t.toLowerCase())===n||0===t.indexOf(n+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=n.location&&n.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===a},focus:function(e){return e===C.activeElement&&(!C.hasFocus||C.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:ve(function(){return[0]}),last:ve(function(e,t){return[t-1]}),eq:ve(function(e,t,n){return[n<0?n+t:n]}),even:ve(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:ve(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:ve(function(e,t,n){for(var r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:ve(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=de(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=he(e);function me(){}function xe(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function be(s,e,t){var u=e.dir,l=e.next,c=l||u,f=t&&"parentNode"===c,p=r++;return e.first?function(e,t,n){while(e=e[u])if(1===e.nodeType||f)return s(e,t,n);return!1}:function(e,t,n){var r,i,o,a=[k,p];if(n){while(e=e[u])if((1===e.nodeType||f)&&s(e,t,n))return!0}else while(e=e[u])if(1===e.nodeType||f)if(i=(o=e[S]||(e[S]={}))[e.uniqueID]||(o[e.uniqueID]={}),l&&l===e.nodeName.toLowerCase())e=e[u]||e;else{if((r=i[c])&&r[0]===k&&r[1]===p)return a[2]=r[2];if((i[c]=a)[2]=s(e,t,n))return!0}return!1}}function we(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Te(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Ce(d,h,g,v,y,e){return v&&!v[S]&&(v=Ce(v)),y&&!y[S]&&(y=Ce(y,e)),le(function(e,t,n,r){var i,o,a,s=[],u=[],l=t.length,c=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)se(e,t[r],n);return n}(h||"*",n.nodeType?[n]:n,[]),f=!d||!e&&h?c:Te(c,s,d,n,r),p=g?y||(e?d:l||v)?[]:t:f;if(g&&g(f,p,n,r),v){i=Te(p,u),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(p[u[o]]=!(f[u[o]]=a))}if(e){if(y||d){if(y){i=[],o=p.length;while(o--)(a=p[o])&&i.push(f[o]=a);y(null,p=[],i,r)}o=p.length;while(o--)(a=p[o])&&-1<(i=y?P(e,a):s[o])&&(e[i]=!(t[i]=a))}}else p=Te(p===t?p.splice(l,p.length):p),y?y(null,t,p,r):H.apply(t,p)})}function Ee(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[" "],s=o?1:0,u=be(function(e){return e===i},a,!0),l=be(function(e){return-1<P(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!==w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=b.relative[e[s].type])c=[be(we(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[S]){for(n=++s;n<r;n++)if(b.relative[e[n].type])break;return Ce(1<s&&we(c),1<s&&xe(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace($,"$1"),t,s<n&&Ee(e.slice(s,n)),n<r&&Ee(e=e.slice(n)),n<r&&xe(e))}c.push(t)}return we(c)}return me.prototype=b.filters=b.pseudos,b.setFilters=new me,h=se.tokenize=function(e,t){var n,r,i,o,a,s,u,l=x[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=b.preFilter;while(a){for(o in n&&!(r=_.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=z.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace($," ")}),a=a.slice(n.length)),b.filter)!(r=G[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?se.error(e):x(e,s).slice(0)},f=se.compile=function(e,t){var n,v,y,m,x,r,i=[],o=[],a=A[e+" "];if(!a){t||(t=h(e)),n=t.length;while(n--)(a=Ee(t[n]))[S]?i.push(a):o.push(a);(a=A(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l="0",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG("*",i),h=k+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t==C||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){a=0,t||o.ownerDocument==C||(T(o),n=!E);while(s=v[a++])if(s(o,t||C,n)){r.push(o);break}i&&(k=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=q.call(r));f=Te(f)}H.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&se.uniqueSort(r)}return i&&(k=h,w=p),c},m?le(r):r))).selector=e}return a},g=se.select=function(e,t,n,r){var i,o,a,s,u,l="function"==typeof e&&e,c=!r&&h(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&"ID"===(a=o[0]).type&&9===t.nodeType&&E&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(te,ne),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=G.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],b.relative[s=a.type])break;if((u=b.find[s])&&(r=u(a.matches[0].replace(te,ne),ee.test(o[0].type)&&ye(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&xe(o)))return H.apply(n,r),n;break}}}return(l||f(e,c))(r,t,!E,n,!t||ee.test(e)&&ye(t.parentNode)||t),n},d.sortStable=S.split("").sort(D).join("")===S,d.detectDuplicates=!!l,T(),d.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(C.createElement("fieldset"))}),ce(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||fe("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),d.attributes&&ce(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||fe("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ce(function(e){return null==e.getAttribute("disabled")})||fe(R,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),se}(C);S.find=d,S.expr=d.selectors,S.expr[":"]=S.expr.pseudos,S.uniqueSort=S.unique=d.uniqueSort,S.text=d.getText,S.isXMLDoc=d.isXML,S.contains=d.contains,S.escapeSelector=d.escape;var h=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&S(e).is(n))break;r.push(e)}return r},T=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},k=S.expr.match.needsContext;function A(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var N=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1<i.call(n,e)!==r}):S.filter(n,e,r)}S.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?S.find.matchesSelector(r,e)?[r]:[]:S.find.matches(e,S.grep(t,function(e){return 1===e.nodeType}))},S.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(S(e).filter(function(){for(t=0;t<r;t++)if(S.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)S.find(e,i[t],n);return 1<r?S.uniqueSort(n):n},filter:function(e){return this.pushStack(D(this,e||[],!1))},not:function(e){return this.pushStack(D(this,e||[],!0))},is:function(e){return!!D(this,"string"==typeof e&&k.test(e)?S(e):e||[],!1).length}});var j,q=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(S.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&S(e);if(!k.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&S.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?S.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?i.call(S(e),this[0]):i.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(S.uniqueSort(S.merge(this.get(),S(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),S.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return h(e,"parentNode")},parentsUntil:function(e,t,n){return h(e,"parentNode",n)},next:function(e){return O(e,"nextSibling")},prev:function(e){return O(e,"previousSibling")},nextAll:function(e){return h(e,"nextSibling")},prevAll:function(e){return h(e,"previousSibling")},nextUntil:function(e,t,n){return h(e,"nextSibling",n)},prevUntil:function(e,t,n){return h(e,"previousSibling",n)},siblings:function(e){return T((e.parentNode||{}).firstChild,e)},children:function(e){return T(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(A(e,"template")&&(e=e.content||e),S.merge([],e.childNodes))}},function(r,i){S.fn[r]=function(e,t){var n=S.map(this,i,e);return"Until"!==r.slice(-5)&&(t=e),t&&"string"==typeof t&&(n=S.filter(t,n)),1<this.length&&(H[r]||S.uniqueSort(n),L.test(r)&&n.reverse()),this.pushStack(n)}});var P=/[^\x20\t\r\n\f]+/g;function R(e){return e}function M(e){throw e}function I(e,t,n,r){var i;try{e&&m(i=e.promise)?i.call(e).done(t).fail(n):e&&m(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}S.Callbacks=function(r){var e,n;r="string"==typeof r?(e=r,n={},S.each(e.match(P)||[],function(e,t){n[t]=!0}),n):S.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:"")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){S.each(e,function(e,t){m(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&"string"!==w(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return S.each(arguments,function(e,t){var n;while(-1<(n=S.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<S.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t="",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},S.extend({Deferred:function(e){var o=[["notify","progress",S.Callbacks("memory"),S.Callbacks("memory"),2],["resolve","done",S.Callbacks("once memory"),S.Callbacks("once memory"),0,"resolved"],["reject","fail",S.Callbacks("once memory"),S.Callbacks("once memory"),1,"rejected"]],i="pending",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},"catch":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return S.Deferred(function(r){S.each(o,function(e,t){var n=m(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&m(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+"With"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError("Thenable self-resolution");t=e&&("object"==typeof e||"function"==typeof e)&&e.then,m(t)?s?t.call(e,l(u,o,R,s),l(u,o,M,s)):(u++,t.call(e,l(u,o,R,s),l(u,o,M,s),l(u,o,R,o.notifyWith))):(a!==R&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){S.Deferred.exceptionHook&&S.Deferred.exceptionHook(e,t.stackTrace),u<=i+1&&(a!==M&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(S.Deferred.getStackHook&&(t.stackTrace=S.Deferred.getStackHook()),C.setTimeout(t))}}return S.Deferred(function(e){o[0][3].add(l(0,e,m(r)?r:R,e.notifyWith)),o[1][3].add(l(0,e,m(t)?t:R)),o[2][3].add(l(0,e,m(n)?n:M))}).promise()},promise:function(e){return null!=e?S.extend(e,a):a}},s={};return S.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+"With"](this===s?void 0:this,arguments),this},s[t[0]+"With"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=s.call(arguments),o=S.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?s.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(I(e,o.done(a(t)).resolve,o.reject,!n),"pending"===o.state()||m(i[t]&&i[t].then)))return o.then();while(t--)I(i[t],a(t),o.reject);return o.promise()}});var W=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;S.Deferred.exceptionHook=function(e,t){C.console&&C.console.warn&&e&&W.test(e.name)&&C.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},S.readyException=function(e){C.setTimeout(function(){throw e})};var F=S.Deferred();function B(){E.removeEventListener("DOMContentLoaded",B),C.removeEventListener("load",B),S.ready()}S.fn.ready=function(e){return F.then(e)["catch"](function(e){S.readyException(e)}),this},S.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--S.readyWait:S.isReady)||(S.isReady=!0)!==e&&0<--S.readyWait||F.resolveWith(E,[S])}}),S.ready.then=F.then,"complete"===E.readyState||"loading"!==E.readyState&&!E.documentElement.doScroll?C.setTimeout(S.ready):(E.addEventListener("DOMContentLoaded",B),C.addEventListener("load",B));var $=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===w(n))for(s in i=!0,n)$(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,m(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(S(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},_=/^-ms-/,z=/-([a-z])/g;function U(e,t){return t.toUpperCase()}function X(e){return e.replace(_,"ms-").replace(z,U)}var V=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function G(){this.expando=S.expando+G.uid++}G.uid=1,G.prototype={cache:function(e){var t=e[this.expando];return t||(t={},V(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[X(t)]=n;else for(r in t)i[X(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][X(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(X):(t=X(t))in r?[t]:t.match(P)||[]).length;while(n--)delete r[t[n]]}(void 0===t||S.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!S.isEmptyObject(t)}};var Y=new G,Q=new G,J=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,K=/[A-Z]/g;function Z(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(K,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===(i=n)||"false"!==i&&("null"===i?null:i===+i+""?+i:J.test(i)?JSON.parse(i):i)}catch(e){}Q.set(e,t,n)}else n=void 0;return n}S.extend({hasData:function(e){return Q.hasData(e)||Y.hasData(e)},data:function(e,t,n){return Q.access(e,t,n)},removeData:function(e,t){Q.remove(e,t)},_data:function(e,t,n){return Y.access(e,t,n)},_removeData:function(e,t){Y.remove(e,t)}}),S.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=Q.get(o),1===o.nodeType&&!Y.get(o,"hasDataAttrs"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf("data-")&&(r=X(r.slice(5)),Z(o,r,i[r]));Y.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof n?this.each(function(){Q.set(this,n)}):$(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=Q.get(o,n))?t:void 0!==(t=Z(o,n))?t:void 0;this.each(function(){Q.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){Q.remove(this,e)})}}),S.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Y.get(e,t),n&&(!r||Array.isArray(n)?r=Y.access(e,t,S.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=S.queue(e,t),r=n.length,i=n.shift(),o=S._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){S.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Y.get(e,n)||Y.access(e,n,{empty:S.Callbacks("once memory").add(function(){Y.remove(e,[t+"queue",n])})})}}),S.fn.extend({queue:function(t,n){var e=2;return"string"!=typeof t&&(n=t,t="fx",e--),arguments.length<e?S.queue(this[0],t):void 0===n?this:this.each(function(){var e=S.queue(this,t,n);S._queueHooks(this,t),"fx"===t&&"inprogress"!==e[0]&&S.dequeue(this,t)})},dequeue:function(e){return this.each(function(){S.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=S.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=Y.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var ee=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,te=new RegExp("^(?:([+-])=|)("+ee+")([a-z%]*)$","i"),ne=["Top","Right","Bottom","Left"],re=E.documentElement,ie=function(e){return S.contains(e.ownerDocument,e)},oe={composed:!0};re.getRootNode&&(ie=function(e){return S.contains(e.ownerDocument,e)||e.getRootNode(oe)===e.ownerDocument});var ae=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&ie(e)&&"none"===S.css(e,"display")};function se(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return S.css(e,t,"")},u=s(),l=n&&n[3]||(S.cssNumber[t]?"":"px"),c=e.nodeType&&(S.cssNumber[t]||"px"!==l&&+u)&&te.exec(S.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)S.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,S.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var ue={};function le(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?("none"===n&&(l[c]=Y.get(r,"display")||null,l[c]||(r.style.display="")),""===r.style.display&&ae(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ue[s])||(o=a.body.appendChild(a.createElement(s)),u=S.css(o,"display"),o.parentNode.removeChild(o),"none"===u&&(u="block"),ue[s]=u)))):"none"!==n&&(l[c]="none",Y.set(r,"display",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}S.fn.extend({show:function(){return le(this,!0)},hide:function(){return le(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?S(this).show():S(this).hide()})}});var ce,fe,pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="<textarea>x</textarea>",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="<option></option>",y.option=!!ce.lastChild;var ge={thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n<r;n++)Y.set(e[n],"globalEval",!t||Y.get(t[n],"globalEval"))}ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td,y.option||(ge.optgroup=ge.option=[1,"<select multiple='multiple'>","</select>"]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===w(o))S.merge(p,o.nodeType?[o]:o);else if(me.test(o)){a=a||f.appendChild(t.createElement("div")),s=(de.exec(o)||["",""])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+S.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;S.merge(p,a.childNodes),(a=f.firstChild).textContent=""}else p.push(t.createTextNode(o));f.textContent="",d=0;while(o=p[d++])if(r&&-1<S.inArray(o,r))i&&i.push(o);else if(l=ie(o),a=ve(f.appendChild(o),"script"),l&&ye(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}var be=/^key/,we=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Te=/^([^.]*)(?:\.(.+)|)/;function Ce(){return!0}function Ee(){return!1}function Se(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function ke(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)ke(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ee;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return S().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=S.guid++)),e.each(function(){S.event.add(this,t,i,r,n)})}function Ae(e,i,o){o?(Y.set(e,i,!1),S.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Y.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(S.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Y.set(this,i,r),t=o(this,i),this[i](),r!==(n=Y.get(this,i))||t?Y.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Y.set(this,i,{value:S.event.trigger(S.extend(r[0],S.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Y.get(e,i)&&S.event.add(e,i,Ce)}S.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.get(t);if(V(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&S.find.matchesSelector(re,i),n.guid||(n.guid=S.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof S&&S.event.triggered!==e.type?S.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(P)||[""]).length;while(l--)d=g=(s=Te.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=S.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=S.event.special[d]||{},c=S.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&S.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),S.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.hasData(e)&&Y.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(P)||[""]).length;while(l--)if(d=g=(s=Te.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=S.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||S.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)S.event.remove(e,d+t[l],n,r,!0);S.isEmptyObject(u)&&Y.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=S.event.fix(e),l=(Y.get(this,"events")||Object.create(null))[u.type]||[],c=S.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=S.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((S.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?-1<S(i,this).index(l):S.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(S.Event.prototype,t,{enumerable:!0,configurable:!0,get:m(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[S.expando]?e:new S.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&Ae(t,"click",Ce),!1},trigger:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&Ae(t,"click"),!0},_default:function(e){var t=e.target;return pe.test(t.type)&&t.click&&A(t,"input")&&Y.get(t,"click")||A(t,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},S.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},S.Event=function(e,t){if(!(this instanceof S.Event))return new S.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ce:Ee,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&S.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[S.expando]=!0},S.Event.prototype={constructor:S.Event,isDefaultPrevented:Ee,isPropagationStopped:Ee,isImmediatePropagationStopped:Ee,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ce,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ce,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ce,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},S.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&be.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&we.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},S.event.addProp),S.each({focus:"focusin",blur:"focusout"},function(e,t){S.event.special[e]={setup:function(){return Ae(this,e,Se),!1},trigger:function(){return Ae(this,e),!0},delegateType:t}}),S.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,i){S.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||S.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),S.fn.extend({on:function(e,t,n,r){return ke(this,e,t,n,r)},one:function(e,t,n,r){return ke(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,S(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=Ee),this.each(function(){S.event.remove(this,e,n,t)})}});var Ne=/<script|<style|<link/i,De=/checked\s*(?:[^=]|=\s*.checked.)/i,je=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n<r;n++)S.event.add(t,i,s[i][n]);Q.hasData(e)&&(o=Q.access(e),a=S.extend({},o),Q.set(t,a))}}function Pe(n,r,i,o){r=g(r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=m(d);if(h||1<f&&"string"==typeof d&&!y.checkClone&&De.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),Pe(t,r,i,o)});if(f&&(t=(e=xe(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=S.map(ve(e,"script"),Le)).length;c<f;c++)u=e,c!==p&&(u=S.clone(u,!0,!0),s&&S.merge(a,ve(u,"script"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,S.map(a,He),c=0;c<s;c++)u=a[c],he.test(u.type||"")&&!Y.access(u,"globalEval")&&S.contains(l,u)&&(u.src&&"module"!==(u.type||"").toLowerCase()?S._evalUrl&&!u.noModule&&S._evalUrl(u.src,{nonce:u.nonce||u.getAttribute("nonce")},l):b(u.textContent.replace(je,""),u,l))}return n}function Re(e,t,n){for(var r,i=t?S.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||S.cleanData(ve(r)),r.parentNode&&(n&&ie(r)&&ye(ve(r,"script")),r.parentNode.removeChild(r));return e}S.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=ie(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||S.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r<i;r++)s=o[r],u=a[r],void 0,"input"===(l=u.nodeName.toLowerCase())&&pe.test(s.type)?u.checked=s.checked:"input"!==l&&"textarea"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||ve(e),a=a||ve(c),r=0,i=o.length;r<i;r++)Oe(o[r],a[r]);else Oe(e,c);return 0<(a=ve(c,"script")).length&&ye(a,!f&&ve(e,"script")),c},cleanData:function(e){for(var t,n,r,i=S.event.special,o=0;void 0!==(n=e[o]);o++)if(V(n)){if(t=n[Y.expando]){if(t.events)for(r in t.events)i[r]?S.event.remove(n,r):S.removeEvent(n,r,t.handle);n[Y.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),S.fn.extend({detach:function(e){return Re(this,e,!0)},remove:function(e){return Re(this,e)},text:function(e){return $(this,function(e){return void 0===e?S.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Pe(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||qe(this,e).appendChild(e)})},prepend:function(){return Pe(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=qe(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(S.cleanData(ve(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return S.clone(this,e,t)})},html:function(e){return $(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ne.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=S.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(S.cleanData(ve(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return Pe(this,arguments,function(e){var t=this.parentNode;S.inArray(this,n)<0&&(S.cleanData(ve(this)),t&&t.replaceChild(e,this))},n)}}),S.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,a){S.fn[e]=function(e){for(var t,n=[],r=S(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),S(r[o])[a](t),u.apply(n,t.get());return this.pushStack(n)}});var Me=new RegExp("^("+ee+")(?!px)[a-z%]+$","i"),Ie=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=C),t.getComputedStyle(e)},We=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Fe=new RegExp(ne.join("|"),"i");function Be(e,t,n){var r,i,o,a,s=e.style;return(n=n||Ie(e))&&(""!==(a=n.getPropertyValue(t)||n[t])||ie(e)||(a=S.style(e,t)),!y.pixelBoxStyles()&&Me.test(a)&&Fe.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function $e(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",l.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",re.appendChild(u).appendChild(l);var e=C.getComputedStyle(l);n="1%"!==e.top,s=12===t(e.marginLeft),l.style.right="60%",o=36===t(e.right),r=36===t(e.width),l.style.position="absolute",i=12===t(l.offsetWidth/3),re.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=E.createElement("div"),l=E.createElement("div");l.style&&(l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",y.clearCloneStyle="content-box"===l.style.backgroundClip,S.extend(y,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=E.createElement("table"),t=E.createElement("tr"),n=E.createElement("div"),e.style.cssText="position:absolute;left:-11111px",t.style.height="1px",n.style.height="9px",re.appendChild(e).appendChild(t).appendChild(n),r=C.getComputedStyle(t),a=3<parseInt(r.height),re.removeChild(e)),a}}))}();var _e=["Webkit","Moz","ms"],ze=E.createElement("div").style,Ue={};function Xe(e){var t=S.cssProps[e]||Ue[e];return t||(e in ze?e:Ue[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=_e.length;while(n--)if((e=_e[n]+t)in ze)return e}(e)||e)}var Ve=/^(none|table(?!-c[ea]).+)/,Ge=/^--/,Ye={position:"absolute",visibility:"hidden",display:"block"},Qe={letterSpacing:"0",fontWeight:"400"};function Je(e,t,n){var r=te.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function Ke(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(u+=S.css(e,n+ne[a],!0,i)),r?("content"===n&&(u-=S.css(e,"padding"+ne[a],!0,i)),"margin"!==n&&(u-=S.css(e,"border"+ne[a]+"Width",!0,i))):(u+=S.css(e,"padding"+ne[a],!0,i),"padding"!==n?u+=S.css(e,"border"+ne[a]+"Width",!0,i):s+=S.css(e,"border"+ne[a]+"Width",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function Ze(e,t,n){var r=Ie(e),i=(!y.boxSizingReliable()||n)&&"border-box"===S.css(e,"boxSizing",!1,r),o=i,a=Be(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(Me.test(a)){if(!n)return a;a="auto"}return(!y.boxSizingReliable()&&i||!y.reliableTrDimensions()&&A(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===S.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===S.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+Ke(e,t,n||(i?"border":"content"),o,r,a)+"px"}function et(e,t,n,r,i){return new et.prototype.init(e,t,n,r,i)}S.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Be(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=X(t),u=Ge.test(t),l=e.style;if(u||(t=Xe(s)),a=S.cssHooks[t]||S.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=te.exec(n))&&i[1]&&(n=se(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(S.cssNumber[s]?"":"px")),y.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=X(t);return Ge.test(t)||(t=Xe(s)),(a=S.cssHooks[t]||S.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Be(e,t,r)),"normal"===i&&t in Qe&&(i=Qe[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),S.each(["height","width"],function(e,u){S.cssHooks[u]={get:function(e,t,n){if(t)return!Ve.test(S.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?Ze(e,u,n):We(e,Ye,function(){return Ze(e,u,n)})},set:function(e,t,n){var r,i=Ie(e),o=!y.scrollboxSize()&&"absolute"===i.position,a=(o||n)&&"border-box"===S.css(e,"boxSizing",!1,i),s=n?Ke(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e["offset"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-Ke(e,u,"border",!1,i)-.5)),s&&(r=te.exec(t))&&"px"!==(r[3]||"px")&&(e.style[u]=t,t=S.css(e,u)),Je(0,t,s)}}}),S.cssHooks.marginLeft=$e(y.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Be(e,"marginLeft"))||e.getBoundingClientRect().left-We(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),S.each({margin:"",padding:"",border:"Width"},function(i,o){S.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r="string"==typeof e?e.split(" "):[e];t<4;t++)n[i+ne[t]+o]=r[t]||r[t-2]||r[0];return n}},"margin"!==i&&(S.cssHooks[i+o].set=Je)}),S.fn.extend({css:function(e,t){return $(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Ie(e),i=t.length;a<i;a++)o[t[a]]=S.css(e,t[a],!1,r);return o}return void 0!==n?S.style(e,t,n):S.css(e,t)},e,t,1<arguments.length)}}),((S.Tween=et).prototype={constructor:et,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||S.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(S.cssNumber[n]?"":"px")},cur:function(){var e=et.propHooks[this.prop];return e&&e.get?e.get(this):et.propHooks._default.get(this)},run:function(e){var t,n=et.propHooks[this.prop];return this.options.duration?this.pos=t=S.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):et.propHooks._default.set(this),this}}).init.prototype=et.prototype,(et.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=S.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){S.fx.step[e.prop]?S.fx.step[e.prop](e):1!==e.elem.nodeType||!S.cssHooks[e.prop]&&null==e.elem.style[Xe(e.prop)]?e.elem[e.prop]=e.now:S.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=et.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},S.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},S.fx=et.prototype.init,S.fx.step={};var tt,nt,rt,it,ot=/^(?:toggle|show|hide)$/,at=/queueHooks$/;function st(){nt&&(!1===E.hidden&&C.requestAnimationFrame?C.requestAnimationFrame(st):C.setTimeout(st,S.fx.interval),S.fx.tick())}function ut(){return C.setTimeout(function(){tt=void 0}),tt=Date.now()}function lt(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=ne[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function ct(e,t,n){for(var r,i=(ft.tweeners[t]||[]).concat(ft.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function ft(o,e,t){var n,a,r=0,i=ft.prefilters.length,s=S.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=tt||ut(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;r<i;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),n<1&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:S.extend({},e),opts:S.extend(!0,{specialEasing:{},easing:S.easing._default},t),originalProperties:e,originalOptions:t,startTime:tt||ut(),duration:t.duration,tweens:[],createTween:function(e,t){var n=S.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;t<n;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for(!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=X(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=S.cssHooks[r])&&"expand"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing);r<i;r++)if(n=ft.prefilters[r].call(l,o,c,l.opts))return m(n.stop)&&(S._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return S.map(c,ct,l),m(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),S.fx.timer(S.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}S.Animation=S.extend(ft,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return se(n.elem,e,te.exec(t),n),n}]},tweener:function(e,t){m(e)?(t=e,e=["*"]):e=e.match(P);for(var n,r=0,i=e.length;r<i;r++)n=e[r],ft.tweeners[n]=ft.tweeners[n]||[],ft.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&ae(e),v=Y.get(e,"fxshow");for(r in n.queue||(null==(a=S._queueHooks(e,"fx")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,S.queue(e,"fx").length||a.empty.fire()})})),t)if(i=t[r],ot.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||S.style(e,r)}if((u=!S.isEmptyObject(t))||!S.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=Y.get(e,"display")),"none"===(c=S.css(e,"display"))&&(l?c=l:(le([e],!0),l=e.style.display||l,c=S.css(e,"display"),le([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===S.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?"hidden"in v&&(g=v.hidden):v=Y.access(e,"fxshow",{display:l}),o&&(v.hidden=!g),g&&le([e],!0),p.done(function(){for(r in g||le([e]),Y.remove(e,"fxshow"),d)S.style(e,r,d[r])})),u=ct(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?ft.prefilters.unshift(e):ft.prefilters.push(e)}}),S.speed=function(e,t,n){var r=e&&"object"==typeof e?S.extend({},e):{complete:n||!n&&t||m(e)&&e,duration:e,easing:n&&t||t&&!m(t)&&t};return S.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in S.fx.speeds?r.duration=S.fx.speeds[r.duration]:r.duration=S.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){m(r.old)&&r.old.call(this),r.queue&&S.dequeue(this,r.queue)},r},S.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ae).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=S.isEmptyObject(t),o=S.speed(e,n,r),a=function(){var e=ft(this,S.extend({},t),o);(i||Y.get(this,"finish"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return"string"!=typeof i&&(o=e,e=i,i=void 0),e&&this.queue(i||"fx",[]),this.each(function(){var e=!0,t=null!=i&&i+"queueHooks",n=S.timers,r=Y.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&at.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1));!e&&o||S.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||"fx"),this.each(function(){var e,t=Y.get(this),n=t[a+"queue"],r=t[a+"queueHooks"],i=S.timers,o=n?n.length:0;for(t.finish=!0,S.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;e<o;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),S.each(["toggle","show","hide"],function(e,r){var i=S.fn[r];S.fn[r]=function(e,t,n){return null==e||"boolean"==typeof e?i.apply(this,arguments):this.animate(lt(r,!0),e,t,n)}}),S.each({slideDown:lt("show"),slideUp:lt("hide"),slideToggle:lt("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,r){S.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),S.timers=[],S.fx.tick=function(){var e,t=0,n=S.timers;for(tt=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||S.fx.stop(),tt=void 0},S.fx.timer=function(e){S.timers.push(e),S.fx.start()},S.fx.interval=13,S.fx.start=function(){nt||(nt=!0,st())},S.fx.stop=function(){nt=null},S.fx.speeds={slow:600,fast:200,_default:400},S.fn.delay=function(r,e){return r=S.fx&&S.fx.speeds[r]||r,e=e||"fx",this.queue(e,function(e,t){var n=C.setTimeout(e,r);t.stop=function(){C.clearTimeout(n)}})},rt=E.createElement("input"),it=E.createElement("select").appendChild(E.createElement("option")),rt.type="checkbox",y.checkOn=""!==rt.value,y.optSelected=it.selected,(rt=E.createElement("input")).value="t",rt.type="radio",y.radioValue="t"===rt.value;var pt,dt=S.expr.attrHandle;S.fn.extend({attr:function(e,t){return $(this,S.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){S.removeAttr(this,e)})}}),S.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?S.prop(e,t,n):(1===o&&S.isXMLDoc(e)||(i=S.attrHooks[t.toLowerCase()]||(S.expr.match.bool.test(t)?pt:void 0)),void 0!==n?null===n?void S.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=S.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!y.radioValue&&"radio"===t&&A(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(P);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),pt={set:function(e,t,n){return!1===t?S.removeAttr(e,n):e.setAttribute(n,n),n}},S.each(S.expr.match.bool.source.match(/\w+/g),function(e,t){var a=dt[t]||S.find.attr;dt[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=dt[o],dt[o]=r,r=null!=a(e,t,n)?o:null,dt[o]=i),r}});var ht=/^(?:input|select|textarea|button)$/i,gt=/^(?:a|area)$/i;function vt(e){return(e.match(P)||[]).join(" ")}function yt(e){return e.getAttribute&&e.getAttribute("class")||""}function mt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(P)||[]}S.fn.extend({prop:function(e,t){return $(this,S.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[S.propFix[e]||e]})}}),S.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&S.isXMLDoc(e)||(t=S.propFix[t]||t,i=S.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=S.find.attr(e,"tabindex");return t?parseInt(t,10):ht.test(e.nodeName)||gt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),y.optSelected||(S.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),S.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){S.propFix[this.toLowerCase()]=this}),S.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).addClass(t.call(this,e,yt(this)))});if((e=mt(t)).length)while(n=this[u++])if(i=yt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=e[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).removeClass(t.call(this,e,yt(this)))});if(!arguments.length)return this.attr("class","");if((e=mt(t)).length)while(n=this[u++])if(i=yt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=e[a++])while(-1<r.indexOf(" "+o+" "))r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(i,t){var o=typeof i,a="string"===o||Array.isArray(i);return"boolean"==typeof t&&a?t?this.addClass(i):this.removeClass(i):m(i)?this.each(function(e){S(this).toggleClass(i.call(this,e,yt(this),t),t)}):this.each(function(){var e,t,n,r;if(a){t=0,n=S(this),r=mt(i);while(e=r[t++])n.hasClass(e)?n.removeClass(e):n.addClass(e)}else void 0!==i&&"boolean"!==o||((e=yt(this))&&Y.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===i?"":Y.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&-1<(" "+vt(yt(n))+" ").indexOf(t))return!0;return!1}});var xt=/\r/g;S.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=m(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,S(this).val()):n)?t="":"number"==typeof t?t+="":Array.isArray(t)&&(t=S.map(t,function(e){return null==e?"":e+""})),(r=S.valHooks[this.type]||S.valHooks[this.nodeName.toLowerCase()])&&"set"in r&&void 0!==r.set(this,t,"value")||(this.value=t))})):t?(r=S.valHooks[t.type]||S.valHooks[t.nodeName.toLowerCase()])&&"get"in r&&void 0!==(e=r.get(t,"value"))?e:"string"==typeof(e=t.value)?e.replace(xt,""):null==e?"":e:void 0}}),S.extend({valHooks:{option:{get:function(e){var t=S.find.attr(e,"value");return null!=t?t:vt(S.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!A(n.parentNode,"optgroup"))){if(t=S(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=S.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<S.inArray(S.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),S.each(["radio","checkbox"],function(){S.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<S.inArray(S(e).val(),t)}},y.checkOn||(S.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),y.focusin="onfocusin"in C;var bt=/^(?:focusinfocus|focusoutblur)$/,wt=function(e){e.stopPropagation()};S.extend(S.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||E],d=v.call(e,"type")?e.type:e,h=v.call(e,"namespace")?e.namespace.split("."):[];if(o=f=a=n=n||E,3!==n.nodeType&&8!==n.nodeType&&!bt.test(d+S.event.triggered)&&(-1<d.indexOf(".")&&(d=(h=d.split(".")).shift(),h.sort()),u=d.indexOf(":")<0&&"on"+d,(e=e[S.expando]?e:new S.Event(d,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:S.makeArray(t,[e]),c=S.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!x(n)){for(s=c.delegateType||d,bt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||E)&&p.push(a.defaultView||a.parentWindow||C)}i=0;while((o=p[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||d,(l=(Y.get(o,"events")||Object.create(null))[e.type]&&Y.get(o,"handle"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&V(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!V(n)||u&&m(n[d])&&!x(n)&&((a=n[u])&&(n[u]=null),S.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,wt),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,wt),S.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=S.extend(new S.Event,n,{type:e,isSimulated:!0});S.event.trigger(r,null,t)}}),S.fn.extend({trigger:function(e,t){return this.each(function(){S.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return S.event.trigger(e,t,n,!0)}}),y.focusin||S.each({focus:"focusin",blur:"focusout"},function(n,r){var i=function(e){S.event.simulate(r,e.target,S.event.fix(e))};S.event.special[r]={setup:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r);t||e.addEventListener(n,i,!0),Y.access(e,r,(t||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r)-1;t?Y.access(e,r,t):(e.removeEventListener(n,i,!0),Y.remove(e,r))}}});var Tt=C.location,Ct={guid:Date.now()},Et=/\?/;S.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new C.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||S.error("Invalid XML: "+e),t};var St=/\[\]$/,kt=/\r?\n/g,At=/^(?:submit|button|image|reset|file)$/i,Nt=/^(?:input|select|textarea|keygen)/i;function Dt(n,e,r,i){var t;if(Array.isArray(e))S.each(e,function(e,t){r||St.test(n)?i(n,t):Dt(n+"["+("object"==typeof t&&null!=t?e:"")+"]",t,r,i)});else if(r||"object"!==w(e))i(n,e);else for(t in e)Dt(n+"["+t+"]",e[t],r,i)}S.param=function(e,t){var n,r=[],i=function(e,t){var n=m(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!S.isPlainObject(e))S.each(e,function(){i(this.name,this.value)});else for(n in e)Dt(n,e[n],t,i);return r.join("&")},S.fn.extend({serialize:function(){return S.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=S.prop(this,"elements");return e?S.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!S(this).is(":disabled")&&Nt.test(this.nodeName)&&!At.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=S(this).val();return null==n?null:Array.isArray(n)?S.map(n,function(e){return{name:t.name,value:e.replace(kt,"\r\n")}}):{name:t.name,value:n.replace(kt,"\r\n")}}).get()}});var jt=/%20/g,qt=/#.*$/,Lt=/([?&])_=[^&]*/,Ht=/^(.*?):[ \t]*([^\r\n]*)$/gm,Ot=/^(?:GET|HEAD)$/,Pt=/^\/\//,Rt={},Mt={},It="*/".concat("*"),Wt=E.createElement("a");function Ft(o){return function(e,t){"string"!=typeof e&&(t=e,e="*");var n,r=0,i=e.toLowerCase().match(P)||[];if(m(t))while(n=i[r++])"+"===n[0]?(n=n.slice(1)||"*",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function Bt(t,i,o,a){var s={},u=t===Mt;function l(e){var r;return s[e]=!0,S.each(t[e]||[],function(e,t){var n=t(i,o,a);return"string"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}return l(i.dataTypes[0])||!s["*"]&&l("*")}function $t(e,t){var n,r,i=S.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&S.extend(!0,e,r),e}Wt.href=Tt.href,S.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Tt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Tt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":It,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":S.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?$t($t(e,S.ajaxSettings),t):$t(S.ajaxSettings,e)},ajaxPrefilter:Ft(Rt),ajaxTransport:Ft(Mt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=S.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?S(y):S.event,x=S.Deferred(),b=S.Callbacks("once memory"),w=v.statusCode||{},a={},s={},u="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n){n={};while(t=Ht.exec(p))n[t[1].toLowerCase()+" "]=(n[t[1].toLowerCase()+" "]||[]).concat(t[2])}t=n[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||Tt.href)+"").replace(Pt,Tt.protocol+"//"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||"*").toLowerCase().match(P)||[""],null==v.crossDomain){r=E.createElement("a");try{r.href=v.url,r.href=r.href,v.crossDomain=Wt.protocol+"//"+Wt.host!=r.protocol+"//"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&"string"!=typeof v.data&&(v.data=S.param(v.data,v.traditional)),Bt(Rt,v,t,T),h)return T;for(i in(g=S.event&&v.global)&&0==S.active++&&S.event.trigger("ajaxStart"),v.type=v.type.toUpperCase(),v.hasContent=!Ot.test(v.type),f=v.url.replace(qt,""),v.hasContent?v.data&&v.processData&&0===(v.contentType||"").indexOf("application/x-www-form-urlencoded")&&(v.data=v.data.replace(jt,"+")):(o=v.url.slice(f.length),v.data&&(v.processData||"string"==typeof v.data)&&(f+=(Et.test(f)?"&":"?")+v.data,delete v.data),!1===v.cache&&(f=f.replace(Lt,"$1"),o=(Et.test(f)?"&":"?")+"_="+Ct.guid+++o),v.url=f+o),v.ifModified&&(S.lastModified[f]&&T.setRequestHeader("If-Modified-Since",S.lastModified[f]),S.etag[f]&&T.setRequestHeader("If-None-Match",S.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader("Content-Type",v.contentType),T.setRequestHeader("Accept",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+("*"!==v.dataTypes[0]?", "+It+"; q=0.01":""):v.accepts["*"]),v.headers)T.setRequestHeader(i,v.headers[i]);if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u="abort",b.add(v.complete),T.done(v.success),T.fail(v.error),c=Bt(Mt,v,t,T)){if(T.readyState=1,g&&m.trigger("ajaxSend",[T,v]),h)return T;v.async&&0<v.timeout&&(d=C.setTimeout(function(){T.abort("timeout")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,"No Transport");function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&C.clearTimeout(d),c=void 0,p=r||"",T.readyState=0<e?4:0,i=200<=e&&e<300||304===e,n&&(s=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(v,T,n)),!i&&-1<S.inArray("script",v.dataTypes)&&(v.converters["text script"]=function(){}),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader("Last-Modified"))&&(S.lastModified[f]=u),(u=T.getResponseHeader("etag"))&&(S.etag[f]=u)),204===e||"HEAD"===v.type?l="nocontent":304===e?l="notmodified":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l="error",e<0&&(e=0))),T.status=e,T.statusText=(t||l)+"",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?"ajaxSuccess":"ajaxError",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger("ajaxComplete",[T,v]),--S.active||S.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return S.get(e,t,n,"json")},getScript:function(e,t){return S.get(e,void 0,t,"script")}}),S.each(["get","post"],function(e,i){S[i]=function(e,t,n,r){return m(t)&&(r=r||n,n=t,t=void 0),S.ajax(S.extend({url:e,type:i,dataType:r,data:t,success:n},S.isPlainObject(e)&&e))}}),S.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),S._evalUrl=function(e,t,n){return S.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){S.globalEval(e,t,n)}})},S.fn.extend({wrapAll:function(e){var t;return this[0]&&(m(e)&&(e=e.call(this[0])),t=S(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return m(n)?this.each(function(e){S(this).wrapInner(n.call(this,e))}):this.each(function(){var e=S(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=m(t);return this.each(function(e){S(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not("body").each(function(){S(this).replaceWith(this.childNodes)}),this}}),S.expr.pseudos.hidden=function(e){return!S.expr.pseudos.visible(e)},S.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},S.ajaxSettings.xhr=function(){try{return new C.XMLHttpRequest}catch(e){}};var _t={0:200,1223:204},zt=S.ajaxSettings.xhr();y.cors=!!zt&&"withCredentials"in zt,y.ajax=zt=!!zt,S.ajaxTransport(function(i){var o,a;if(y.cors||zt&&!i.crossDomain)return{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,"abort"===e?r.abort():"error"===e?"number"!=typeof r.status?t(0,"error"):t(r.status,r.statusText):t(_t[r.status]||r.status,r.statusText,"text"!==(r.responseType||"text")||"string"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o("error"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&C.setTimeout(function(){o&&a()})},o=o("abort");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}}),S.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),S.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return S.globalEval(e),e}}}),S.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),S.ajaxTransport("script",function(n){var r,i;if(n.crossDomain||n.scriptAttrs)return{send:function(e,t){r=S("<script>").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="<form></form><form></form>",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1<s&&(r=vt(e.slice(s)),e=e.slice(0,s)),m(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),0<a.length&&S.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?S("<div>").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var Gt=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;S.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),m(e))return r=s.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(s.call(arguments)))}).guid=e.guid=e.guid||S.guid++,i},S.holdReady=function(e){e?S.readyWait++:S.ready(!0)},S.isArray=Array.isArray,S.parseJSON=JSON.parse,S.nodeName=A,S.isFunction=m,S.isWindow=x,S.camelCase=X,S.type=w,S.now=Date.now,S.isNumeric=function(e){var t=S.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},S.trim=function(e){return null==e?"":(e+"").replace(Gt,"")},"function"==typeof define&&define.amd&&define("jquery",[],function(){return S});var Yt=C.jQuery,Qt=C.$;return S.noConflict=function(e){return C.$===S&&(C.$=Qt),e&&C.jQuery===S&&(C.jQuery=Yt),S},"undefined"==typeof e&&(C.jQuery=C.$=S),S});
  3. ;/*
  4. Copyright (C) Federico Zivolo 2020
  5. Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
  6. */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:'top',o='top'===t?'scrollTop':'scrollLeft',n=e.nodeName;if('BODY'===n||'HTML'===n){var i=e.ownerDocument.documentElement,r=e.ownerDocument.scrollingElement||i;return r[o]}return e[o]}function f(e,t){var o=2<arguments.length&&void 0!==arguments[2]&&arguments[2],n=l(t,'top'),i=l(t,'left'),r=o?-1:1;return e.top+=n*r,e.bottom+=n*r,e.left+=i*r,e.right+=i*r,e}function m(e,t){var o='x'===t?'Left':'Top',n='Left'==o?'Right':'Bottom';return parseFloat(e['border'+o+'Width'])+parseFloat(e['border'+n+'Width'])}function h(e,t,o,n){return ee(t['offset'+e],t['scroll'+e],o['client'+e],o['offset'+e],o['scroll'+e],r(10)?parseInt(o['offset'+e])+parseInt(n['margin'+('Height'===e?'Top':'Left')])+parseInt(n['margin'+('Height'===e?'Bottom':'Right')]):0)}function c(e){var t=e.body,o=e.documentElement,n=r(10)&&getComputedStyle(o);return{height:h('Height',t,o,n),width:h('Width',t,o,n)}}function g(e){return le({},e,{right:e.left+e.width,bottom:e.top+e.height})}function u(e){var o={};try{if(r(10)){o=e.getBoundingClientRect();var n=l(e,'top'),i=l(e,'left');o.top+=n,o.left+=i,o.bottom+=n,o.right+=i}else o=e.getBoundingClientRect()}catch(t){}var p={left:o.left,top:o.top,width:o.right-o.left,height:o.bottom-o.top},s='HTML'===e.nodeName?c(e.ownerDocument):{},d=s.width||e.clientWidth||p.width,a=s.height||e.clientHeight||p.height,f=e.offsetWidth-d,h=e.offsetHeight-a;if(f||h){var u=t(e);f-=m(u,'x'),h-=m(u,'y'),p.width-=f,p.height-=h}return g(p)}function b(e,o){var i=2<arguments.length&&void 0!==arguments[2]&&arguments[2],p=r(10),s='HTML'===o.nodeName,d=u(e),a=u(o),l=n(e),m=t(o),h=parseFloat(m.borderTopWidth),c=parseFloat(m.borderLeftWidth);i&&s&&(a.top=ee(a.top,0),a.left=ee(a.left,0));var b=g({top:d.top-a.top-h,left:d.left-a.left-c,width:d.width,height:d.height});if(b.marginTop=0,b.marginLeft=0,!p&&s){var w=parseFloat(m.marginTop),y=parseFloat(m.marginLeft);b.top-=h-w,b.bottom-=h-w,b.left-=c-y,b.right-=c-y,b.marginTop=w,b.marginLeft=y}return(p&&!i?o.contains(l):o===l&&'BODY'!==l.nodeName)&&(b=f(b,o)),b}function w(e){var t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],o=e.ownerDocument.documentElement,n=b(e,o),i=ee(o.clientWidth,window.innerWidth||0),r=ee(o.clientHeight,window.innerHeight||0),p=t?0:l(o),s=t?0:l(o,'left'),d={top:p-n.top+n.marginTop,left:s-n.left+n.marginLeft,width:i,height:r};return g(d)}function y(e){var n=e.nodeName;if('BODY'===n||'HTML'===n)return!1;if('fixed'===t(e,'position'))return!0;var i=o(e);return!!i&&y(i)}function E(e){if(!e||!e.parentElement||r())return document.documentElement;for(var o=e.parentElement;o&&'none'===t(o,'transform');)o=o.parentElement;return o||document.documentElement}function v(e,t,r,p){var s=4<arguments.length&&void 0!==arguments[4]&&arguments[4],d={top:0,left:0},l=s?E(e):a(e,i(t));if('viewport'===p)d=w(l,s);else{var f;'scrollParent'===p?(f=n(o(t)),'BODY'===f.nodeName&&(f=e.ownerDocument.documentElement)):'window'===p?f=e.ownerDocument.documentElement:f=p;var m=b(f,l,s);if('HTML'===f.nodeName&&!y(l)){var h=c(e.ownerDocument),g=h.height,u=h.width;d.top+=m.top-m.marginTop,d.bottom=g+m.top,d.left+=m.left-m.marginLeft,d.right=u+m.left}else d=m}r=r||0;var v='number'==typeof r;return d.left+=v?r:r.left||0,d.top+=v?r:r.top||0,d.right-=v?r:r.right||0,d.bottom-=v?r:r.bottom||0,d}function x(e){var t=e.width,o=e.height;return t*o}function O(e,t,o,n,i){var r=5<arguments.length&&void 0!==arguments[5]?arguments[5]:0;if(-1===e.indexOf('auto'))return e;var p=v(o,n,r,i),s={top:{width:p.width,height:t.top-p.top},right:{width:p.right-t.right,height:p.height},bottom:{width:p.width,height:p.bottom-t.bottom},left:{width:t.left-p.left,height:p.height}},d=Object.keys(s).map(function(e){return le({key:e},s[e],{area:x(s[e])})}).sort(function(e,t){return t.area-e.area}),a=d.filter(function(e){var t=e.width,n=e.height;return t>=o.clientWidth&&n>=o.clientHeight}),l=0<a.length?a[0].key:d[0].key,f=e.split('-')[1];return l+(f?'-'+f:'')}function L(e,t,o){var n=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null,r=n?E(t):a(t,i(o));return b(o,r,n)}function S(e){var t=e.ownerDocument.defaultView,o=t.getComputedStyle(e),n=parseFloat(o.marginTop||0)+parseFloat(o.marginBottom||0),i=parseFloat(o.marginLeft||0)+parseFloat(o.marginRight||0),r={width:e.offsetWidth+i,height:e.offsetHeight+n};return r}function T(e){var t={left:'right',right:'left',bottom:'top',top:'bottom'};return e.replace(/left|right|bottom|top/g,function(e){return t[e]})}function C(e,t,o){o=o.split('-')[0];var n=S(e),i={width:n.width,height:n.height},r=-1!==['right','left'].indexOf(o),p=r?'top':'left',s=r?'left':'top',d=r?'height':'width',a=r?'width':'height';return i[p]=t[p]+t[d]/2-n[d]/2,i[s]=o===s?t[s]-n[a]:t[T(s)],i}function D(e,t){return Array.prototype.find?e.find(t):e.filter(t)[0]}function N(e,t,o){if(Array.prototype.findIndex)return e.findIndex(function(e){return e[t]===o});var n=D(e,function(e){return e[t]===o});return e.indexOf(n)}function P(t,o,n){var i=void 0===n?t:t.slice(0,N(t,'name',n));return i.forEach(function(t){t['function']&&console.warn('`modifier.function` is deprecated, use `modifier.fn`!');var n=t['function']||t.fn;t.enabled&&e(n)&&(o.offsets.popper=g(o.offsets.popper),o.offsets.reference=g(o.offsets.reference),o=n(o,t))}),o}function k(){if(!this.state.isDestroyed){var e={instance:this,styles:{},arrowStyles:{},attributes:{},flipped:!1,offsets:{}};e.offsets.reference=L(this.state,this.popper,this.reference,this.options.positionFixed),e.placement=O(this.options.placement,e.offsets.reference,this.popper,this.reference,this.options.modifiers.flip.boundariesElement,this.options.modifiers.flip.padding),e.originalPlacement=e.placement,e.positionFixed=this.options.positionFixed,e.offsets.popper=C(this.popper,e.offsets.reference,e.placement),e.offsets.popper.position=this.options.positionFixed?'fixed':'absolute',e=P(this.modifiers,e),this.state.isCreated?this.options.onUpdate(e):(this.state.isCreated=!0,this.options.onCreate(e))}}function W(e,t){return e.some(function(e){var o=e.name,n=e.enabled;return n&&o===t})}function B(e){for(var t=[!1,'ms','Webkit','Moz','O'],o=e.charAt(0).toUpperCase()+e.slice(1),n=0;n<t.length;n++){var i=t[n],r=i?''+i+o:e;if('undefined'!=typeof document.body.style[r])return r}return null}function H(){return this.state.isDestroyed=!0,W(this.modifiers,'applyStyle')&&(this.popper.removeAttribute('x-placement'),this.popper.style.position='',this.popper.style.top='',this.popper.style.left='',this.popper.style.right='',this.popper.style.bottom='',this.popper.style.willChange='',this.popper.style[B('transform')]=''),this.disableEventListeners(),this.options.removeOnDestroy&&this.popper.parentNode.removeChild(this.popper),this}function A(e){var t=e.ownerDocument;return t?t.defaultView:window}function M(e,t,o,i){var r='BODY'===e.nodeName,p=r?e.ownerDocument.defaultView:e;p.addEventListener(t,o,{passive:!0}),r||M(n(p.parentNode),t,o,i),i.push(p)}function F(e,t,o,i){o.updateBound=i,A(e).addEventListener('resize',o.updateBound,{passive:!0});var r=n(e);return M(r,'scroll',o.updateBound,o.scrollParents),o.scrollElement=r,o.eventsEnabled=!0,o}function I(){this.state.eventsEnabled||(this.state=F(this.reference,this.options,this.state,this.scheduleUpdate))}function R(e,t){return A(e).removeEventListener('resize',t.updateBound),t.scrollParents.forEach(function(e){e.removeEventListener('scroll',t.updateBound)}),t.updateBound=null,t.scrollParents=[],t.scrollElement=null,t.eventsEnabled=!1,t}function U(){this.state.eventsEnabled&&(cancelAnimationFrame(this.scheduleUpdate),this.state=R(this.reference,this.state))}function Y(e){return''!==e&&!isNaN(parseFloat(e))&&isFinite(e)}function V(e,t){Object.keys(t).forEach(function(o){var n='';-1!==['width','height','top','right','bottom','left'].indexOf(o)&&Y(t[o])&&(n='px'),e.style[o]=t[o]+n})}function j(e,t){Object.keys(t).forEach(function(o){var n=t[o];!1===n?e.removeAttribute(o):e.setAttribute(o,t[o])})}function q(e,t){var o=e.offsets,n=o.popper,i=o.reference,r=$,p=function(e){return e},s=r(i.width),d=r(n.width),a=-1!==['left','right'].indexOf(e.placement),l=-1!==e.placement.indexOf('-'),f=t?a||l||s%2==d%2?r:Z:p,m=t?r:p;return{left:f(1==s%2&&1==d%2&&!l&&t?n.left-1:n.left),top:m(n.top),bottom:m(n.bottom),right:f(n.right)}}function K(e,t,o){var n=D(e,function(e){var o=e.name;return o===t}),i=!!n&&e.some(function(e){return e.name===o&&e.enabled&&e.order<n.order});if(!i){var r='`'+t+'`';console.warn('`'+o+'`'+' modifier is required by '+r+' modifier in order to work, be sure to include it before '+r+'!')}return i}function z(e){return'end'===e?'start':'start'===e?'end':e}function G(e){var t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],o=he.indexOf(e),n=he.slice(o+1).concat(he.slice(0,o));return t?n.reverse():n}function _(e,t,o,n){var i=e.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),r=+i[1],p=i[2];if(!r)return e;if(0===p.indexOf('%')){var s;switch(p){case'%p':s=o;break;case'%':case'%r':default:s=n;}var d=g(s);return d[t]/100*r}if('vh'===p||'vw'===p){var a;return a='vh'===p?ee(document.documentElement.clientHeight,window.innerHeight||0):ee(document.documentElement.clientWidth,window.innerWidth||0),a/100*r}return r}function X(e,t,o,n){var i=[0,0],r=-1!==['right','left'].indexOf(n),p=e.split(/(\+|\-)/).map(function(e){return e.trim()}),s=p.indexOf(D(p,function(e){return-1!==e.search(/,|\s/)}));p[s]&&-1===p[s].indexOf(',')&&console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.');var d=/\s*,\s*|\s+/,a=-1===s?[p]:[p.slice(0,s).concat([p[s].split(d)[0]]),[p[s].split(d)[1]].concat(p.slice(s+1))];return a=a.map(function(e,n){var i=(1===n?!r:r)?'height':'width',p=!1;return e.reduce(function(e,t){return''===e[e.length-1]&&-1!==['+','-'].indexOf(t)?(e[e.length-1]=t,p=!0,e):p?(e[e.length-1]+=t,p=!1,e):e.concat(t)},[]).map(function(e){return _(e,i,t,o)})}),a.forEach(function(e,t){e.forEach(function(o,n){Y(o)&&(i[t]+=o*('-'===e[n-1]?-1:1))})}),i}function J(e,t){var o,n=t.offset,i=e.placement,r=e.offsets,p=r.popper,s=r.reference,d=i.split('-')[0];return o=Y(+n)?[+n,0]:X(n,p,s,d),'left'===d?(p.top+=o[0],p.left-=o[1]):'right'===d?(p.top+=o[0],p.left+=o[1]):'top'===d?(p.left+=o[0],p.top-=o[1]):'bottom'===d&&(p.left+=o[0],p.top+=o[1]),e.popper=p,e}var Q=Math.min,Z=Math.floor,$=Math.round,ee=Math.max,te='undefined'!=typeof window&&'undefined'!=typeof document&&'undefined'!=typeof navigator,oe=function(){for(var e=['Edge','Trident','Firefox'],t=0;t<e.length;t+=1)if(te&&0<=navigator.userAgent.indexOf(e[t]))return 1;return 0}(),ne=te&&window.Promise,ie=ne?function(e){var t=!1;return function(){t||(t=!0,window.Promise.resolve().then(function(){t=!1,e()}))}}:function(e){var t=!1;return function(){t||(t=!0,setTimeout(function(){t=!1,e()},oe))}},re=te&&!!(window.MSInputMethodContext&&document.documentMode),pe=te&&/MSIE 10/.test(navigator.userAgent),se=function(e,t){if(!(e instanceof t))throw new TypeError('Cannot call a class as a function')},de=function(){function e(e,t){for(var o,n=0;n<t.length;n++)o=t[n],o.enumerable=o.enumerable||!1,o.configurable=!0,'value'in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}return function(t,o,n){return o&&e(t.prototype,o),n&&e(t,n),t}}(),ae=function(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e},le=Object.assign||function(e){for(var t,o=1;o<arguments.length;o++)for(var n in t=arguments[o],t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e},fe=te&&/Firefox/i.test(navigator.userAgent),me=['auto-start','auto','auto-end','top-start','top','top-end','right-start','right','right-end','bottom-end','bottom','bottom-start','left-end','left','left-start'],he=me.slice(3),ce={FLIP:'flip',CLOCKWISE:'clockwise',COUNTERCLOCKWISE:'counterclockwise'},ge=function(){function t(o,n){var i=this,r=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{};se(this,t),this.scheduleUpdate=function(){return requestAnimationFrame(i.update)},this.update=ie(this.update.bind(this)),this.options=le({},t.Defaults,r),this.state={isDestroyed:!1,isCreated:!1,scrollParents:[]},this.reference=o&&o.jquery?o[0]:o,this.popper=n&&n.jquery?n[0]:n,this.options.modifiers={},Object.keys(le({},t.Defaults.modifiers,r.modifiers)).forEach(function(e){i.options.modifiers[e]=le({},t.Defaults.modifiers[e]||{},r.modifiers?r.modifiers[e]:{})}),this.modifiers=Object.keys(this.options.modifiers).map(function(e){return le({name:e},i.options.modifiers[e])}).sort(function(e,t){return e.order-t.order}),this.modifiers.forEach(function(t){t.enabled&&e(t.onLoad)&&t.onLoad(i.reference,i.popper,i.options,t,i.state)}),this.update();var p=this.options.eventsEnabled;p&&this.enableEventListeners(),this.state.eventsEnabled=p}return de(t,[{key:'update',value:function(){return k.call(this)}},{key:'destroy',value:function(){return H.call(this)}},{key:'enableEventListeners',value:function(){return I.call(this)}},{key:'disableEventListeners',value:function(){return U.call(this)}}]),t}();return ge.Utils=('undefined'==typeof window?global:window).PopperUtils,ge.placements=me,ge.Defaults={placement:'bottom',positionFixed:!1,eventsEnabled:!0,removeOnDestroy:!1,onCreate:function(){},onUpdate:function(){},modifiers:{shift:{order:100,enabled:!0,fn:function(e){var t=e.placement,o=t.split('-')[0],n=t.split('-')[1];if(n){var i=e.offsets,r=i.reference,p=i.popper,s=-1!==['bottom','top'].indexOf(o),d=s?'left':'top',a=s?'width':'height',l={start:ae({},d,r[d]),end:ae({},d,r[d]+r[a]-p[a])};e.offsets.popper=le({},p,l[n])}return e}},offset:{order:200,enabled:!0,fn:J,offset:0},preventOverflow:{order:300,enabled:!0,fn:function(e,t){var o=t.boundariesElement||p(e.instance.popper);e.instance.reference===o&&(o=p(o));var n=B('transform'),i=e.instance.popper.style,r=i.top,s=i.left,d=i[n];i.top='',i.left='',i[n]='';var a=v(e.instance.popper,e.instance.reference,t.padding,o,e.positionFixed);i.top=r,i.left=s,i[n]=d,t.boundaries=a;var l=t.priority,f=e.offsets.popper,m={primary:function(e){var o=f[e];return f[e]<a[e]&&!t.escapeWithReference&&(o=ee(f[e],a[e])),ae({},e,o)},secondary:function(e){var o='right'===e?'left':'top',n=f[o];return f[e]>a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]<r(n[d])&&(e.offsets.popper[d]=r(n[d])-o[a]),o[d]>r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-u<s[m]&&(e.offsets.popper[m]-=s[m]-(d[c]-u)),d[m]+u>s[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f]),E=parseFloat(w['border'+f+'Width']),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)<f(l.right)||'top'===n&&f(a.bottom)>f(l.top)||'bottom'===n&&f(a.top)<f(l.bottom),h=f(a.left)<f(o.left),c=f(a.right)>f(o.right),g=f(a.top)<f(o.top),u=f(a.bottom)>f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottom<o.top||t.left>o.right||t.top>o.bottom||t.right<o.left){if(!0===e.hide)return e;e.hide=!0,e.attributes['x-out-of-boundaries']=''}else{if(!1===e.hide)return e;e.hide=!1,e.attributes['x-out-of-boundaries']=!1}return e}},computeStyle:{order:850,enabled:!0,fn:function(e,t){var o=t.x,n=t.y,i=e.offsets.popper,r=D(e.instance.modifiers,function(e){return'applyStyle'===e.name}).gpuAcceleration;void 0!==r&&console.warn('WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!');var s,d,a=void 0===r?t.gpuAcceleration:r,l=p(e.instance.popper),f=u(l),m={position:i.position},h=q(e,2>window.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge});
  7. //# sourceMappingURL=popper.min.js.map
  8. ;//! openseadragon 2.4.1
  9. //! Built on 2019-07-03
  10. //! Git commit: v2.4.1-0-244790e
  11. //! http://openseadragon.github.io
  12. //! License: http://openseadragon.github.io/license/
  13. /*
  14. * OpenSeadragon
  15. *
  16. * Copyright (C) 2009 CodePlex Foundation
  17. * Copyright (C) 2010-2013 OpenSeadragon contributors
  18. *
  19. * Redistribution and use in source and binary forms, with or without
  20. * modification, are permitted provided that the following conditions are
  21. * met:
  22. *
  23. * - Redistributions of source code must retain the above copyright notice,
  24. * this list of conditions and the following disclaimer.
  25. *
  26. * - Redistributions in binary form must reproduce the above copyright
  27. * notice, this list of conditions and the following disclaimer in the
  28. * documentation and/or other materials provided with the distribution.
  29. *
  30. * - Neither the name of CodePlex Foundation nor the names of its
  31. * contributors may be used to endorse or promote products derived from
  32. * this software without specific prior written permission.
  33. *
  34. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  35. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  36. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  37. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  38. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  39. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  40. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  41. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  42. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  43. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  44. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  45. */
  46. /*
  47. * Portions of this source file taken from jQuery:
  48. *
  49. * Copyright 2011 John Resig
  50. *
  51. * Permission is hereby granted, free of charge, to any person obtaining
  52. * a copy of this software and associated documentation files (the
  53. * "Software"), to deal in the Software without restriction, including
  54. * without limitation the rights to use, copy, modify, merge, publish,
  55. * distribute, sublicense, and/or sell copies of the Software, and to
  56. * permit persons to whom the Software is furnished to do so, subject to
  57. * the following conditions:
  58. *
  59. * The above copyright notice and this permission notice shall be
  60. * included in all copies or substantial portions of the Software.
  61. *
  62. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  63. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  64. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  65. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  66. * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  67. * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  68. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  69. */
  70. /*
  71. * Portions of this source file taken from mattsnider.com:
  72. *
  73. * Copyright (c) 2006-2013 Matt Snider
  74. *
  75. * Permission is hereby granted, free of charge, to any person obtaining a
  76. * copy of this software and associated documentation files (the "Software"),
  77. * to deal in the Software without restriction, including without limitation
  78. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  79. * and/or sell copies of the Software, and to permit persons to whom the
  80. * Software is furnished to do so, subject to the following conditions:
  81. *
  82. * The above copyright notice and this permission notice shall be included
  83. * in all copies or substantial portions of the Software.
  84. *
  85. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  86. * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  87. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  88. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  89. * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
  90. * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
  91. * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  92. */
  93. /**
  94. * @namespace OpenSeadragon
  95. * @version openseadragon 2.4.1
  96. * @classdesc The root namespace for OpenSeadragon. All utility methods
  97. * and classes are defined on or below this namespace.
  98. *
  99. */
  100. // Typedefs
  101. /**
  102. * All required and optional settings for instantiating a new instance of an OpenSeadragon image viewer.
  103. *
  104. * @typedef {Object} Options
  105. * @memberof OpenSeadragon
  106. *
  107. * @property {String} id
  108. * Id of the element to append the viewer's container element to. If not provided, the 'element' property must be provided.
  109. * If both the element and id properties are specified, the viewer is appended to the element provided in the element property.
  110. *
  111. * @property {Element} element
  112. * The element to append the viewer's container element to. If not provided, the 'id' property must be provided.
  113. * If both the element and id properties are specified, the viewer is appended to the element provided in the element property.
  114. *
  115. * @property {Array|String|Function|Object} [tileSources=null]
  116. * Tile source(s) to open initially. This is a complex parameter; see
  117. * {@link OpenSeadragon.Viewer#open} for details.
  118. *
  119. * @property {Number} [tabIndex=0]
  120. * Tabbing order index to assign to the viewer element. Positive values are selected in increasing order. When tabIndex is 0
  121. * source order is used. A negative value omits the viewer from the tabbing order.
  122. *
  123. * @property {Array} overlays Array of objects defining permanent overlays of
  124. * the viewer. The overlays added via this option and later removed with
  125. * {@link OpenSeadragon.Viewer#removeOverlay} will be added back when a new
  126. * image is opened.
  127. * To add overlays which can be definitively removed, one must use
  128. * {@link OpenSeadragon.Viewer#addOverlay}
  129. * If displaying a sequence of images, the overlays can be associated
  130. * with a specific page by passing the overlays array to the page's
  131. * tile source configuration.
  132. * Expected properties:
  133. * * x, y, (or px, py for pixel coordinates) to define the location.
  134. * * width, height in point if using x,y or in pixels if using px,py. If width
  135. * and height are specified, the overlay size is adjusted when zooming,
  136. * otherwise the size stays the size of the content (or the size defined by CSS).
  137. * * className to associate a class to the overlay
  138. * * id to set the overlay element. If an element with this id already exists,
  139. * it is reused, otherwise it is created. If not specified, a new element is
  140. * created.
  141. * * placement a string to define the relative position to the viewport.
  142. * Only used if no width and height are specified. Default: 'TOP_LEFT'.
  143. * See {@link OpenSeadragon.Placement} for possible values.
  144. *
  145. * @property {String} [xmlPath=null]
  146. * <strong>DEPRECATED</strong>. A relative path to load a DZI file from the server.
  147. * Prefer the newer Options.tileSources.
  148. *
  149. * @property {String} [prefixUrl='/images/']
  150. * Prepends the prefixUrl to navImages paths, which is very useful
  151. * since the default paths are rarely useful for production
  152. * environments.
  153. *
  154. * @property {OpenSeadragon.NavImages} [navImages]
  155. * An object with a property for each button or other built-in navigation
  156. * control, eg the current 'zoomIn', 'zoomOut', 'home', and 'fullpage'.
  157. * Each of those in turn provides an image path for each state of the button
  158. * or navigation control, eg 'REST', 'GROUP', 'HOVER', 'PRESS'. Finally the
  159. * image paths, by default assume there is a folder on the servers root path
  160. * called '/images', eg '/images/zoomin_rest.png'. If you need to adjust
  161. * these paths, prefer setting the option.prefixUrl rather than overriding
  162. * every image path directly through this setting.
  163. *
  164. * @property {Boolean} [debugMode=false]
  165. * TODO: provide an in-screen panel providing event detail feedback.
  166. *
  167. * @property {String} [debugGridColor=['#437AB2', '#1B9E77', '#D95F02', '#7570B3', '#E7298A', '#66A61E', '#E6AB02', '#A6761D', '#666666']]
  168. * The colors of grids in debug mode. Each tiled image's grid uses a consecutive color.
  169. * If there are more tiled images than provided colors, the color vector is recycled.
  170. *
  171. * @property {Number} [blendTime=0]
  172. * Specifies the duration of animation as higher or lower level tiles are
  173. * replacing the existing tile.
  174. *
  175. * @property {Boolean} [alwaysBlend=false]
  176. * Forces the tile to always blend. By default the tiles skip blending
  177. * when the blendTime is surpassed and the current animation frame would
  178. * not complete the blend.
  179. *
  180. * @property {Boolean} [autoHideControls=true]
  181. * If the user stops interacting with the viewport, fade the navigation
  182. * controls. Useful for presentation since the controls are by default
  183. * floated on top of the image the user is viewing.
  184. *
  185. * @property {Boolean} [immediateRender=false]
  186. * Render the best closest level first, ignoring the lowering levels which
  187. * provide the effect of very blurry to sharp. It is recommended to change
  188. * setting to true for mobile devices.
  189. *
  190. * @property {Number} [defaultZoomLevel=0]
  191. * Zoom level to use when image is first opened or the home button is clicked.
  192. * If 0, adjusts to fit viewer.
  193. *
  194. * @property {Number} [opacity=1]
  195. * Default proportional opacity of the tiled images (1=opaque, 0=hidden)
  196. * Hidden images do not draw and only load when preloading is allowed.
  197. *
  198. * @property {Boolean} [preload=false]
  199. * Default switch for loading hidden images (true loads, false blocks)
  200. *
  201. * @property {String} [compositeOperation=null]
  202. * Valid values are 'source-over', 'source-atop', 'source-in', 'source-out',
  203. * 'destination-over', 'destination-atop', 'destination-in',
  204. * 'destination-out', 'lighter', 'copy' or 'xor'
  205. *
  206. * @property {Boolean} [imageSmoothingEnabled=true]
  207. * Image smoothing for canvas rendering (only if canvas is used). Note: Ignored
  208. * by some (especially older) browsers which do not support this canvas property.
  209. * This property can be changed in {@link Viewer.Drawer.setImageSmoothingEnabled}.
  210. *
  211. * @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null]
  212. * Draws a colored rectangle behind the tile if it is not loaded yet.
  213. * You can pass a CSS color value like "#FF8800".
  214. * When passing a function the tiledImage and canvas context are available as argument which is useful when you draw a gradient or pattern.
  215. *
  216. * @property {Number} [degrees=0]
  217. * Initial rotation.
  218. *
  219. * @property {Boolean} [flipped=false]
  220. * Initial flip state.
  221. *
  222. * @property {Number} [minZoomLevel=null]
  223. *
  224. * @property {Number} [maxZoomLevel=null]
  225. *
  226. * @property {Boolean} [homeFillsViewer=false]
  227. * Make the 'home' button fill the viewer and clip the image, instead
  228. * of fitting the image to the viewer and letterboxing.
  229. *
  230. * @property {Boolean} [panHorizontal=true]
  231. * Allow horizontal pan.
  232. *
  233. * @property {Boolean} [panVertical=true]
  234. * Allow vertical pan.
  235. *
  236. * @property {Boolean} [constrainDuringPan=false]
  237. *
  238. * @property {Boolean} [wrapHorizontal=false]
  239. * Set to true to force the image to wrap horizontally within the viewport.
  240. * Useful for maps or images representing the surface of a sphere or cylinder.
  241. *
  242. * @property {Boolean} [wrapVertical=false]
  243. * Set to true to force the image to wrap vertically within the viewport.
  244. * Useful for maps or images representing the surface of a sphere or cylinder.
  245. *
  246. * @property {Number} [minZoomImageRatio=0.9]
  247. * The minimum percentage ( expressed as a number between 0 and 1 ) of
  248. * the viewport height or width at which the zoom out will be constrained.
  249. * Setting it to 0, for example will allow you to zoom out infinity.
  250. *
  251. * @property {Number} [maxZoomPixelRatio=1.1]
  252. * The maximum ratio to allow a zoom-in to affect the highest level pixel
  253. * ratio. This can be set to Infinity to allow 'infinite' zooming into the
  254. * image though it is less effective visually if the HTML5 Canvas is not
  255. * available on the viewing device.
  256. *
  257. * @property {Number} [smoothTileEdgesMinZoom=1.1]
  258. * A zoom percentage ( where 1 is 100% ) of the highest resolution level.
  259. * When zoomed in beyond this value alternative compositing will be used to
  260. * smooth out the edges between tiles. This will have a performance impact.
  261. * Can be set to Infinity to turn it off.
  262. * Note: This setting is ignored on iOS devices due to a known bug (See {@link https://github.com/openseadragon/openseadragon/issues/952})
  263. *
  264. * @property {Boolean} [iOSDevice=?]
  265. * True if running on an iOS device, false otherwise.
  266. * Used to disable certain features that behave differently on iOS devices.
  267. *
  268. * @property {Boolean} [autoResize=true]
  269. * Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior.
  270. *
  271. * @property {Boolean} [preserveImageSizeOnResize=false]
  272. * Set to true to have the image size preserved when the viewer is resized. This requires autoResize=true (default).
  273. *
  274. * @property {Number} [minScrollDeltaTime=50]
  275. * Number of milliseconds between canvas-scroll events. This value helps normalize the rate of canvas-scroll
  276. * events between different devices, causing the faster devices to slow down enough to make the zoom control
  277. * more manageable.
  278. *
  279. * @property {Number} [rotationIncrement=90]
  280. * The number of degrees to rotate right or left when the rotate buttons or keyboard shortcuts are activated.
  281. *
  282. * @property {Number} [pixelsPerWheelLine=40]
  283. * For pixel-resolution scrolling devices, the number of pixels equal to one scroll line.
  284. *
  285. * @property {Number} [pixelsPerArrowPress=40]
  286. * The number of pixels viewport moves when an arrow key is pressed.
  287. *
  288. * @property {Number} [visibilityRatio=0.5]
  289. * The percentage ( as a number from 0 to 1 ) of the source image which
  290. * must be kept within the viewport. If the image is dragged beyond that
  291. * limit, it will 'bounce' back until the minimum visibility ratio is
  292. * achieved. Setting this to 0 and wrapHorizontal ( or wrapVertical ) to
  293. * true will provide the effect of an infinitely scrolling viewport.
  294. *
  295. * @property {Object} [viewportMargins={}]
  296. * Pushes the "home" region in from the sides by the specified amounts.
  297. * Possible subproperties (Numbers, in screen coordinates): left, top, right, bottom.
  298. *
  299. * @property {Number} [imageLoaderLimit=0]
  300. * The maximum number of image requests to make concurrently. By default
  301. * it is set to 0 allowing the browser to make the maximum number of
  302. * image requests in parallel as allowed by the browsers policy.
  303. *
  304. * @property {Number} [clickTimeThreshold=300]
  305. * The number of milliseconds within which a pointer down-up event combination
  306. * will be treated as a click gesture.
  307. *
  308. * @property {Number} [clickDistThreshold=5]
  309. * The maximum distance allowed between a pointer down event and a pointer up event
  310. * to be treated as a click gesture.
  311. *
  312. * @property {Number} [dblClickTimeThreshold=300]
  313. * The number of milliseconds within which two pointer down-up event combinations
  314. * will be treated as a double-click gesture.
  315. *
  316. * @property {Number} [dblClickDistThreshold=20]
  317. * The maximum distance allowed between two pointer click events
  318. * to be treated as a double-click gesture.
  319. *
  320. * @property {Number} [springStiffness=6.5]
  321. *
  322. * @property {Number} [animationTime=1.2]
  323. * Specifies the animation duration per each {@link OpenSeadragon.Spring}
  324. * which occur when the image is dragged or zoomed.
  325. *
  326. * @property {OpenSeadragon.GestureSettings} [gestureSettingsMouse]
  327. * Settings for gestures generated by a mouse pointer device. (See {@link OpenSeadragon.GestureSettings})
  328. * @property {Boolean} [gestureSettingsMouse.scrollToZoom=true] - Zoom on scroll gesture
  329. * @property {Boolean} [gestureSettingsMouse.clickToZoom=true] - Zoom on click gesture
  330. * @property {Boolean} [gestureSettingsMouse.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true
  331. * then clickToZoom should be set to false to prevent multiple zooms.
  332. * @property {Boolean} [gestureSettingsMouse.pinchToZoom=false] - Zoom on pinch gesture
  333. * @property {Boolean} [gestureSettingsMouse.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
  334. * the zoom is centered at the canvas center.
  335. * @property {Boolean} [gestureSettingsMouse.flickEnabled=false] - Enable flick gesture
  336. * @property {Number} [gestureSettingsMouse.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
  337. * @property {Number} [gestureSettingsMouse.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
  338. * @property {Boolean} [gestureSettingsMouse.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
  339. *
  340. * @property {OpenSeadragon.GestureSettings} [gestureSettingsTouch]
  341. * Settings for gestures generated by a touch pointer device. (See {@link OpenSeadragon.GestureSettings})
  342. * @property {Boolean} [gestureSettingsTouch.scrollToZoom=false] - Zoom on scroll gesture
  343. * @property {Boolean} [gestureSettingsTouch.clickToZoom=false] - Zoom on click gesture
  344. * @property {Boolean} [gestureSettingsTouch.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true
  345. * then clickToZoom should be set to false to prevent multiple zooms.
  346. * @property {Boolean} [gestureSettingsTouch.pinchToZoom=true] - Zoom on pinch gesture
  347. * @property {Boolean} [gestureSettingsTouch.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
  348. * the zoom is centered at the canvas center.
  349. * @property {Boolean} [gestureSettingsTouch.flickEnabled=true] - Enable flick gesture
  350. * @property {Number} [gestureSettingsTouch.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
  351. * @property {Number} [gestureSettingsTouch.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
  352. * @property {Boolean} [gestureSettingsTouch.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
  353. *
  354. * @property {OpenSeadragon.GestureSettings} [gestureSettingsPen]
  355. * Settings for gestures generated by a pen pointer device. (See {@link OpenSeadragon.GestureSettings})
  356. * @property {Boolean} [gestureSettingsPen.scrollToZoom=false] - Zoom on scroll gesture
  357. * @property {Boolean} [gestureSettingsPen.clickToZoom=true] - Zoom on click gesture
  358. * @property {Boolean} [gestureSettingsPen.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true
  359. * then clickToZoom should be set to false to prevent multiple zooms.
  360. * @property {Boolean} [gestureSettingsPen.pinchToZoom=false] - Zoom on pinch gesture
  361. * @property {Boolean} [gestureSettingsPan.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
  362. * the zoom is centered at the canvas center.
  363. * @property {Boolean} [gestureSettingsPen.flickEnabled=false] - Enable flick gesture
  364. * @property {Number} [gestureSettingsPen.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
  365. * @property {Number} [gestureSettingsPen.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
  366. * @property {Boolean} [gestureSettingsPen.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
  367. *
  368. * @property {OpenSeadragon.GestureSettings} [gestureSettingsUnknown]
  369. * Settings for gestures generated by unknown pointer devices. (See {@link OpenSeadragon.GestureSettings})
  370. * @property {Boolean} [gestureSettingsUnknown.scrollToZoom=true] - Zoom on scroll gesture
  371. * @property {Boolean} [gestureSettingsUnknown.clickToZoom=false] - Zoom on click gesture
  372. * @property {Boolean} [gestureSettingsUnknown.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true
  373. * then clickToZoom should be set to false to prevent multiple zooms.
  374. * @property {Boolean} [gestureSettingsUnknown.pinchToZoom=true] - Zoom on pinch gesture
  375. * @property {Boolean} [gestureSettingsUnknown.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
  376. * the zoom is centered at the canvas center.
  377. * @property {Boolean} [gestureSettingsUnknown.flickEnabled=true] - Enable flick gesture
  378. * @property {Number} [gestureSettingsUnknown.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
  379. * @property {Number} [gestureSettingsUnknown.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
  380. * @property {Boolean} [gestureSettingsUnknown.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
  381. *
  382. * @property {Number} [zoomPerClick=2.0]
  383. * The "zoom distance" per mouse click or touch tap. <em><strong>Note:</strong> Setting this to 1.0 effectively disables the click-to-zoom feature (also see gestureSettings[Mouse|Touch|Pen].clickToZoom/dblClickToZoom).</em>
  384. *
  385. * @property {Number} [zoomPerScroll=1.2]
  386. * The "zoom distance" per mouse scroll or touch pinch. <em><strong>Note:</strong> Setting this to 1.0 effectively disables the mouse-wheel zoom feature (also see gestureSettings[Mouse|Touch|Pen].scrollToZoom}).</em>
  387. *
  388. * @property {Number} [zoomPerSecond=1.0]
  389. * The number of seconds to animate a single zoom event over.
  390. *
  391. * @property {Boolean} [showNavigator=false]
  392. * Set to true to make the navigator minimap appear.
  393. *
  394. * @property {String} [navigatorId=navigator-GENERATED DATE]
  395. * The ID of a div to hold the navigator minimap.
  396. * If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, navigator[Top|Left|Height|Width] and navigatorAutoFade options will be ignored.
  397. * If an ID is not specified, a div element will be generated and placed on top of the main image.
  398. *
  399. * @property {String} [navigatorPosition='TOP_RIGHT']
  400. * Valid values are 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', 'BOTTOM_RIGHT', or 'ABSOLUTE'.<br>
  401. * If 'ABSOLUTE' is specified, then navigator[Top|Left|Height|Width] determines the size and position of the navigator minimap in the viewer, and navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.<br>
  402. * For 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', and 'BOTTOM_RIGHT', the navigatorSizeRatio or navigator[Height|Width] values determine the size of the navigator minimap.
  403. *
  404. * @property {Number} [navigatorSizeRatio=0.2]
  405. * Ratio of navigator size to viewer size. Ignored if navigator[Height|Width] are specified.
  406. *
  407. * @property {Boolean} [navigatorMaintainSizeRatio=false]
  408. * If true, the navigator minimap is resized (using navigatorSizeRatio) when the viewer size changes.
  409. *
  410. * @property {Number|String} [navigatorTop=null]
  411. * Specifies the location of the navigator minimap (see navigatorPosition).
  412. *
  413. * @property {Number|String} [navigatorLeft=null]
  414. * Specifies the location of the navigator minimap (see navigatorPosition).
  415. *
  416. * @property {Number|String} [navigatorHeight=null]
  417. * Specifies the size of the navigator minimap (see navigatorPosition).
  418. * If specified, navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.
  419. *
  420. * @property {Number|String} [navigatorWidth=null]
  421. * Specifies the size of the navigator minimap (see navigatorPosition).
  422. * If specified, navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.
  423. *
  424. * @property {Boolean} [navigatorAutoResize=true]
  425. * Set to false to prevent polling for navigator size changes. Useful for providing custom resize behavior.
  426. * Setting to false can also improve performance when the navigator is configured to a fixed size.
  427. *
  428. * @property {Boolean} [navigatorAutoFade=true]
  429. * If the user stops interacting with the viewport, fade the navigator minimap.
  430. * Setting to false will make the navigator minimap always visible.
  431. *
  432. * @property {Boolean} [navigatorRotate=true]
  433. * If true, the navigator will be rotated together with the viewer.
  434. *
  435. * @property {String} [navigatorBackground='#000']
  436. * Specifies the background color of the navigator minimap
  437. *
  438. * @property {Number} [navigatorOpacity=0.8]
  439. * Specifies the opacity of the navigator minimap.
  440. *
  441. * @property {String} [navigatorBorderColor='#555']
  442. * Specifies the border color of the navigator minimap
  443. *
  444. * @property {String} [navigatorDisplayRegionColor='#900']
  445. * Specifies the border color of the display region rectangle of the navigator minimap
  446. *
  447. * @property {Number} [controlsFadeDelay=2000]
  448. * The number of milliseconds to wait once the user has stopped interacting
  449. * with the interface before beginning to fade the controls. Assumes
  450. * showNavigationControl and autoHideControls are both true.
  451. *
  452. * @property {Number} [controlsFadeLength=1500]
  453. * The number of milliseconds to animate the controls fading out.
  454. *
  455. * @property {Number} [maxImageCacheCount=200]
  456. * The max number of images we should keep in memory (per drawer).
  457. *
  458. * @property {Number} [timeout=30000]
  459. * The max number of milliseconds that an image job may take to complete.
  460. *
  461. * @property {Boolean} [useCanvas=true]
  462. * Set to false to not use an HTML canvas element for image rendering even if canvas is supported.
  463. *
  464. * @property {Number} [minPixelRatio=0.5]
  465. * The higher the minPixelRatio, the lower the quality of the image that
  466. * is considered sufficient to stop rendering a given zoom level. For
  467. * example, if you are targeting mobile devices with less bandwidth you may
  468. * try setting this to 1.5 or higher.
  469. *
  470. * @property {Boolean} [mouseNavEnabled=true]
  471. * Is the user able to interact with the image via mouse or touch. Default
  472. * interactions include draging the image in a plane, and zooming in toward
  473. * and away from the image.
  474. *
  475. * @property {Boolean} [showNavigationControl=true]
  476. * Set to false to prevent the appearance of the default navigation controls.<br>
  477. * Note that if set to false, the customs buttons set by the options
  478. * zoomInButton, zoomOutButton etc, are rendered inactive.
  479. *
  480. * @property {OpenSeadragon.ControlAnchor} [navigationControlAnchor=TOP_LEFT]
  481. * Placement of the default navigation controls.
  482. * To set the placement of the sequence controls, see the
  483. * sequenceControlAnchor option.
  484. *
  485. * @property {Boolean} [showZoomControl=true]
  486. * If true then + and - buttons to zoom in and out are displayed.<br>
  487. * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
  488. * this setting when set to false.
  489. *
  490. * @property {Boolean} [showHomeControl=true]
  491. * If true then the 'Go home' button is displayed to go back to the original
  492. * zoom and pan.<br>
  493. * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
  494. * this setting when set to false.
  495. *
  496. * @property {Boolean} [showFullPageControl=true]
  497. * If true then the 'Toggle full page' button is displayed to switch
  498. * between full page and normal mode.<br>
  499. * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
  500. * this setting when set to false.
  501. *
  502. * @property {Boolean} [showRotationControl=false]
  503. * If true then the rotate left/right controls will be displayed as part of the
  504. * standard controls. This is also subject to the browser support for rotate
  505. * (e.g. viewer.drawer.canRotate()).<br>
  506. * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
  507. * this setting when set to false.
  508. *
  509. * @property {Boolean} [showFlipControl=false]
  510. * If true then the flip controls will be displayed as part of the
  511. * standard controls.
  512. *
  513. * @property {Boolean} [showSequenceControl=true]
  514. * If sequenceMode is true, then provide buttons for navigating forward and
  515. * backward through the images.
  516. *
  517. * @property {OpenSeadragon.ControlAnchor} [sequenceControlAnchor=TOP_LEFT]
  518. * Placement of the default sequence controls.
  519. *
  520. * @property {Boolean} [navPrevNextWrap=false]
  521. * If true then the 'previous' button will wrap to the last image when
  522. * viewing the first image and the 'next' button will wrap to the first
  523. * image when viewing the last image.
  524. *
  525. * @property {String} zoomInButton
  526. * Set the id of the custom 'Zoom in' button to use.
  527. * This is useful to have a custom button anywhere in the web page.<br>
  528. * To only change the button images, consider using
  529. * {@link OpenSeadragon.Options.navImages}
  530. *
  531. * @property {String} zoomOutButton
  532. * Set the id of the custom 'Zoom out' button to use.
  533. * This is useful to have a custom button anywhere in the web page.<br>
  534. * To only change the button images, consider using
  535. * {@link OpenSeadragon.Options.navImages}
  536. *
  537. * @property {String} homeButton
  538. * Set the id of the custom 'Go home' button to use.
  539. * This is useful to have a custom button anywhere in the web page.<br>
  540. * To only change the button images, consider using
  541. * {@link OpenSeadragon.Options.navImages}
  542. *
  543. * @property {String} fullPageButton
  544. * Set the id of the custom 'Toggle full page' button to use.
  545. * This is useful to have a custom button anywhere in the web page.<br>
  546. * To only change the button images, consider using
  547. * {@link OpenSeadragon.Options.navImages}
  548. *
  549. * @property {String} rotateLeftButton
  550. * Set the id of the custom 'Rotate left' button to use.
  551. * This is useful to have a custom button anywhere in the web page.<br>
  552. * To only change the button images, consider using
  553. * {@link OpenSeadragon.Options.navImages}
  554. *
  555. * @property {String} rotateRightButton
  556. * Set the id of the custom 'Rotate right' button to use.
  557. * This is useful to have a custom button anywhere in the web page.<br>
  558. * To only change the button images, consider using
  559. * {@link OpenSeadragon.Options.navImages}
  560. *
  561. * @property {String} previousButton
  562. * Set the id of the custom 'Previous page' button to use.
  563. * This is useful to have a custom button anywhere in the web page.<br>
  564. * To only change the button images, consider using
  565. * {@link OpenSeadragon.Options.navImages}
  566. *
  567. * @property {String} nextButton
  568. * Set the id of the custom 'Next page' button to use.
  569. * This is useful to have a custom button anywhere in the web page.<br>
  570. * To only change the button images, consider using
  571. * {@link OpenSeadragon.Options.navImages}
  572. *
  573. * @property {Boolean} [sequenceMode=false]
  574. * Set to true to have the viewer treat your tilesources as a sequence of images to
  575. * be opened one at a time rather than all at once.
  576. *
  577. * @property {Number} [initialPage=0]
  578. * If sequenceMode is true, display this page initially.
  579. *
  580. * @property {Boolean} [preserveViewport=false]
  581. * If sequenceMode is true, then normally navigating through each image resets the
  582. * viewport to 'home' position. If preserveViewport is set to true, then the viewport
  583. * position is preserved when navigating between images in the sequence.
  584. *
  585. * @property {Boolean} [preserveOverlays=false]
  586. * If sequenceMode is true, then normally navigating through each image
  587. * resets the overlays.
  588. * If preserveOverlays is set to true, then the overlays added with {@link OpenSeadragon.Viewer#addOverlay}
  589. * are preserved when navigating between images in the sequence.
  590. * Note: setting preserveOverlays overrides any overlays specified in the global
  591. * "overlays" option for the Viewer. It's also not compatible with specifying
  592. * per-tileSource overlays via the options, as those overlays will persist
  593. * even after the tileSource is closed.
  594. *
  595. * @property {Boolean} [showReferenceStrip=false]
  596. * If sequenceMode is true, then display a scrolling strip of image thumbnails for
  597. * navigating through the images.
  598. *
  599. * @property {String} [referenceStripScroll='horizontal']
  600. *
  601. * @property {Element} [referenceStripElement=null]
  602. *
  603. * @property {Number} [referenceStripHeight=null]
  604. *
  605. * @property {Number} [referenceStripWidth=null]
  606. *
  607. * @property {String} [referenceStripPosition='BOTTOM_LEFT']
  608. *
  609. * @property {Number} [referenceStripSizeRatio=0.2]
  610. *
  611. * @property {Boolean} [collectionMode=false]
  612. * Set to true to have the viewer arrange your TiledImages in a grid or line.
  613. *
  614. * @property {Number} [collectionRows=3]
  615. * If collectionMode is true, specifies how many rows the grid should have. Use 1 to make a line.
  616. * If collectionLayout is 'vertical', specifies how many columns instead.
  617. *
  618. * @property {Number} [collectionColumns=0]
  619. * If collectionMode is true, specifies how many columns the grid should have. Use 1 to make a line.
  620. * If collectionLayout is 'vertical', specifies how many rows instead. Ignored if collectionRows is not set to a falsy value.
  621. *
  622. * @property {String} [collectionLayout='horizontal']
  623. * If collectionMode is true, specifies whether to arrange vertically or horizontally.
  624. *
  625. * @property {Number} [collectionTileSize=800]
  626. * If collectionMode is true, specifies the size, in viewport coordinates, for each TiledImage to fit into.
  627. * The TiledImage will be centered within a square of the specified size.
  628. *
  629. * @property {Number} [collectionTileMargin=80]
  630. * If collectionMode is true, specifies the margin, in viewport coordinates, between each TiledImage.
  631. *
  632. * @property {String|Boolean} [crossOriginPolicy=false]
  633. * Valid values are 'Anonymous', 'use-credentials', and false. If false, canvas requests will
  634. * not use CORS, and the canvas will be tainted.
  635. *
  636. * @property {Boolean} [ajaxWithCredentials=false]
  637. * Whether to set the withCredentials XHR flag for AJAX requests.
  638. * Note that this can be overridden at the {@link OpenSeadragon.TileSource} level.
  639. *
  640. * @property {Boolean} [loadTilesWithAjax=false]
  641. * Whether to load tile data using AJAX requests.
  642. * Note that this can be overridden at the {@link OpenSeadragon.TileSource} level.
  643. *
  644. * @property {Object} [ajaxHeaders={}]
  645. * A set of headers to include when making AJAX requests for tile sources or tiles.
  646. *
  647. */
  648. /**
  649. * Settings for gestures generated by a pointer device.
  650. *
  651. * @typedef {Object} GestureSettings
  652. * @memberof OpenSeadragon
  653. *
  654. * @property {Boolean} scrollToZoom
  655. * Set to false to disable zooming on scroll gestures.
  656. *
  657. * @property {Boolean} clickToZoom
  658. * Set to false to disable zooming on click gestures.
  659. *
  660. * @property {Boolean} dblClickToZoom
  661. * Set to false to disable zooming on double-click gestures. Note: If set to true
  662. * then clickToZoom should be set to false to prevent multiple zooms.
  663. *
  664. * @property {Boolean} pinchToZoom
  665. * Set to false to disable zooming on pinch gestures.
  666. *
  667. * @property {Boolean} flickEnabled
  668. * Set to false to disable the kinetic panning effect (flick) at the end of a drag gesture.
  669. *
  670. * @property {Number} flickMinSpeed
  671. * If flickEnabled is true, the minimum speed (in pixels-per-second) required to cause the kinetic panning effect (flick) at the end of a drag gesture.
  672. *
  673. * @property {Number} flickMomentum
  674. * If flickEnabled is true, a constant multiplied by the velocity to determine the distance of the kinetic panning effect (flick) at the end of a drag gesture.
  675. * A larger value will make the flick feel "lighter", while a smaller value will make the flick feel "heavier".
  676. * Note: springStiffness and animationTime also affect the "spring" used to stop the flick animation.
  677. *
  678. */
  679. /**
  680. * The names for the image resources used for the image navigation buttons.
  681. *
  682. * @typedef {Object} NavImages
  683. * @memberof OpenSeadragon
  684. *
  685. * @property {Object} zoomIn - Images for the zoom-in button.
  686. * @property {String} zoomIn.REST
  687. * @property {String} zoomIn.GROUP
  688. * @property {String} zoomIn.HOVER
  689. * @property {String} zoomIn.DOWN
  690. *
  691. * @property {Object} zoomOut - Images for the zoom-out button.
  692. * @property {String} zoomOut.REST
  693. * @property {String} zoomOut.GROUP
  694. * @property {String} zoomOut.HOVER
  695. * @property {String} zoomOut.DOWN
  696. *
  697. * @property {Object} home - Images for the home button.
  698. * @property {String} home.REST
  699. * @property {String} home.GROUP
  700. * @property {String} home.HOVER
  701. * @property {String} home.DOWN
  702. *
  703. * @property {Object} fullpage - Images for the full-page button.
  704. * @property {String} fullpage.REST
  705. * @property {String} fullpage.GROUP
  706. * @property {String} fullpage.HOVER
  707. * @property {String} fullpage.DOWN
  708. *
  709. * @property {Object} rotateleft - Images for the rotate left button.
  710. * @property {String} rotateleft.REST
  711. * @property {String} rotateleft.GROUP
  712. * @property {String} rotateleft.HOVER
  713. * @property {String} rotateleft.DOWN
  714. *
  715. * @property {Object} rotateright - Images for the rotate right button.
  716. * @property {String} rotateright.REST
  717. * @property {String} rotateright.GROUP
  718. * @property {String} rotateright.HOVER
  719. * @property {String} rotateright.DOWN
  720. *
  721. * @property {Object} flip - Images for the flip button.
  722. * @property {String} flip.REST
  723. * @property {String} flip.GROUP
  724. * @property {String} flip.HOVER
  725. * @property {String} flip.DOWN
  726. *
  727. * @property {Object} previous - Images for the previous button.
  728. * @property {String} previous.REST
  729. * @property {String} previous.GROUP
  730. * @property {String} previous.HOVER
  731. * @property {String} previous.DOWN
  732. *
  733. * @property {Object} next - Images for the next button.
  734. * @property {String} next.REST
  735. * @property {String} next.GROUP
  736. * @property {String} next.HOVER
  737. * @property {String} next.DOWN
  738. *
  739. */
  740. function OpenSeadragon( options ){
  741. return new OpenSeadragon.Viewer( options );
  742. }
  743. (function( $ ){
  744. /**
  745. * The OpenSeadragon version.
  746. *
  747. * @member {Object} OpenSeadragon.version
  748. * @property {String} versionStr - The version number as a string ('major.minor.revision').
  749. * @property {Number} major - The major version number.
  750. * @property {Number} minor - The minor version number.
  751. * @property {Number} revision - The revision number.
  752. * @since 1.0.0
  753. */
  754. $.version = {
  755. versionStr: '2.4.1',
  756. major: parseInt('2', 10),
  757. minor: parseInt('4', 10),
  758. revision: parseInt('1', 10)
  759. };
  760. /**
  761. * Taken from jquery 1.6.1
  762. * [[Class]] -> type pairs
  763. * @private
  764. */
  765. var class2type = {
  766. '[object Boolean]': 'boolean',
  767. '[object Number]': 'number',
  768. '[object String]': 'string',
  769. '[object Function]': 'function',
  770. '[object Array]': 'array',
  771. '[object Date]': 'date',
  772. '[object RegExp]': 'regexp',
  773. '[object Object]': 'object'
  774. },
  775. // Save a reference to some core methods
  776. toString = Object.prototype.toString,
  777. hasOwn = Object.prototype.hasOwnProperty;
  778. /**
  779. * Taken from jQuery 1.6.1
  780. * @function isFunction
  781. * @memberof OpenSeadragon
  782. * @see {@link http://www.jquery.com/ jQuery}
  783. */
  784. $.isFunction = function( obj ) {
  785. return $.type(obj) === "function";
  786. };
  787. /**
  788. * Taken from jQuery 1.6.1
  789. * @function isArray
  790. * @memberof OpenSeadragon
  791. * @see {@link http://www.jquery.com/ jQuery}
  792. */
  793. $.isArray = Array.isArray || function( obj ) {
  794. return $.type(obj) === "array";
  795. };
  796. /**
  797. * A crude way of determining if an object is a window.
  798. * Taken from jQuery 1.6.1
  799. * @function isWindow
  800. * @memberof OpenSeadragon
  801. * @see {@link http://www.jquery.com/ jQuery}
  802. */
  803. $.isWindow = function( obj ) {
  804. return obj && typeof obj === "object" && "setInterval" in obj;
  805. };
  806. /**
  807. * Taken from jQuery 1.6.1
  808. * @function type
  809. * @memberof OpenSeadragon
  810. * @see {@link http://www.jquery.com/ jQuery}
  811. */
  812. $.type = function( obj ) {
  813. return ( obj === null ) || ( obj === undefined ) ?
  814. String( obj ) :
  815. class2type[ toString.call(obj) ] || "object";
  816. };
  817. /**
  818. * Taken from jQuery 1.6.1
  819. * @function isPlainObject
  820. * @memberof OpenSeadragon
  821. * @see {@link http://www.jquery.com/ jQuery}
  822. */
  823. $.isPlainObject = function( obj ) {
  824. // Must be an Object.
  825. // Because of IE, we also have to check the presence of the constructor property.
  826. // Make sure that DOM nodes and window objects don't pass through, as well
  827. if ( !obj || OpenSeadragon.type(obj) !== "object" || obj.nodeType || $.isWindow( obj ) ) {
  828. return false;
  829. }
  830. // Not own constructor property must be Object
  831. if ( obj.constructor &&
  832. !hasOwn.call(obj, "constructor") &&
  833. !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
  834. return false;
  835. }
  836. // Own properties are enumerated firstly, so to speed up,
  837. // if last one is own, then all properties are own.
  838. var lastKey;
  839. for (var key in obj ) {
  840. lastKey = key;
  841. }
  842. return lastKey === undefined || hasOwn.call( obj, lastKey );
  843. };
  844. /**
  845. * Taken from jQuery 1.6.1
  846. * @function isEmptyObject
  847. * @memberof OpenSeadragon
  848. * @see {@link http://www.jquery.com/ jQuery}
  849. */
  850. $.isEmptyObject = function( obj ) {
  851. for ( var name in obj ) {
  852. return false;
  853. }
  854. return true;
  855. };
  856. /**
  857. * Shim around Object.freeze. Does nothing if Object.freeze is not supported.
  858. * @param {Object} obj The object to freeze.
  859. * @return {Object} obj The frozen object.
  860. */
  861. $.freezeObject = function(obj) {
  862. if (Object.freeze) {
  863. $.freezeObject = Object.freeze;
  864. } else {
  865. $.freezeObject = function(obj) {
  866. return obj;
  867. };
  868. }
  869. return $.freezeObject(obj);
  870. };
  871. /**
  872. * True if the browser supports the HTML5 canvas element
  873. * @member {Boolean} supportsCanvas
  874. * @memberof OpenSeadragon
  875. */
  876. $.supportsCanvas = (function () {
  877. var canvasElement = document.createElement( 'canvas' );
  878. return !!( $.isFunction( canvasElement.getContext ) &&
  879. canvasElement.getContext( '2d' ) );
  880. }());
  881. /**
  882. * Test whether the submitted canvas is tainted or not.
  883. * @argument {Canvas} canvas The canvas to test.
  884. * @returns {Boolean} True if the canvas is tainted.
  885. */
  886. $.isCanvasTainted = function(canvas) {
  887. var isTainted = false;
  888. try {
  889. // We test if the canvas is tainted by retrieving data from it.
  890. // An exception will be raised if the canvas is tainted.
  891. canvas.getContext('2d').getImageData(0, 0, 1, 1);
  892. } catch (e) {
  893. isTainted = true;
  894. }
  895. return isTainted;
  896. };
  897. /**
  898. * A ratio comparing the device screen's pixel density to the canvas's backing store pixel density,
  899. * clamped to a minimum of 1. Defaults to 1 if canvas isn't supported by the browser.
  900. * @member {Number} pixelDensityRatio
  901. * @memberof OpenSeadragon
  902. */
  903. $.pixelDensityRatio = (function () {
  904. if ( $.supportsCanvas ) {
  905. var context = document.createElement('canvas').getContext('2d');
  906. var devicePixelRatio = window.devicePixelRatio || 1;
  907. var backingStoreRatio = context.webkitBackingStorePixelRatio ||
  908. context.mozBackingStorePixelRatio ||
  909. context.msBackingStorePixelRatio ||
  910. context.oBackingStorePixelRatio ||
  911. context.backingStorePixelRatio || 1;
  912. return Math.max(devicePixelRatio, 1) / backingStoreRatio;
  913. } else {
  914. return 1;
  915. }
  916. }());
  917. }( OpenSeadragon ));
  918. /**
  919. * This closure defines all static methods available to the OpenSeadragon
  920. * namespace. Many, if not most, are taked directly from jQuery for use
  921. * to simplify and reduce common programming patterns. More static methods
  922. * from jQuery may eventually make their way into this though we are
  923. * attempting to avoid an explicit dependency on jQuery only because
  924. * OpenSeadragon is a broadly useful code base and would be made less broad
  925. * by requiring jQuery fully.
  926. *
  927. * Some static methods have also been refactored from the original OpenSeadragon
  928. * project.
  929. */
  930. (function( $ ){
  931. /**
  932. * Taken from jQuery 1.6.1
  933. * @function extend
  934. * @memberof OpenSeadragon
  935. * @see {@link http://www.jquery.com/ jQuery}
  936. */
  937. $.extend = function() {
  938. var options,
  939. name,
  940. src,
  941. copy,
  942. copyIsArray,
  943. clone,
  944. target = arguments[ 0 ] || {},
  945. length = arguments.length,
  946. deep = false,
  947. i = 1;
  948. // Handle a deep copy situation
  949. if ( typeof target === "boolean" ) {
  950. deep = target;
  951. target = arguments[ 1 ] || {};
  952. // skip the boolean and the target
  953. i = 2;
  954. }
  955. // Handle case when target is a string or something (possible in deep copy)
  956. if ( typeof target !== "object" && !OpenSeadragon.isFunction( target ) ) {
  957. target = {};
  958. }
  959. // extend jQuery itself if only one argument is passed
  960. if ( length === i ) {
  961. target = this;
  962. --i;
  963. }
  964. for ( ; i < length; i++ ) {
  965. // Only deal with non-null/undefined values
  966. options = arguments[ i ];
  967. if ( options !== null || options !== undefined ) {
  968. // Extend the base object
  969. for ( name in options ) {
  970. src = target[ name ];
  971. copy = options[ name ];
  972. // Prevent never-ending loop
  973. if ( target === copy ) {
  974. continue;
  975. }
  976. // Recurse if we're merging plain objects or arrays
  977. if ( deep && copy && ( OpenSeadragon.isPlainObject( copy ) || ( copyIsArray = OpenSeadragon.isArray( copy ) ) ) ) {
  978. if ( copyIsArray ) {
  979. copyIsArray = false;
  980. clone = src && OpenSeadragon.isArray( src ) ? src : [];
  981. } else {
  982. clone = src && OpenSeadragon.isPlainObject( src ) ? src : {};
  983. }
  984. // Never move original objects, clone them
  985. target[ name ] = OpenSeadragon.extend( deep, clone, copy );
  986. // Don't bring in undefined values
  987. } else if ( copy !== undefined ) {
  988. target[ name ] = copy;
  989. }
  990. }
  991. }
  992. }
  993. // Return the modified object
  994. return target;
  995. };
  996. var isIOSDevice = function () {
  997. if (typeof navigator !== 'object') {
  998. return false;
  999. }
  1000. var userAgent = navigator.userAgent;
  1001. if (typeof userAgent !== 'string') {
  1002. return false;
  1003. }
  1004. return userAgent.indexOf('iPhone') !== -1 ||
  1005. userAgent.indexOf('iPad') !== -1 ||
  1006. userAgent.indexOf('iPod') !== -1;
  1007. };
  1008. $.extend( $, /** @lends OpenSeadragon */{
  1009. /**
  1010. * The default values for the optional settings documented at {@link OpenSeadragon.Options}.
  1011. * @static
  1012. * @type {Object}
  1013. */
  1014. DEFAULT_SETTINGS: {
  1015. //DATA SOURCE DETAILS
  1016. xmlPath: null,
  1017. tileSources: null,
  1018. tileHost: null,
  1019. initialPage: 0,
  1020. crossOriginPolicy: false,
  1021. ajaxWithCredentials: false,
  1022. loadTilesWithAjax: false,
  1023. ajaxHeaders: {},
  1024. //PAN AND ZOOM SETTINGS AND CONSTRAINTS
  1025. panHorizontal: true,
  1026. panVertical: true,
  1027. constrainDuringPan: false,
  1028. wrapHorizontal: false,
  1029. wrapVertical: false,
  1030. visibilityRatio: 0.5, //-> how much of the viewer can be negative space
  1031. minPixelRatio: 0.5, //->closer to 0 draws tiles meant for a higher zoom at this zoom
  1032. defaultZoomLevel: 0,
  1033. minZoomLevel: null,
  1034. maxZoomLevel: null,
  1035. homeFillsViewer: false,
  1036. //UI RESPONSIVENESS AND FEEL
  1037. clickTimeThreshold: 300,
  1038. clickDistThreshold: 5,
  1039. dblClickTimeThreshold: 300,
  1040. dblClickDistThreshold: 20,
  1041. springStiffness: 6.5,
  1042. animationTime: 1.2,
  1043. gestureSettingsMouse: {
  1044. scrollToZoom: true,
  1045. clickToZoom: true,
  1046. dblClickToZoom: false,
  1047. pinchToZoom: false,
  1048. zoomToRefPoint: true,
  1049. flickEnabled: false,
  1050. flickMinSpeed: 120,
  1051. flickMomentum: 0.25,
  1052. pinchRotate: false
  1053. },
  1054. gestureSettingsTouch: {
  1055. scrollToZoom: false,
  1056. clickToZoom: false,
  1057. dblClickToZoom: true,
  1058. pinchToZoom: true,
  1059. zoomToRefPoint: true,
  1060. flickEnabled: true,
  1061. flickMinSpeed: 120,
  1062. flickMomentum: 0.25,
  1063. pinchRotate: false
  1064. },
  1065. gestureSettingsPen: {
  1066. scrollToZoom: false,
  1067. clickToZoom: true,
  1068. dblClickToZoom: false,
  1069. pinchToZoom: false,
  1070. zoomToRefPoint: true,
  1071. flickEnabled: false,
  1072. flickMinSpeed: 120,
  1073. flickMomentum: 0.25,
  1074. pinchRotate: false
  1075. },
  1076. gestureSettingsUnknown: {
  1077. scrollToZoom: false,
  1078. clickToZoom: false,
  1079. dblClickToZoom: true,
  1080. pinchToZoom: true,
  1081. zoomToRefPoint: true,
  1082. flickEnabled: true,
  1083. flickMinSpeed: 120,
  1084. flickMomentum: 0.25,
  1085. pinchRotate: false
  1086. },
  1087. zoomPerClick: 2,
  1088. zoomPerScroll: 1.2,
  1089. zoomPerSecond: 1.0,
  1090. blendTime: 0,
  1091. alwaysBlend: false,
  1092. autoHideControls: true,
  1093. immediateRender: false,
  1094. minZoomImageRatio: 0.9, //-> closer to 0 allows zoom out to infinity
  1095. maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels
  1096. smoothTileEdgesMinZoom: 1.1, //-> higher than maxZoomPixelRatio disables it
  1097. iOSDevice: isIOSDevice(),
  1098. pixelsPerWheelLine: 40,
  1099. pixelsPerArrowPress: 40,
  1100. autoResize: true,
  1101. preserveImageSizeOnResize: false, // requires autoResize=true
  1102. minScrollDeltaTime: 50,
  1103. rotationIncrement: 90,
  1104. //DEFAULT CONTROL SETTINGS
  1105. showSequenceControl: true, //SEQUENCE
  1106. sequenceControlAnchor: null, //SEQUENCE
  1107. preserveViewport: false, //SEQUENCE
  1108. preserveOverlays: false, //SEQUENCE
  1109. navPrevNextWrap: false, //SEQUENCE
  1110. showNavigationControl: true, //ZOOM/HOME/FULL/ROTATION
  1111. navigationControlAnchor: null, //ZOOM/HOME/FULL/ROTATION
  1112. showZoomControl: true, //ZOOM
  1113. showHomeControl: true, //HOME
  1114. showFullPageControl: true, //FULL
  1115. showRotationControl: false, //ROTATION
  1116. showFlipControl: false, //FLIP
  1117. controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE
  1118. controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE
  1119. mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY
  1120. //VIEWPORT NAVIGATOR SETTINGS
  1121. showNavigator: false,
  1122. navigatorId: null,
  1123. navigatorPosition: null,
  1124. navigatorSizeRatio: 0.2,
  1125. navigatorMaintainSizeRatio: false,
  1126. navigatorTop: null,
  1127. navigatorLeft: null,
  1128. navigatorHeight: null,
  1129. navigatorWidth: null,
  1130. navigatorAutoResize: true,
  1131. navigatorAutoFade: true,
  1132. navigatorRotate: true,
  1133. navigatorBackground: '#000',
  1134. navigatorOpacity: 0.8,
  1135. navigatorBorderColor: '#555',
  1136. navigatorDisplayRegionColor: '#900',
  1137. // INITIAL ROTATION
  1138. degrees: 0,
  1139. // INITIAL FLIP STATE
  1140. flipped: false,
  1141. // APPEARANCE
  1142. opacity: 1,
  1143. preload: false,
  1144. compositeOperation: null,
  1145. imageSmoothingEnabled: true,
  1146. placeholderFillStyle: null,
  1147. //REFERENCE STRIP SETTINGS
  1148. showReferenceStrip: false,
  1149. referenceStripScroll: 'horizontal',
  1150. referenceStripElement: null,
  1151. referenceStripHeight: null,
  1152. referenceStripWidth: null,
  1153. referenceStripPosition: 'BOTTOM_LEFT',
  1154. referenceStripSizeRatio: 0.2,
  1155. //COLLECTION VISUALIZATION SETTINGS
  1156. collectionRows: 3, //or columns depending on layout
  1157. collectionColumns: 0, //columns in horizontal layout, rows in vertical layout
  1158. collectionLayout: 'horizontal', //vertical
  1159. collectionMode: false,
  1160. collectionTileSize: 800,
  1161. collectionTileMargin: 80,
  1162. //PERFORMANCE SETTINGS
  1163. imageLoaderLimit: 0,
  1164. maxImageCacheCount: 200,
  1165. timeout: 30000,
  1166. useCanvas: true, // Use canvas element for drawing if available
  1167. //INTERFACE RESOURCE SETTINGS
  1168. prefixUrl: "/images/",
  1169. navImages: {
  1170. zoomIn: {
  1171. REST: 'zoomin_rest.png',
  1172. GROUP: 'zoomin_grouphover.png',
  1173. HOVER: 'zoomin_hover.png',
  1174. DOWN: 'zoomin_pressed.png'
  1175. },
  1176. zoomOut: {
  1177. REST: 'zoomout_rest.png',
  1178. GROUP: 'zoomout_grouphover.png',
  1179. HOVER: 'zoomout_hover.png',
  1180. DOWN: 'zoomout_pressed.png'
  1181. },
  1182. home: {
  1183. REST: 'home_rest.png',
  1184. GROUP: 'home_grouphover.png',
  1185. HOVER: 'home_hover.png',
  1186. DOWN: 'home_pressed.png'
  1187. },
  1188. fullpage: {
  1189. REST: 'fullpage_rest.png',
  1190. GROUP: 'fullpage_grouphover.png',
  1191. HOVER: 'fullpage_hover.png',
  1192. DOWN: 'fullpage_pressed.png'
  1193. },
  1194. rotateleft: {
  1195. REST: 'rotateleft_rest.png',
  1196. GROUP: 'rotateleft_grouphover.png',
  1197. HOVER: 'rotateleft_hover.png',
  1198. DOWN: 'rotateleft_pressed.png'
  1199. },
  1200. rotateright: {
  1201. REST: 'rotateright_rest.png',
  1202. GROUP: 'rotateright_grouphover.png',
  1203. HOVER: 'rotateright_hover.png',
  1204. DOWN: 'rotateright_pressed.png'
  1205. },
  1206. flip: { // Flip icon designed by Yaroslav Samoylov from the Noun Project and modified by Nelson Campos ncampos@criteriamarathon.com, https://thenounproject.com/term/flip/136289/
  1207. REST: 'flip_rest.png',
  1208. GROUP: 'flip_grouphover.png',
  1209. HOVER: 'flip_hover.png',
  1210. DOWN: 'flip_pressed.png'
  1211. },
  1212. previous: {
  1213. REST: 'previous_rest.png',
  1214. GROUP: 'previous_grouphover.png',
  1215. HOVER: 'previous_hover.png',
  1216. DOWN: 'previous_pressed.png'
  1217. },
  1218. next: {
  1219. REST: 'next_rest.png',
  1220. GROUP: 'next_grouphover.png',
  1221. HOVER: 'next_hover.png',
  1222. DOWN: 'next_pressed.png'
  1223. }
  1224. },
  1225. //DEVELOPER SETTINGS
  1226. debugMode: false,
  1227. debugGridColor: ['#437AB2', '#1B9E77', '#D95F02', '#7570B3', '#E7298A', '#66A61E', '#E6AB02', '#A6761D', '#666666']
  1228. },
  1229. /**
  1230. * TODO: get rid of this. I can't see how it's required at all. Looks
  1231. * like an early legacy code artifact.
  1232. * @static
  1233. * @ignore
  1234. */
  1235. SIGNAL: "----seadragon----",
  1236. /**
  1237. * Returns a function which invokes the method as if it were a method belonging to the object.
  1238. * @function
  1239. * @param {Object} object
  1240. * @param {Function} method
  1241. * @returns {Function}
  1242. */
  1243. delegate: function( object, method ) {
  1244. return function(){
  1245. var args = arguments;
  1246. if ( args === undefined ){
  1247. args = [];
  1248. }
  1249. return method.apply( object, args );
  1250. };
  1251. },
  1252. /**
  1253. * An enumeration of Browser vendors.
  1254. * @static
  1255. * @type {Object}
  1256. * @property {Number} UNKNOWN
  1257. * @property {Number} IE
  1258. * @property {Number} FIREFOX
  1259. * @property {Number} SAFARI
  1260. * @property {Number} CHROME
  1261. * @property {Number} OPERA
  1262. */
  1263. BROWSERS: {
  1264. UNKNOWN: 0,
  1265. IE: 1,
  1266. FIREFOX: 2,
  1267. SAFARI: 3,
  1268. CHROME: 4,
  1269. OPERA: 5
  1270. },
  1271. /**
  1272. * Returns a DOM Element for the given id or element.
  1273. * @function
  1274. * @param {String|Element} element Accepts an id or element.
  1275. * @returns {Element} The element with the given id, null, or the element itself.
  1276. */
  1277. getElement: function( element ) {
  1278. if ( typeof ( element ) == "string" ) {
  1279. element = document.getElementById( element );
  1280. }
  1281. return element;
  1282. },
  1283. /**
  1284. * Determines the position of the upper-left corner of the element.
  1285. * @function
  1286. * @param {Element|String} element - the element we want the position for.
  1287. * @returns {OpenSeadragon.Point} - the position of the upper left corner of the element.
  1288. */
  1289. getElementPosition: function( element ) {
  1290. var result = new $.Point(),
  1291. isFixed,
  1292. offsetParent;
  1293. element = $.getElement( element );
  1294. isFixed = $.getElementStyle( element ).position == "fixed";
  1295. offsetParent = getOffsetParent( element, isFixed );
  1296. while ( offsetParent ) {
  1297. result.x += element.offsetLeft;
  1298. result.y += element.offsetTop;
  1299. if ( isFixed ) {
  1300. result = result.plus( $.getPageScroll() );
  1301. }
  1302. element = offsetParent;
  1303. isFixed = $.getElementStyle( element ).position == "fixed";
  1304. offsetParent = getOffsetParent( element, isFixed );
  1305. }
  1306. return result;
  1307. },
  1308. /**
  1309. * Determines the position of the upper-left corner of the element adjusted for current page and/or element scroll.
  1310. * @function
  1311. * @param {Element|String} element - the element we want the position for.
  1312. * @returns {OpenSeadragon.Point} - the position of the upper left corner of the element adjusted for current page and/or element scroll.
  1313. */
  1314. getElementOffset: function( element ) {
  1315. element = $.getElement( element );
  1316. var doc = element && element.ownerDocument,
  1317. docElement,
  1318. win,
  1319. boundingRect = { top: 0, left: 0 };
  1320. if ( !doc ) {
  1321. return new $.Point();
  1322. }
  1323. docElement = doc.documentElement;
  1324. if ( typeof element.getBoundingClientRect !== typeof undefined ) {
  1325. boundingRect = element.getBoundingClientRect();
  1326. }
  1327. win = ( doc == doc.window ) ?
  1328. doc :
  1329. ( doc.nodeType === 9 ) ?
  1330. doc.defaultView || doc.parentWindow :
  1331. false;
  1332. return new $.Point(
  1333. boundingRect.left + ( win.pageXOffset || docElement.scrollLeft ) - ( docElement.clientLeft || 0 ),
  1334. boundingRect.top + ( win.pageYOffset || docElement.scrollTop ) - ( docElement.clientTop || 0 )
  1335. );
  1336. },
  1337. /**
  1338. * Determines the height and width of the given element.
  1339. * @function
  1340. * @param {Element|String} element
  1341. * @returns {OpenSeadragon.Point}
  1342. */
  1343. getElementSize: function( element ) {
  1344. element = $.getElement( element );
  1345. return new $.Point(
  1346. element.clientWidth,
  1347. element.clientHeight
  1348. );
  1349. },
  1350. /**
  1351. * Returns the CSSStyle object for the given element.
  1352. * @function
  1353. * @param {Element|String} element
  1354. * @returns {CSSStyle}
  1355. */
  1356. getElementStyle:
  1357. document.documentElement.currentStyle ?
  1358. function( element ) {
  1359. element = $.getElement( element );
  1360. return element.currentStyle;
  1361. } :
  1362. function( element ) {
  1363. element = $.getElement( element );
  1364. return window.getComputedStyle( element, "" );
  1365. },
  1366. /**
  1367. * Returns the property with the correct vendor prefix appended.
  1368. * @param {String} property the property name
  1369. * @returns {String} the property with the correct prefix or null if not
  1370. * supported.
  1371. */
  1372. getCssPropertyWithVendorPrefix: function(property) {
  1373. var memo = {};
  1374. $.getCssPropertyWithVendorPrefix = function(property) {
  1375. if (memo[property] !== undefined) {
  1376. return memo[property];
  1377. }
  1378. var style = document.createElement('div').style;
  1379. var result = null;
  1380. if (style[property] !== undefined) {
  1381. result = property;
  1382. } else {
  1383. var prefixes = ['Webkit', 'Moz', 'MS', 'O',
  1384. 'webkit', 'moz', 'ms', 'o'];
  1385. var suffix = $.capitalizeFirstLetter(property);
  1386. for (var i = 0; i < prefixes.length; i++) {
  1387. var prop = prefixes[i] + suffix;
  1388. if (style[prop] !== undefined) {
  1389. result = prop;
  1390. break;
  1391. }
  1392. }
  1393. }
  1394. memo[property] = result;
  1395. return result;
  1396. };
  1397. return $.getCssPropertyWithVendorPrefix(property);
  1398. },
  1399. /**
  1400. * Capitalizes the first letter of a string
  1401. * @param {String} string
  1402. * @returns {String} The string with the first letter capitalized
  1403. */
  1404. capitalizeFirstLetter: function(string) {
  1405. return string.charAt(0).toUpperCase() + string.slice(1);
  1406. },
  1407. /**
  1408. * Compute the modulo of a number but makes sure to always return
  1409. * a positive value.
  1410. * @param {Number} number the number to computes the modulo of
  1411. * @param {Number} modulo the modulo
  1412. * @returns {Number} the result of the modulo of number
  1413. */
  1414. positiveModulo: function(number, modulo) {
  1415. var result = number % modulo;
  1416. if (result < 0) {
  1417. result += modulo;
  1418. }
  1419. return result;
  1420. },
  1421. /**
  1422. * Determines if a point is within the bounding rectangle of the given element (hit-test).
  1423. * @function
  1424. * @param {Element|String} element
  1425. * @param {OpenSeadragon.Point} point
  1426. * @returns {Boolean}
  1427. */
  1428. pointInElement: function( element, point ) {
  1429. element = $.getElement( element );
  1430. var offset = $.getElementOffset( element ),
  1431. size = $.getElementSize( element );
  1432. return point.x >= offset.x && point.x < offset.x + size.x && point.y < offset.y + size.y && point.y >= offset.y;
  1433. },
  1434. /**
  1435. * Gets the latest event, really only useful internally since its
  1436. * specific to IE behavior.
  1437. * @function
  1438. * @param {Event} [event]
  1439. * @returns {Event}
  1440. * @deprecated For internal use only
  1441. * @private
  1442. */
  1443. getEvent: function( event ) {
  1444. if( event ){
  1445. $.getEvent = function( event ) {
  1446. return event;
  1447. };
  1448. } else {
  1449. $.getEvent = function() {
  1450. return window.event;
  1451. };
  1452. }
  1453. return $.getEvent( event );
  1454. },
  1455. /**
  1456. * Gets the position of the mouse on the screen for a given event.
  1457. * @function
  1458. * @param {Event} [event]
  1459. * @returns {OpenSeadragon.Point}
  1460. */
  1461. getMousePosition: function( event ) {
  1462. if ( typeof ( event.pageX ) == "number" ) {
  1463. $.getMousePosition = function( event ){
  1464. var result = new $.Point();
  1465. event = $.getEvent( event );
  1466. result.x = event.pageX;
  1467. result.y = event.pageY;
  1468. return result;
  1469. };
  1470. } else if ( typeof ( event.clientX ) == "number" ) {
  1471. $.getMousePosition = function( event ){
  1472. var result = new $.Point();
  1473. event = $.getEvent( event );
  1474. result.x =
  1475. event.clientX +
  1476. document.body.scrollLeft +
  1477. document.documentElement.scrollLeft;
  1478. result.y =
  1479. event.clientY +
  1480. document.body.scrollTop +
  1481. document.documentElement.scrollTop;
  1482. return result;
  1483. };
  1484. } else {
  1485. throw new Error(
  1486. "Unknown event mouse position, no known technique."
  1487. );
  1488. }
  1489. return $.getMousePosition( event );
  1490. },
  1491. /**
  1492. * Determines the page's current scroll position.
  1493. * @function
  1494. * @returns {OpenSeadragon.Point}
  1495. */
  1496. getPageScroll: function() {
  1497. var docElement = document.documentElement || {},
  1498. body = document.body || {};
  1499. if ( typeof ( window.pageXOffset ) == "number" ) {
  1500. $.getPageScroll = function(){
  1501. return new $.Point(
  1502. window.pageXOffset,
  1503. window.pageYOffset
  1504. );
  1505. };
  1506. } else if ( body.scrollLeft || body.scrollTop ) {
  1507. $.getPageScroll = function(){
  1508. return new $.Point(
  1509. document.body.scrollLeft,
  1510. document.body.scrollTop
  1511. );
  1512. };
  1513. } else if ( docElement.scrollLeft || docElement.scrollTop ) {
  1514. $.getPageScroll = function(){
  1515. return new $.Point(
  1516. document.documentElement.scrollLeft,
  1517. document.documentElement.scrollTop
  1518. );
  1519. };
  1520. } else {
  1521. // We can't reassign the function yet, as there was no scroll.
  1522. return new $.Point(0, 0);
  1523. }
  1524. return $.getPageScroll();
  1525. },
  1526. /**
  1527. * Set the page scroll position.
  1528. * @function
  1529. * @returns {OpenSeadragon.Point}
  1530. */
  1531. setPageScroll: function( scroll ) {
  1532. if ( typeof ( window.scrollTo ) !== "undefined" ) {
  1533. $.setPageScroll = function( scroll ) {
  1534. window.scrollTo( scroll.x, scroll.y );
  1535. };
  1536. } else {
  1537. var originalScroll = $.getPageScroll();
  1538. if ( originalScroll.x === scroll.x &&
  1539. originalScroll.y === scroll.y ) {
  1540. // We are already correctly positioned and there
  1541. // is no way to detect the correct method.
  1542. return;
  1543. }
  1544. document.body.scrollLeft = scroll.x;
  1545. document.body.scrollTop = scroll.y;
  1546. var currentScroll = $.getPageScroll();
  1547. if ( currentScroll.x !== originalScroll.x &&
  1548. currentScroll.y !== originalScroll.y ) {
  1549. $.setPageScroll = function( scroll ) {
  1550. document.body.scrollLeft = scroll.x;
  1551. document.body.scrollTop = scroll.y;
  1552. };
  1553. return;
  1554. }
  1555. document.documentElement.scrollLeft = scroll.x;
  1556. document.documentElement.scrollTop = scroll.y;
  1557. currentScroll = $.getPageScroll();
  1558. if ( currentScroll.x !== originalScroll.x &&
  1559. currentScroll.y !== originalScroll.y ) {
  1560. $.setPageScroll = function( scroll ) {
  1561. document.documentElement.scrollLeft = scroll.x;
  1562. document.documentElement.scrollTop = scroll.y;
  1563. };
  1564. return;
  1565. }
  1566. // We can't find anything working, so we do nothing.
  1567. $.setPageScroll = function( scroll ) {
  1568. };
  1569. }
  1570. return $.setPageScroll( scroll );
  1571. },
  1572. /**
  1573. * Determines the size of the browsers window.
  1574. * @function
  1575. * @returns {OpenSeadragon.Point}
  1576. */
  1577. getWindowSize: function() {
  1578. var docElement = document.documentElement || {},
  1579. body = document.body || {};
  1580. if ( typeof ( window.innerWidth ) == 'number' ) {
  1581. $.getWindowSize = function(){
  1582. return new $.Point(
  1583. window.innerWidth,
  1584. window.innerHeight
  1585. );
  1586. };
  1587. } else if ( docElement.clientWidth || docElement.clientHeight ) {
  1588. $.getWindowSize = function(){
  1589. return new $.Point(
  1590. document.documentElement.clientWidth,
  1591. document.documentElement.clientHeight
  1592. );
  1593. };
  1594. } else if ( body.clientWidth || body.clientHeight ) {
  1595. $.getWindowSize = function(){
  1596. return new $.Point(
  1597. document.body.clientWidth,
  1598. document.body.clientHeight
  1599. );
  1600. };
  1601. } else {
  1602. throw new Error("Unknown window size, no known technique.");
  1603. }
  1604. return $.getWindowSize();
  1605. },
  1606. /**
  1607. * Wraps the given element in a nest of divs so that the element can
  1608. * be easily centered using CSS tables
  1609. * @function
  1610. * @param {Element|String} element
  1611. * @returns {Element} outermost wrapper element
  1612. */
  1613. makeCenteredNode: function( element ) {
  1614. // Convert a possible ID to an actual HTMLElement
  1615. element = $.getElement( element );
  1616. /*
  1617. CSS tables require you to have a display:table/row/cell hierarchy so we need to create
  1618. three nested wrapper divs:
  1619. */
  1620. var wrappers = [
  1621. $.makeNeutralElement( 'div' ),
  1622. $.makeNeutralElement( 'div' ),
  1623. $.makeNeutralElement( 'div' )
  1624. ];
  1625. // It feels like we should be able to pass style dicts to makeNeutralElement:
  1626. $.extend(wrappers[0].style, {
  1627. display: "table",
  1628. height: "100%",
  1629. width: "100%"
  1630. });
  1631. $.extend(wrappers[1].style, {
  1632. display: "table-row"
  1633. });
  1634. $.extend(wrappers[2].style, {
  1635. display: "table-cell",
  1636. verticalAlign: "middle",
  1637. textAlign: "center"
  1638. });
  1639. wrappers[0].appendChild(wrappers[1]);
  1640. wrappers[1].appendChild(wrappers[2]);
  1641. wrappers[2].appendChild(element);
  1642. return wrappers[0];
  1643. },
  1644. /**
  1645. * Creates an easily positionable element of the given type that therefor
  1646. * serves as an excellent container element.
  1647. * @function
  1648. * @param {String} tagName
  1649. * @returns {Element}
  1650. */
  1651. makeNeutralElement: function( tagName ) {
  1652. var element = document.createElement( tagName ),
  1653. style = element.style;
  1654. style.background = "transparent none";
  1655. style.border = "none";
  1656. style.margin = "0px";
  1657. style.padding = "0px";
  1658. style.position = "static";
  1659. return element;
  1660. },
  1661. /**
  1662. * Returns the current milliseconds, using Date.now() if available
  1663. * @function
  1664. */
  1665. now: function( ) {
  1666. if (Date.now) {
  1667. $.now = Date.now;
  1668. } else {
  1669. $.now = function() {
  1670. return new Date().getTime();
  1671. };
  1672. }
  1673. return $.now();
  1674. },
  1675. /**
  1676. * Ensures an image is loaded correctly to support alpha transparency.
  1677. * Generally only IE has issues doing this correctly for formats like
  1678. * png.
  1679. * @function
  1680. * @param {String} src
  1681. * @returns {Element}
  1682. */
  1683. makeTransparentImage: function( src ) {
  1684. $.makeTransparentImage = function( src ){
  1685. var img = $.makeNeutralElement( "img" );
  1686. img.src = src;
  1687. return img;
  1688. };
  1689. if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 7 ) {
  1690. $.makeTransparentImage = function( src ){
  1691. var img = $.makeNeutralElement( "img" ),
  1692. element = null;
  1693. element = $.makeNeutralElement("span");
  1694. element.style.display = "inline-block";
  1695. img.onload = function() {
  1696. element.style.width = element.style.width || img.width + "px";
  1697. element.style.height = element.style.height || img.height + "px";
  1698. img.onload = null;
  1699. img = null; // to prevent memory leaks in IE
  1700. };
  1701. img.src = src;
  1702. element.style.filter =
  1703. "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
  1704. src +
  1705. "', sizingMethod='scale')";
  1706. return element;
  1707. };
  1708. }
  1709. return $.makeTransparentImage( src );
  1710. },
  1711. /**
  1712. * Sets the opacity of the specified element.
  1713. * @function
  1714. * @param {Element|String} element
  1715. * @param {Number} opacity
  1716. * @param {Boolean} [usesAlpha]
  1717. */
  1718. setElementOpacity: function( element, opacity, usesAlpha ) {
  1719. var ieOpacity,
  1720. ieFilter;
  1721. element = $.getElement( element );
  1722. if ( usesAlpha && !$.Browser.alpha ) {
  1723. opacity = Math.round( opacity );
  1724. }
  1725. if ( $.Browser.opacity ) {
  1726. element.style.opacity = opacity < 1 ? opacity : "";
  1727. } else {
  1728. if ( opacity < 1 ) {
  1729. ieOpacity = Math.round( 100 * opacity );
  1730. ieFilter = "alpha(opacity=" + ieOpacity + ")";
  1731. element.style.filter = ieFilter;
  1732. } else {
  1733. element.style.filter = "";
  1734. }
  1735. }
  1736. },
  1737. /**
  1738. * Sets the specified element's touch-action style attribute to 'none'.
  1739. * @function
  1740. * @param {Element|String} element
  1741. */
  1742. setElementTouchActionNone: function( element ) {
  1743. element = $.getElement( element );
  1744. if ( typeof element.style.touchAction !== 'undefined' ) {
  1745. element.style.touchAction = 'none';
  1746. } else if ( typeof element.style.msTouchAction !== 'undefined' ) {
  1747. element.style.msTouchAction = 'none';
  1748. }
  1749. },
  1750. /**
  1751. * Add the specified CSS class to the element if not present.
  1752. * @function
  1753. * @param {Element|String} element
  1754. * @param {String} className
  1755. */
  1756. addClass: function( element, className ) {
  1757. element = $.getElement( element );
  1758. if (!element.className) {
  1759. element.className = className;
  1760. } else if ( ( ' ' + element.className + ' ' ).
  1761. indexOf( ' ' + className + ' ' ) === -1 ) {
  1762. element.className += ' ' + className;
  1763. }
  1764. },
  1765. /**
  1766. * Find the first index at which an element is found in an array or -1
  1767. * if not present.
  1768. *
  1769. * Code taken and adapted from
  1770. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Compatibility
  1771. *
  1772. * @function
  1773. * @param {Array} array The array from which to find the element
  1774. * @param {Object} searchElement The element to find
  1775. * @param {Number} [fromIndex=0] Index to start research.
  1776. * @returns {Number} The index of the element in the array.
  1777. */
  1778. indexOf: function( array, searchElement, fromIndex ) {
  1779. if ( Array.prototype.indexOf ) {
  1780. this.indexOf = function( array, searchElement, fromIndex ) {
  1781. return array.indexOf( searchElement, fromIndex );
  1782. };
  1783. } else {
  1784. this.indexOf = function( array, searchElement, fromIndex ) {
  1785. var i,
  1786. pivot = ( fromIndex ) ? fromIndex : 0,
  1787. length;
  1788. if ( !array ) {
  1789. throw new TypeError( );
  1790. }
  1791. length = array.length;
  1792. if ( length === 0 || pivot >= length ) {
  1793. return -1;
  1794. }
  1795. if ( pivot < 0 ) {
  1796. pivot = length - Math.abs( pivot );
  1797. }
  1798. for ( i = pivot; i < length; i++ ) {
  1799. if ( array[i] === searchElement ) {
  1800. return i;
  1801. }
  1802. }
  1803. return -1;
  1804. };
  1805. }
  1806. return this.indexOf( array, searchElement, fromIndex );
  1807. },
  1808. /**
  1809. * Remove the specified CSS class from the element.
  1810. * @function
  1811. * @param {Element|String} element
  1812. * @param {String} className
  1813. */
  1814. removeClass: function( element, className ) {
  1815. var oldClasses,
  1816. newClasses = [],
  1817. i;
  1818. element = $.getElement( element );
  1819. oldClasses = element.className.split( /\s+/ );
  1820. for ( i = 0; i < oldClasses.length; i++ ) {
  1821. if ( oldClasses[ i ] && oldClasses[ i ] !== className ) {
  1822. newClasses.push( oldClasses[ i ] );
  1823. }
  1824. }
  1825. element.className = newClasses.join(' ');
  1826. },
  1827. /**
  1828. * Adds an event listener for the given element, eventName and handler.
  1829. * @function
  1830. * @param {Element|String} element
  1831. * @param {String} eventName
  1832. * @param {Function} handler
  1833. * @param {Boolean} [useCapture]
  1834. */
  1835. addEvent: (function () {
  1836. if ( window.addEventListener ) {
  1837. return function ( element, eventName, handler, useCapture ) {
  1838. element = $.getElement( element );
  1839. element.addEventListener( eventName, handler, useCapture );
  1840. };
  1841. } else if ( window.attachEvent ) {
  1842. return function ( element, eventName, handler, useCapture ) {
  1843. element = $.getElement( element );
  1844. element.attachEvent( 'on' + eventName, handler );
  1845. };
  1846. } else {
  1847. throw new Error( "No known event model." );
  1848. }
  1849. }()),
  1850. /**
  1851. * Remove a given event listener for the given element, event type and
  1852. * handler.
  1853. * @function
  1854. * @param {Element|String} element
  1855. * @param {String} eventName
  1856. * @param {Function} handler
  1857. * @param {Boolean} [useCapture]
  1858. */
  1859. removeEvent: (function () {
  1860. if ( window.removeEventListener ) {
  1861. return function ( element, eventName, handler, useCapture ) {
  1862. element = $.getElement( element );
  1863. element.removeEventListener( eventName, handler, useCapture );
  1864. };
  1865. } else if ( window.detachEvent ) {
  1866. return function( element, eventName, handler, useCapture ) {
  1867. element = $.getElement( element );
  1868. element.detachEvent( 'on' + eventName, handler );
  1869. };
  1870. } else {
  1871. throw new Error( "No known event model." );
  1872. }
  1873. }()),
  1874. /**
  1875. * Cancels the default browser behavior had the event propagated all
  1876. * the way up the DOM to the window object.
  1877. * @function
  1878. * @param {Event} [event]
  1879. */
  1880. cancelEvent: function( event ) {
  1881. event = $.getEvent( event );
  1882. if ( event.preventDefault ) {
  1883. $.cancelEvent = function( event ){
  1884. // W3C for preventing default
  1885. event.preventDefault();
  1886. };
  1887. } else {
  1888. $.cancelEvent = function( event ){
  1889. event = $.getEvent( event );
  1890. // legacy for preventing default
  1891. event.cancel = true;
  1892. // IE for preventing default
  1893. event.returnValue = false;
  1894. };
  1895. }
  1896. $.cancelEvent( event );
  1897. },
  1898. /**
  1899. * Stops the propagation of the event up the DOM.
  1900. * @function
  1901. * @param {Event} [event]
  1902. */
  1903. stopEvent: function( event ) {
  1904. event = $.getEvent( event );
  1905. if ( event.stopPropagation ) {
  1906. // W3C for stopping propagation
  1907. $.stopEvent = function( event ){
  1908. event.stopPropagation();
  1909. };
  1910. } else {
  1911. // IE for stopping propagation
  1912. $.stopEvent = function( event ){
  1913. event = $.getEvent( event );
  1914. event.cancelBubble = true;
  1915. };
  1916. }
  1917. $.stopEvent( event );
  1918. },
  1919. /**
  1920. * Similar to OpenSeadragon.delegate, but it does not immediately call
  1921. * the method on the object, returning a function which can be called
  1922. * repeatedly to delegate the method. It also allows additional arguments
  1923. * to be passed during construction which will be added during each
  1924. * invocation, and each invocation can add additional arguments as well.
  1925. *
  1926. * @function
  1927. * @param {Object} object
  1928. * @param {Function} method
  1929. * @param [args] any additional arguments are passed as arguments to the
  1930. * created callback
  1931. * @returns {Function}
  1932. */
  1933. createCallback: function( object, method ) {
  1934. //TODO: This pattern is painful to use and debug. It's much cleaner
  1935. // to use pinning plus anonymous functions. Get rid of this
  1936. // pattern!
  1937. var initialArgs = [],
  1938. i;
  1939. for ( i = 2; i < arguments.length; i++ ) {
  1940. initialArgs.push( arguments[ i ] );
  1941. }
  1942. return function() {
  1943. var args = initialArgs.concat( [] ),
  1944. i;
  1945. for ( i = 0; i < arguments.length; i++ ) {
  1946. args.push( arguments[ i ] );
  1947. }
  1948. return method.apply( object, args );
  1949. };
  1950. },
  1951. /**
  1952. * Retrieves the value of a url parameter from the window.location string.
  1953. * @function
  1954. * @param {String} key
  1955. * @returns {String} The value of the url parameter or null if no param matches.
  1956. */
  1957. getUrlParameter: function( key ) {
  1958. // eslint-disable-next-line no-use-before-define
  1959. var value = URLPARAMS[ key ];
  1960. return value ? value : null;
  1961. },
  1962. /**
  1963. * Retrieves the protocol used by the url. The url can either be absolute
  1964. * or relative.
  1965. * @function
  1966. * @private
  1967. * @param {String} url The url to retrieve the protocol from.
  1968. * @return {String} The protocol (http:, https:, file:, ftp: ...)
  1969. */
  1970. getUrlProtocol: function( url ) {
  1971. var match = url.match(/^([a-z]+:)\/\//i);
  1972. if ( match === null ) {
  1973. // Relative URL, retrive the protocol from window.location
  1974. return window.location.protocol;
  1975. }
  1976. return match[1].toLowerCase();
  1977. },
  1978. /**
  1979. * Create an XHR object
  1980. * @private
  1981. * @param {type} [local] If set to true, the XHR will be file: protocol
  1982. * compatible if possible (but may raise a warning in the browser).
  1983. * @returns {XMLHttpRequest}
  1984. */
  1985. createAjaxRequest: function( local ) {
  1986. // IE11 does not support window.ActiveXObject so we just try to
  1987. // create one to see if it is supported.
  1988. // See: http://msdn.microsoft.com/en-us/library/ie/dn423948%28v=vs.85%29.aspx
  1989. var supportActiveX;
  1990. try {
  1991. /* global ActiveXObject:true */
  1992. supportActiveX = !!new ActiveXObject( "Microsoft.XMLHTTP" );
  1993. } catch( e ) {
  1994. supportActiveX = false;
  1995. }
  1996. if ( supportActiveX ) {
  1997. if ( window.XMLHttpRequest ) {
  1998. $.createAjaxRequest = function( local ) {
  1999. if ( local ) {
  2000. return new ActiveXObject( "Microsoft.XMLHTTP" );
  2001. }
  2002. return new XMLHttpRequest();
  2003. };
  2004. } else {
  2005. $.createAjaxRequest = function() {
  2006. return new ActiveXObject( "Microsoft.XMLHTTP" );
  2007. };
  2008. }
  2009. } else if ( window.XMLHttpRequest ) {
  2010. $.createAjaxRequest = function() {
  2011. return new XMLHttpRequest();
  2012. };
  2013. } else {
  2014. throw new Error( "Browser doesn't support XMLHttpRequest." );
  2015. }
  2016. return $.createAjaxRequest( local );
  2017. },
  2018. /**
  2019. * Makes an AJAX request.
  2020. * @param {Object} options
  2021. * @param {String} options.url - the url to request
  2022. * @param {Function} options.success - a function to call on a successful response
  2023. * @param {Function} options.error - a function to call on when an error occurs
  2024. * @param {Object} options.headers - headers to add to the AJAX request
  2025. * @param {String} options.responseType - the response type of the the AJAX request
  2026. * @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials
  2027. * @throws {Error}
  2028. * @returns {XMLHttpRequest}
  2029. */
  2030. makeAjaxRequest: function( url, onSuccess, onError ) {
  2031. var withCredentials;
  2032. var headers;
  2033. var responseType;
  2034. // Note that our preferred API is that you pass in a single object; the named
  2035. // arguments are for legacy support.
  2036. if( $.isPlainObject( url ) ){
  2037. onSuccess = url.success;
  2038. onError = url.error;
  2039. withCredentials = url.withCredentials;
  2040. headers = url.headers;
  2041. responseType = url.responseType || null;
  2042. url = url.url;
  2043. }
  2044. var protocol = $.getUrlProtocol( url );
  2045. var request = $.createAjaxRequest( protocol === "file:" );
  2046. if ( !$.isFunction( onSuccess ) ) {
  2047. throw new Error( "makeAjaxRequest requires a success callback" );
  2048. }
  2049. request.onreadystatechange = function() {
  2050. // 4 = DONE (https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Properties)
  2051. if ( request.readyState == 4 ) {
  2052. request.onreadystatechange = function(){};
  2053. // With protocols other than http/https, a successful request status is in
  2054. // the 200's on Firefox and 0 on other browsers
  2055. if ( (request.status >= 200 && request.status < 300) ||
  2056. ( request.status === 0 &&
  2057. protocol !== "http:" &&
  2058. protocol !== "https:" )) {
  2059. onSuccess( request );
  2060. } else {
  2061. $.console.log( "AJAX request returned %d: %s", request.status, url );
  2062. if ( $.isFunction( onError ) ) {
  2063. onError( request );
  2064. }
  2065. }
  2066. }
  2067. };
  2068. try {
  2069. request.open( "GET", url, true );
  2070. if (responseType) {
  2071. request.responseType = responseType;
  2072. }
  2073. if (headers) {
  2074. for (var headerName in headers) {
  2075. if (headers.hasOwnProperty(headerName) && headers[headerName]) {
  2076. request.setRequestHeader(headerName, headers[headerName]);
  2077. }
  2078. }
  2079. }
  2080. if (withCredentials) {
  2081. request.withCredentials = true;
  2082. }
  2083. request.send(null);
  2084. } catch (e) {
  2085. var msg = e.message;
  2086. /*
  2087. IE < 10 does not support CORS and an XHR request to a different origin will fail as soon
  2088. as send() is called. This is particularly easy to miss during development and appear in
  2089. production if you use a CDN or domain sharding and the security policy is likely to break
  2090. exception handlers since any attempt to access a property of the request object will
  2091. raise an access denied TypeError inside the catch block.
  2092. To be friendlier, we'll check for this specific error and add a documentation pointer
  2093. to point developers in the right direction. We test the exception number because IE's
  2094. error messages are localized.
  2095. */
  2096. var oldIE = $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 10;
  2097. if ( oldIE && typeof ( e.number ) != "undefined" && e.number == -2147024891 ) {
  2098. msg += "\nSee http://msdn.microsoft.com/en-us/library/ms537505(v=vs.85).aspx#xdomain";
  2099. }
  2100. $.console.log( "%s while making AJAX request: %s", e.name, msg );
  2101. request.onreadystatechange = function(){};
  2102. if (window.XDomainRequest) { // IE9 or IE8 might as well try to use XDomainRequest
  2103. var xdr = new XDomainRequest();
  2104. if (xdr) {
  2105. xdr.onload = function (e) {
  2106. if ( $.isFunction( onSuccess ) ) {
  2107. onSuccess({ // Faking an xhr object
  2108. responseText: xdr.responseText,
  2109. status: 200, // XDomainRequest doesn't support status codes, so we just fake one! :/
  2110. statusText: 'OK'
  2111. });
  2112. }
  2113. };
  2114. xdr.onerror = function (e) {
  2115. if ($.isFunction(onError)) {
  2116. onError({ // Faking an xhr object
  2117. responseText: xdr.responseText,
  2118. status: 444, // 444 No Response
  2119. statusText: 'An error happened. Due to an XDomainRequest deficiency we can not extract any information about this error. Upgrade your browser.'
  2120. });
  2121. }
  2122. };
  2123. try {
  2124. xdr.open('GET', url);
  2125. xdr.send();
  2126. } catch (e2) {
  2127. if ( $.isFunction( onError ) ) {
  2128. onError( request, e );
  2129. }
  2130. }
  2131. }
  2132. } else {
  2133. if ( $.isFunction( onError ) ) {
  2134. onError( request, e );
  2135. }
  2136. }
  2137. }
  2138. return request;
  2139. },
  2140. /**
  2141. * Taken from jQuery 1.6.1
  2142. * @function
  2143. * @param {Object} options
  2144. * @param {String} options.url
  2145. * @param {Function} options.callback
  2146. * @param {String} [options.param='callback'] The name of the url parameter
  2147. * to request the jsonp provider with.
  2148. * @param {String} [options.callbackName=] The name of the callback to
  2149. * request the jsonp provider with.
  2150. */
  2151. jsonp: function( options ){
  2152. var script,
  2153. url = options.url,
  2154. head = document.head ||
  2155. document.getElementsByTagName( "head" )[ 0 ] ||
  2156. document.documentElement,
  2157. jsonpCallback = options.callbackName || 'openseadragon' + $.now(),
  2158. previous = window[ jsonpCallback ],
  2159. replace = "$1" + jsonpCallback + "$2",
  2160. callbackParam = options.param || 'callback',
  2161. callback = options.callback;
  2162. url = url.replace( /(\=)\?(&|$)|\?\?/i, replace );
  2163. // Add callback manually
  2164. url += (/\?/.test( url ) ? "&" : "?") + callbackParam + "=" + jsonpCallback;
  2165. // Install callback
  2166. window[ jsonpCallback ] = function( response ) {
  2167. if ( !previous ){
  2168. try{
  2169. delete window[ jsonpCallback ];
  2170. }catch(e){
  2171. //swallow
  2172. }
  2173. } else {
  2174. window[ jsonpCallback ] = previous;
  2175. }
  2176. if( callback && $.isFunction( callback ) ){
  2177. callback( response );
  2178. }
  2179. };
  2180. script = document.createElement( "script" );
  2181. //TODO: having an issue with async info requests
  2182. if( undefined !== options.async || false !== options.async ){
  2183. script.async = "async";
  2184. }
  2185. if ( options.scriptCharset ) {
  2186. script.charset = options.scriptCharset;
  2187. }
  2188. script.src = url;
  2189. // Attach handlers for all browsers
  2190. script.onload = script.onreadystatechange = function( _, isAbort ) {
  2191. if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
  2192. // Handle memory leak in IE
  2193. script.onload = script.onreadystatechange = null;
  2194. // Remove the script
  2195. if ( head && script.parentNode ) {
  2196. head.removeChild( script );
  2197. }
  2198. // Dereference the script
  2199. script = undefined;
  2200. }
  2201. };
  2202. // Use insertBefore instead of appendChild to circumvent an IE6 bug.
  2203. // This arises when a base node is used (#2709 and #4378).
  2204. head.insertBefore( script, head.firstChild );
  2205. },
  2206. /**
  2207. * Fully deprecated. Will throw an error.
  2208. * @function
  2209. * @deprecated use {@link OpenSeadragon.Viewer#open}
  2210. */
  2211. createFromDZI: function() {
  2212. throw "OpenSeadragon.createFromDZI is deprecated, use Viewer.open.";
  2213. },
  2214. /**
  2215. * Parses an XML string into a DOM Document.
  2216. * @function
  2217. * @param {String} string
  2218. * @returns {Document}
  2219. */
  2220. parseXml: function( string ) {
  2221. if ( window.DOMParser ) {
  2222. $.parseXml = function( string ) {
  2223. var xmlDoc = null,
  2224. parser;
  2225. parser = new DOMParser();
  2226. xmlDoc = parser.parseFromString( string, "text/xml" );
  2227. return xmlDoc;
  2228. };
  2229. } else if ( window.ActiveXObject ) {
  2230. $.parseXml = function( string ) {
  2231. var xmlDoc = null;
  2232. xmlDoc = new ActiveXObject( "Microsoft.XMLDOM" );
  2233. xmlDoc.async = false;
  2234. xmlDoc.loadXML( string );
  2235. return xmlDoc;
  2236. };
  2237. } else {
  2238. throw new Error( "Browser doesn't support XML DOM." );
  2239. }
  2240. return $.parseXml( string );
  2241. },
  2242. /**
  2243. * Parses a JSON string into a Javascript object.
  2244. * @function
  2245. * @param {String} string
  2246. * @returns {Object}
  2247. */
  2248. parseJSON: function(string) {
  2249. if (window.JSON && window.JSON.parse) {
  2250. $.parseJSON = window.JSON.parse;
  2251. } else {
  2252. // Should only be used by IE8 in non standards mode
  2253. $.parseJSON = function(string) {
  2254. /*jshint evil:true*/
  2255. //eslint-disable-next-line no-eval
  2256. return eval('(' + string + ')');
  2257. };
  2258. }
  2259. return $.parseJSON(string);
  2260. },
  2261. /**
  2262. * Reports whether the image format is supported for tiling in this
  2263. * version.
  2264. * @function
  2265. * @param {String} [extension]
  2266. * @returns {Boolean}
  2267. */
  2268. imageFormatSupported: function( extension ) {
  2269. extension = extension ? extension : "";
  2270. // eslint-disable-next-line no-use-before-define
  2271. return !!FILEFORMATS[ extension.toLowerCase() ];
  2272. }
  2273. });
  2274. /**
  2275. * The current browser vendor, version, and related information regarding detected features.
  2276. * @member {Object} Browser
  2277. * @memberof OpenSeadragon
  2278. * @static
  2279. * @type {Object}
  2280. * @property {OpenSeadragon.BROWSERS} vendor - One of the {@link OpenSeadragon.BROWSERS} enumeration values.
  2281. * @property {Number} version
  2282. * @property {Boolean} alpha - Does the browser support image alpha transparency.
  2283. */
  2284. $.Browser = {
  2285. vendor: $.BROWSERS.UNKNOWN,
  2286. version: 0,
  2287. alpha: true
  2288. };
  2289. var FILEFORMATS = {
  2290. "bmp": false,
  2291. "jpeg": true,
  2292. "jpg": true,
  2293. "png": true,
  2294. "tif": false,
  2295. "wdp": false
  2296. },
  2297. URLPARAMS = {};
  2298. (function() {
  2299. //A small auto-executing routine to determine the browser vendor,
  2300. //version and supporting feature sets.
  2301. var ver = navigator.appVersion,
  2302. ua = navigator.userAgent,
  2303. regex;
  2304. //console.error( 'appName: ' + navigator.appName );
  2305. //console.error( 'appVersion: ' + navigator.appVersion );
  2306. //console.error( 'userAgent: ' + navigator.userAgent );
  2307. switch( navigator.appName ){
  2308. case "Microsoft Internet Explorer":
  2309. if( !!window.attachEvent &&
  2310. !!window.ActiveXObject ) {
  2311. $.Browser.vendor = $.BROWSERS.IE;
  2312. $.Browser.version = parseFloat(
  2313. ua.substring(
  2314. ua.indexOf( "MSIE" ) + 5,
  2315. ua.indexOf( ";", ua.indexOf( "MSIE" ) ) )
  2316. );
  2317. }
  2318. break;
  2319. case "Netscape":
  2320. if (window.addEventListener) {
  2321. if ( ua.indexOf( "Firefox" ) >= 0 ) {
  2322. $.Browser.vendor = $.BROWSERS.FIREFOX;
  2323. $.Browser.version = parseFloat(
  2324. ua.substring( ua.indexOf( "Firefox" ) + 8 )
  2325. );
  2326. } else if ( ua.indexOf( "Safari" ) >= 0 ) {
  2327. $.Browser.vendor = ua.indexOf( "Chrome" ) >= 0 ?
  2328. $.BROWSERS.CHROME :
  2329. $.BROWSERS.SAFARI;
  2330. $.Browser.version = parseFloat(
  2331. ua.substring(
  2332. ua.substring( 0, ua.indexOf( "Safari" ) ).lastIndexOf( "/" ) + 1,
  2333. ua.indexOf( "Safari" )
  2334. )
  2335. );
  2336. } else {
  2337. regex = new RegExp( "Trident/.*rv:([0-9]{1,}[.0-9]{0,})");
  2338. if ( regex.exec( ua ) !== null ) {
  2339. $.Browser.vendor = $.BROWSERS.IE;
  2340. $.Browser.version = parseFloat( RegExp.$1 );
  2341. }
  2342. }
  2343. }
  2344. break;
  2345. case "Opera":
  2346. $.Browser.vendor = $.BROWSERS.OPERA;
  2347. $.Browser.version = parseFloat( ver );
  2348. break;
  2349. }
  2350. // ignore '?' portion of query string
  2351. var query = window.location.search.substring( 1 ),
  2352. parts = query.split('&'),
  2353. part,
  2354. sep,
  2355. i;
  2356. for ( i = 0; i < parts.length; i++ ) {
  2357. part = parts[ i ];
  2358. sep = part.indexOf( '=' );
  2359. if ( sep > 0 ) {
  2360. URLPARAMS[ part.substring( 0, sep ) ] =
  2361. decodeURIComponent( part.substring( sep + 1 ) );
  2362. }
  2363. }
  2364. //determine if this browser supports image alpha transparency
  2365. $.Browser.alpha = !(
  2366. (
  2367. $.Browser.vendor == $.BROWSERS.IE &&
  2368. $.Browser.version < 9
  2369. ) || (
  2370. $.Browser.vendor == $.BROWSERS.CHROME &&
  2371. $.Browser.version < 2
  2372. )
  2373. );
  2374. //determine if this browser supports element.style.opacity
  2375. $.Browser.opacity = !(
  2376. $.Browser.vendor == $.BROWSERS.IE &&
  2377. $.Browser.version < 9
  2378. );
  2379. })();
  2380. //TODO: $.console is often used inside a try/catch block which generally
  2381. // prevents allowings errors to occur with detection until a debugger
  2382. // is attached. Although I've been guilty of the same anti-pattern
  2383. // I eventually was convinced that errors should naturally propagate in
  2384. // all but the most special cases.
  2385. /**
  2386. * A convenient alias for console when available, and a simple null
  2387. * function when console is unavailable.
  2388. * @static
  2389. * @private
  2390. */
  2391. var nullfunction = function( msg ){
  2392. //document.location.hash = msg;
  2393. };
  2394. $.console = window.console || {
  2395. log: nullfunction,
  2396. debug: nullfunction,
  2397. info: nullfunction,
  2398. warn: nullfunction,
  2399. error: nullfunction,
  2400. assert: nullfunction
  2401. };
  2402. // Adding support for HTML5's requestAnimationFrame as suggested by acdha.
  2403. // Implementation taken from matt synder's post here:
  2404. // http://mattsnider.com/cross-browser-and-legacy-supported-requestframeanimation/
  2405. (function( w ) {
  2406. // most browsers have an implementation
  2407. var requestAnimationFrame = w.requestAnimationFrame ||
  2408. w.mozRequestAnimationFrame ||
  2409. w.webkitRequestAnimationFrame ||
  2410. w.msRequestAnimationFrame;
  2411. var cancelAnimationFrame = w.cancelAnimationFrame ||
  2412. w.mozCancelAnimationFrame ||
  2413. w.webkitCancelAnimationFrame ||
  2414. w.msCancelAnimationFrame;
  2415. // polyfill, when necessary
  2416. if ( requestAnimationFrame && cancelAnimationFrame ) {
  2417. // We can't assign these window methods directly to $ because they
  2418. // expect their "this" to be "window", so we call them in wrappers.
  2419. $.requestAnimationFrame = function(){
  2420. return requestAnimationFrame.apply( w, arguments );
  2421. };
  2422. $.cancelAnimationFrame = function(){
  2423. return cancelAnimationFrame.apply( w, arguments );
  2424. };
  2425. } else {
  2426. var aAnimQueue = [],
  2427. processing = [],
  2428. iRequestId = 0,
  2429. iIntervalId;
  2430. // create a mock requestAnimationFrame function
  2431. $.requestAnimationFrame = function( callback ) {
  2432. aAnimQueue.push( [ ++iRequestId, callback ] );
  2433. if ( !iIntervalId ) {
  2434. iIntervalId = setInterval( function() {
  2435. if ( aAnimQueue.length ) {
  2436. var time = $.now();
  2437. // Process all of the currently outstanding frame
  2438. // requests, but none that get added during the
  2439. // processing.
  2440. // Swap the arrays so we don't have to create a new
  2441. // array every frame.
  2442. var temp = processing;
  2443. processing = aAnimQueue;
  2444. aAnimQueue = temp;
  2445. while ( processing.length ) {
  2446. processing.shift()[ 1 ]( time );
  2447. }
  2448. } else {
  2449. // don't continue the interval, if unnecessary
  2450. clearInterval( iIntervalId );
  2451. iIntervalId = undefined;
  2452. }
  2453. }, 1000 / 50); // estimating support for 50 frames per second
  2454. }
  2455. return iRequestId;
  2456. };
  2457. // create a mock cancelAnimationFrame function
  2458. $.cancelAnimationFrame = function( requestId ) {
  2459. // find the request ID and remove it
  2460. var i, j;
  2461. for ( i = 0, j = aAnimQueue.length; i < j; i += 1 ) {
  2462. if ( aAnimQueue[ i ][ 0 ] === requestId ) {
  2463. aAnimQueue.splice( i, 1 );
  2464. return;
  2465. }
  2466. }
  2467. // If it's not in the queue, it may be in the set we're currently
  2468. // processing (if cancelAnimationFrame is called from within a
  2469. // requestAnimationFrame callback).
  2470. for ( i = 0, j = processing.length; i < j; i += 1 ) {
  2471. if ( processing[ i ][ 0 ] === requestId ) {
  2472. processing.splice( i, 1 );
  2473. return;
  2474. }
  2475. }
  2476. };
  2477. }
  2478. })( window );
  2479. /**
  2480. * @private
  2481. * @inner
  2482. * @function
  2483. * @param {Element} element
  2484. * @param {Boolean} [isFixed]
  2485. * @returns {Element}
  2486. */
  2487. function getOffsetParent( element, isFixed ) {
  2488. if ( isFixed && element != document.body ) {
  2489. return document.body;
  2490. } else {
  2491. return element.offsetParent;
  2492. }
  2493. }
  2494. }(OpenSeadragon));
  2495. // Universal Module Definition, supports CommonJS, AMD and simple script tag
  2496. (function (root, factory) {
  2497. if (typeof define === 'function' && define.amd) {
  2498. // expose as amd module
  2499. define([], factory);
  2500. } else if (typeof module === 'object' && module.exports) {
  2501. // expose as commonjs module
  2502. module.exports = factory();
  2503. } else {
  2504. // expose as window.OpenSeadragon
  2505. root.OpenSeadragon = factory();
  2506. }
  2507. }(this, function () {
  2508. return OpenSeadragon;
  2509. }));
  2510. /*
  2511. * OpenSeadragon - full-screen support functions
  2512. *
  2513. * Copyright (C) 2009 CodePlex Foundation
  2514. * Copyright (C) 2010-2013 OpenSeadragon contributors
  2515. *
  2516. * Redistribution and use in source and binary forms, with or without
  2517. * modification, are permitted provided that the following conditions are
  2518. * met:
  2519. *
  2520. * - Redistributions of source code must retain the above copyright notice,
  2521. * this list of conditions and the following disclaimer.
  2522. *
  2523. * - Redistributions in binary form must reproduce the above copyright
  2524. * notice, this list of conditions and the following disclaimer in the
  2525. * documentation and/or other materials provided with the distribution.
  2526. *
  2527. * - Neither the name of CodePlex Foundation nor the names of its
  2528. * contributors may be used to endorse or promote products derived from
  2529. * this software without specific prior written permission.
  2530. *
  2531. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  2532. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  2533. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  2534. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  2535. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  2536. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  2537. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  2538. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  2539. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  2540. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  2541. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  2542. */
  2543. (function( $ ) {
  2544. /**
  2545. * Determine native full screen support we can get from the browser.
  2546. * @member fullScreenApi
  2547. * @memberof OpenSeadragon
  2548. * @type {object}
  2549. * @property {Boolean} supportsFullScreen Return true if full screen API is supported.
  2550. * @property {Function} isFullScreen Return true if currently in full screen mode.
  2551. * @property {Function} getFullScreenElement Return the element currently in full screen mode.
  2552. * @property {Function} requestFullScreen Make a request to go in full screen mode.
  2553. * @property {Function} exitFullScreen Make a request to exit full screen mode.
  2554. * @property {Function} cancelFullScreen Deprecated, use exitFullScreen instead.
  2555. * @property {String} fullScreenEventName Event fired when the full screen mode change.
  2556. * @property {String} fullScreenErrorEventName Event fired when a request to go
  2557. * in full screen mode failed.
  2558. */
  2559. var fullScreenApi = {
  2560. supportsFullScreen: false,
  2561. isFullScreen: function() { return false; },
  2562. getFullScreenElement: function() { return null; },
  2563. requestFullScreen: function() {},
  2564. exitFullScreen: function() {},
  2565. cancelFullScreen: function() {},
  2566. fullScreenEventName: '',
  2567. fullScreenErrorEventName: ''
  2568. };
  2569. // check for native support
  2570. if ( document.exitFullscreen ) {
  2571. // W3C standard
  2572. fullScreenApi.supportsFullScreen = true;
  2573. fullScreenApi.getFullScreenElement = function() {
  2574. return document.fullscreenElement;
  2575. };
  2576. fullScreenApi.requestFullScreen = function( element ) {
  2577. return element.requestFullscreen();
  2578. };
  2579. fullScreenApi.exitFullScreen = function() {
  2580. document.exitFullscreen();
  2581. };
  2582. fullScreenApi.fullScreenEventName = "fullscreenchange";
  2583. fullScreenApi.fullScreenErrorEventName = "fullscreenerror";
  2584. } else if ( document.msExitFullscreen ) {
  2585. // IE 11
  2586. fullScreenApi.supportsFullScreen = true;
  2587. fullScreenApi.getFullScreenElement = function() {
  2588. return document.msFullscreenElement;
  2589. };
  2590. fullScreenApi.requestFullScreen = function( element ) {
  2591. return element.msRequestFullscreen();
  2592. };
  2593. fullScreenApi.exitFullScreen = function() {
  2594. document.msExitFullscreen();
  2595. };
  2596. fullScreenApi.fullScreenEventName = "MSFullscreenChange";
  2597. fullScreenApi.fullScreenErrorEventName = "MSFullscreenError";
  2598. } else if ( document.webkitExitFullscreen ) {
  2599. // Recent webkit
  2600. fullScreenApi.supportsFullScreen = true;
  2601. fullScreenApi.getFullScreenElement = function() {
  2602. return document.webkitFullscreenElement;
  2603. };
  2604. fullScreenApi.requestFullScreen = function( element ) {
  2605. return element.webkitRequestFullscreen();
  2606. };
  2607. fullScreenApi.exitFullScreen = function() {
  2608. document.webkitExitFullscreen();
  2609. };
  2610. fullScreenApi.fullScreenEventName = "webkitfullscreenchange";
  2611. fullScreenApi.fullScreenErrorEventName = "webkitfullscreenerror";
  2612. } else if ( document.webkitCancelFullScreen ) {
  2613. // Old webkit
  2614. fullScreenApi.supportsFullScreen = true;
  2615. fullScreenApi.getFullScreenElement = function() {
  2616. return document.webkitCurrentFullScreenElement;
  2617. };
  2618. fullScreenApi.requestFullScreen = function( element ) {
  2619. return element.webkitRequestFullScreen();
  2620. };
  2621. fullScreenApi.exitFullScreen = function() {
  2622. document.webkitCancelFullScreen();
  2623. };
  2624. fullScreenApi.fullScreenEventName = "webkitfullscreenchange";
  2625. fullScreenApi.fullScreenErrorEventName = "webkitfullscreenerror";
  2626. } else if ( document.mozCancelFullScreen ) {
  2627. // Firefox
  2628. fullScreenApi.supportsFullScreen = true;
  2629. fullScreenApi.getFullScreenElement = function() {
  2630. return document.mozFullScreenElement;
  2631. };
  2632. fullScreenApi.requestFullScreen = function( element ) {
  2633. return element.mozRequestFullScreen();
  2634. };
  2635. fullScreenApi.exitFullScreen = function() {
  2636. document.mozCancelFullScreen();
  2637. };
  2638. fullScreenApi.fullScreenEventName = "mozfullscreenchange";
  2639. fullScreenApi.fullScreenErrorEventName = "mozfullscreenerror";
  2640. }
  2641. fullScreenApi.isFullScreen = function() {
  2642. return fullScreenApi.getFullScreenElement() !== null;
  2643. };
  2644. fullScreenApi.cancelFullScreen = function() {
  2645. $.console.error("cancelFullScreen is deprecated. Use exitFullScreen instead.");
  2646. fullScreenApi.exitFullScreen();
  2647. };
  2648. // export api
  2649. $.extend( $, fullScreenApi );
  2650. })( OpenSeadragon );
  2651. /*
  2652. * OpenSeadragon - EventSource
  2653. *
  2654. * Copyright (C) 2009 CodePlex Foundation
  2655. * Copyright (C) 2010-2013 OpenSeadragon contributors
  2656. *
  2657. * Redistribution and use in source and binary forms, with or without
  2658. * modification, are permitted provided that the following conditions are
  2659. * met:
  2660. *
  2661. * - Redistributions of source code must retain the above copyright notice,
  2662. * this list of conditions and the following disclaimer.
  2663. *
  2664. * - Redistributions in binary form must reproduce the above copyright
  2665. * notice, this list of conditions and the following disclaimer in the
  2666. * documentation and/or other materials provided with the distribution.
  2667. *
  2668. * - Neither the name of CodePlex Foundation nor the names of its
  2669. * contributors may be used to endorse or promote products derived from
  2670. * this software without specific prior written permission.
  2671. *
  2672. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  2673. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  2674. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  2675. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  2676. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  2677. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  2678. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  2679. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  2680. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  2681. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  2682. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  2683. */
  2684. (function($){
  2685. /**
  2686. * Event handler method signature used by all OpenSeadragon events.
  2687. *
  2688. * @callback EventHandler
  2689. * @memberof OpenSeadragon
  2690. * @param {Object} event - See individual events for event-specific properties.
  2691. */
  2692. /**
  2693. * @class EventSource
  2694. * @classdesc For use by classes which want to support custom, non-browser events.
  2695. *
  2696. * @memberof OpenSeadragon
  2697. */
  2698. $.EventSource = function() {
  2699. this.events = {};
  2700. };
  2701. /** @lends OpenSeadragon.EventSource.prototype */
  2702. $.EventSource.prototype = {
  2703. /**
  2704. * Add an event handler to be triggered only once (or a given number of times)
  2705. * for a given event.
  2706. * @function
  2707. * @param {String} eventName - Name of event to register.
  2708. * @param {OpenSeadragon.EventHandler} handler - Function to call when event
  2709. * is triggered.
  2710. * @param {Object} [userData=null] - Arbitrary object to be passed unchanged
  2711. * to the handler.
  2712. * @param {Number} [times=1] - The number of times to handle the event
  2713. * before removing it.
  2714. */
  2715. addOnceHandler: function(eventName, handler, userData, times) {
  2716. var self = this;
  2717. times = times || 1;
  2718. var count = 0;
  2719. var onceHandler = function(event) {
  2720. count++;
  2721. if (count === times) {
  2722. self.removeHandler(eventName, onceHandler);
  2723. }
  2724. handler(event);
  2725. };
  2726. this.addHandler(eventName, onceHandler, userData);
  2727. },
  2728. /**
  2729. * Add an event handler for a given event.
  2730. * @function
  2731. * @param {String} eventName - Name of event to register.
  2732. * @param {OpenSeadragon.EventHandler} handler - Function to call when event is triggered.
  2733. * @param {Object} [userData=null] - Arbitrary object to be passed unchanged to the handler.
  2734. */
  2735. addHandler: function ( eventName, handler, userData ) {
  2736. var events = this.events[ eventName ];
  2737. if ( !events ) {
  2738. this.events[ eventName ] = events = [];
  2739. }
  2740. if ( handler && $.isFunction( handler ) ) {
  2741. events[ events.length ] = { handler: handler, userData: userData || null };
  2742. }
  2743. },
  2744. /**
  2745. * Remove a specific event handler for a given event.
  2746. * @function
  2747. * @param {String} eventName - Name of event for which the handler is to be removed.
  2748. * @param {OpenSeadragon.EventHandler} handler - Function to be removed.
  2749. */
  2750. removeHandler: function ( eventName, handler ) {
  2751. var events = this.events[ eventName ],
  2752. handlers = [],
  2753. i;
  2754. if ( !events ) {
  2755. return;
  2756. }
  2757. if ( $.isArray( events ) ) {
  2758. for ( i = 0; i < events.length; i++ ) {
  2759. if ( events[i].handler !== handler ) {
  2760. handlers.push( events[ i ] );
  2761. }
  2762. }
  2763. this.events[ eventName ] = handlers;
  2764. }
  2765. },
  2766. /**
  2767. * Remove all event handlers for a given event type. If no type is given all
  2768. * event handlers for every event type are removed.
  2769. * @function
  2770. * @param {String} eventName - Name of event for which all handlers are to be removed.
  2771. */
  2772. removeAllHandlers: function( eventName ) {
  2773. if ( eventName ){
  2774. this.events[ eventName ] = [];
  2775. } else{
  2776. for ( var eventType in this.events ) {
  2777. this.events[ eventType ] = [];
  2778. }
  2779. }
  2780. },
  2781. /**
  2782. * Get a function which iterates the list of all handlers registered for a given event, calling the handler for each.
  2783. * @function
  2784. * @param {String} eventName - Name of event to get handlers for.
  2785. */
  2786. getHandler: function ( eventName ) {
  2787. var events = this.events[ eventName ];
  2788. if ( !events || !events.length ) {
  2789. return null;
  2790. }
  2791. events = events.length === 1 ?
  2792. [ events[ 0 ] ] :
  2793. Array.apply( null, events );
  2794. return function ( source, args ) {
  2795. var i,
  2796. length = events.length;
  2797. for ( i = 0; i < length; i++ ) {
  2798. if ( events[ i ] ) {
  2799. args.eventSource = source;
  2800. args.userData = events[ i ].userData;
  2801. events[ i ].handler( args );
  2802. }
  2803. }
  2804. };
  2805. },
  2806. /**
  2807. * Trigger an event, optionally passing additional information.
  2808. * @function
  2809. * @param {String} eventName - Name of event to register.
  2810. * @param {Object} eventArgs - Event-specific data.
  2811. */
  2812. raiseEvent: function( eventName, eventArgs ) {
  2813. //uncomment if you want to get a log of all events
  2814. //$.console.log( eventName );
  2815. var handler = this.getHandler( eventName );
  2816. if ( handler ) {
  2817. if ( !eventArgs ) {
  2818. eventArgs = {};
  2819. }
  2820. handler( this, eventArgs );
  2821. }
  2822. }
  2823. };
  2824. }( OpenSeadragon ));
  2825. /*
  2826. * OpenSeadragon - MouseTracker
  2827. *
  2828. * Copyright (C) 2009 CodePlex Foundation
  2829. * Copyright (C) 2010-2013 OpenSeadragon contributors
  2830. *
  2831. * Redistribution and use in source and binary forms, with or without
  2832. * modification, are permitted provided that the following conditions are
  2833. * met:
  2834. *
  2835. * - Redistributions of source code must retain the above copyright notice,
  2836. * this list of conditions and the following disclaimer.
  2837. *
  2838. * - Redistributions in binary form must reproduce the above copyright
  2839. * notice, this list of conditions and the following disclaimer in the
  2840. * documentation and/or other materials provided with the distribution.
  2841. *
  2842. * - Neither the name of CodePlex Foundation nor the names of its
  2843. * contributors may be used to endorse or promote products derived from
  2844. * this software without specific prior written permission.
  2845. *
  2846. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  2847. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  2848. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  2849. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  2850. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  2851. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  2852. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  2853. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  2854. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  2855. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  2856. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  2857. */
  2858. (function ( $ ) {
  2859. // All MouseTracker instances
  2860. var MOUSETRACKERS = [];
  2861. // dictionary from hash to private properties
  2862. var THIS = {};
  2863. /**
  2864. * @class MouseTracker
  2865. * @classdesc Provides simplified handling of common pointer device (mouse, touch, pen, etc.) gestures
  2866. * and keyboard events on a specified element.
  2867. * @memberof OpenSeadragon
  2868. * @param {Object} options
  2869. * Allows configurable properties to be entirely specified by passing
  2870. * an options object to the constructor. The constructor also supports
  2871. * the original positional arguments 'element', 'clickTimeThreshold',
  2872. * and 'clickDistThreshold' in that order.
  2873. * @param {Element|String} options.element
  2874. * A reference to an element or an element id for which the pointer/key
  2875. * events will be monitored.
  2876. * @param {Boolean} [options.startDisabled=false]
  2877. * If true, event tracking on the element will not start until
  2878. * {@link OpenSeadragon.MouseTracker.setTracking|setTracking} is called.
  2879. * @param {Number} options.clickTimeThreshold
  2880. * The number of milliseconds within which a pointer down-up event combination
  2881. * will be treated as a click gesture.
  2882. * @param {Number} options.clickDistThreshold
  2883. * The maximum distance allowed between a pointer down event and a pointer up event
  2884. * to be treated as a click gesture.
  2885. * @param {Number} options.dblClickTimeThreshold
  2886. * The number of milliseconds within which two pointer down-up event combinations
  2887. * will be treated as a double-click gesture.
  2888. * @param {Number} options.dblClickDistThreshold
  2889. * The maximum distance allowed between two pointer click events
  2890. * to be treated as a click gesture.
  2891. * @param {Number} [options.stopDelay=50]
  2892. * The number of milliseconds without pointer move before the stop
  2893. * event is fired.
  2894. * @param {OpenSeadragon.EventHandler} [options.enterHandler=null]
  2895. * An optional handler for pointer enter.
  2896. * @param {OpenSeadragon.EventHandler} [options.exitHandler=null]
  2897. * An optional handler for pointer exit.
  2898. * @param {OpenSeadragon.EventHandler} [options.pressHandler=null]
  2899. * An optional handler for pointer press.
  2900. * @param {OpenSeadragon.EventHandler} [options.nonPrimaryPressHandler=null]
  2901. * An optional handler for pointer non-primary button press.
  2902. * @param {OpenSeadragon.EventHandler} [options.releaseHandler=null]
  2903. * An optional handler for pointer release.
  2904. * @param {OpenSeadragon.EventHandler} [options.nonPrimaryReleaseHandler=null]
  2905. * An optional handler for pointer non-primary button release.
  2906. * @param {OpenSeadragon.EventHandler} [options.moveHandler=null]
  2907. * An optional handler for pointer move.
  2908. * @param {OpenSeadragon.EventHandler} [options.scrollHandler=null]
  2909. * An optional handler for mouse wheel scroll.
  2910. * @param {OpenSeadragon.EventHandler} [options.clickHandler=null]
  2911. * An optional handler for pointer click.
  2912. * @param {OpenSeadragon.EventHandler} [options.dblClickHandler=null]
  2913. * An optional handler for pointer double-click.
  2914. * @param {OpenSeadragon.EventHandler} [options.dragHandler=null]
  2915. * An optional handler for the drag gesture.
  2916. * @param {OpenSeadragon.EventHandler} [options.dragEndHandler=null]
  2917. * An optional handler for after a drag gesture.
  2918. * @param {OpenSeadragon.EventHandler} [options.pinchHandler=null]
  2919. * An optional handler for the pinch gesture.
  2920. * @param {OpenSeadragon.EventHandler} [options.keyDownHandler=null]
  2921. * An optional handler for keydown.
  2922. * @param {OpenSeadragon.EventHandler} [options.keyUpHandler=null]
  2923. * An optional handler for keyup.
  2924. * @param {OpenSeadragon.EventHandler} [options.keyHandler=null]
  2925. * An optional handler for keypress.
  2926. * @param {OpenSeadragon.EventHandler} [options.focusHandler=null]
  2927. * An optional handler for focus.
  2928. * @param {OpenSeadragon.EventHandler} [options.blurHandler=null]
  2929. * An optional handler for blur.
  2930. * @param {Object} [options.userData=null]
  2931. * Arbitrary object to be passed unchanged to any attached handler methods.
  2932. */
  2933. $.MouseTracker = function ( options ) {
  2934. MOUSETRACKERS.push( this );
  2935. var args = arguments;
  2936. if ( !$.isPlainObject( options ) ) {
  2937. options = {
  2938. element: args[ 0 ],
  2939. clickTimeThreshold: args[ 1 ],
  2940. clickDistThreshold: args[ 2 ]
  2941. };
  2942. }
  2943. this.hash = Math.random(); // An unique hash for this tracker.
  2944. /**
  2945. * The element for which pointer events are being monitored.
  2946. * @member {Element} element
  2947. * @memberof OpenSeadragon.MouseTracker#
  2948. */
  2949. this.element = $.getElement( options.element );
  2950. /**
  2951. * The number of milliseconds within which a pointer down-up event combination
  2952. * will be treated as a click gesture.
  2953. * @member {Number} clickTimeThreshold
  2954. * @memberof OpenSeadragon.MouseTracker#
  2955. */
  2956. this.clickTimeThreshold = options.clickTimeThreshold || $.DEFAULT_SETTINGS.clickTimeThreshold;
  2957. /**
  2958. * The maximum distance allowed between a pointer down event and a pointer up event
  2959. * to be treated as a click gesture.
  2960. * @member {Number} clickDistThreshold
  2961. * @memberof OpenSeadragon.MouseTracker#
  2962. */
  2963. this.clickDistThreshold = options.clickDistThreshold || $.DEFAULT_SETTINGS.clickDistThreshold;
  2964. /**
  2965. * The number of milliseconds within which two pointer down-up event combinations
  2966. * will be treated as a double-click gesture.
  2967. * @member {Number} dblClickTimeThreshold
  2968. * @memberof OpenSeadragon.MouseTracker#
  2969. */
  2970. this.dblClickTimeThreshold = options.dblClickTimeThreshold || $.DEFAULT_SETTINGS.dblClickTimeThreshold;
  2971. /**
  2972. * The maximum distance allowed between two pointer click events
  2973. * to be treated as a click gesture.
  2974. * @member {Number} clickDistThreshold
  2975. * @memberof OpenSeadragon.MouseTracker#
  2976. */
  2977. this.dblClickDistThreshold = options.dblClickDistThreshold || $.DEFAULT_SETTINGS.dblClickDistThreshold;
  2978. /*eslint-disable no-multi-spaces*/
  2979. this.userData = options.userData || null;
  2980. this.stopDelay = options.stopDelay || 50;
  2981. this.enterHandler = options.enterHandler || null;
  2982. this.exitHandler = options.exitHandler || null;
  2983. this.pressHandler = options.pressHandler || null;
  2984. this.nonPrimaryPressHandler = options.nonPrimaryPressHandler || null;
  2985. this.releaseHandler = options.releaseHandler || null;
  2986. this.nonPrimaryReleaseHandler = options.nonPrimaryReleaseHandler || null;
  2987. this.moveHandler = options.moveHandler || null;
  2988. this.scrollHandler = options.scrollHandler || null;
  2989. this.clickHandler = options.clickHandler || null;
  2990. this.dblClickHandler = options.dblClickHandler || null;
  2991. this.dragHandler = options.dragHandler || null;
  2992. this.dragEndHandler = options.dragEndHandler || null;
  2993. this.pinchHandler = options.pinchHandler || null;
  2994. this.stopHandler = options.stopHandler || null;
  2995. this.keyDownHandler = options.keyDownHandler || null;
  2996. this.keyUpHandler = options.keyUpHandler || null;
  2997. this.keyHandler = options.keyHandler || null;
  2998. this.focusHandler = options.focusHandler || null;
  2999. this.blurHandler = options.blurHandler || null;
  3000. /*eslint-enable no-multi-spaces*/
  3001. //Store private properties in a scope sealed hash map
  3002. var _this = this;
  3003. /**
  3004. * @private
  3005. * @property {Boolean} tracking
  3006. * Are we currently tracking pointer events for this element.
  3007. */
  3008. THIS[ this.hash ] = {
  3009. click: function ( event ) { onClick( _this, event ); },
  3010. dblclick: function ( event ) { onDblClick( _this, event ); },
  3011. keydown: function ( event ) { onKeyDown( _this, event ); },
  3012. keyup: function ( event ) { onKeyUp( _this, event ); },
  3013. keypress: function ( event ) { onKeyPress( _this, event ); },
  3014. focus: function ( event ) { onFocus( _this, event ); },
  3015. blur: function ( event ) { onBlur( _this, event ); },
  3016. wheel: function ( event ) { onWheel( _this, event ); },
  3017. mousewheel: function ( event ) { onMouseWheel( _this, event ); },
  3018. DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); },
  3019. MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); },
  3020. mouseenter: function ( event ) { onMouseEnter( _this, event ); }, // Used on IE8 only
  3021. mouseleave: function ( event ) { onMouseLeave( _this, event ); }, // Used on IE8 only
  3022. mouseover: function ( event ) { onMouseOver( _this, event ); },
  3023. mouseout: function ( event ) { onMouseOut( _this, event ); },
  3024. mousedown: function ( event ) { onMouseDown( _this, event ); },
  3025. mouseup: function ( event ) { onMouseUp( _this, event ); },
  3026. mouseupcaptured: function ( event ) { onMouseUpCaptured( _this, event ); },
  3027. mousemove: function ( event ) { onMouseMove( _this, event ); },
  3028. mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event ); },
  3029. touchstart: function ( event ) { onTouchStart( _this, event ); },
  3030. touchend: function ( event ) { onTouchEnd( _this, event ); },
  3031. touchendcaptured: function ( event ) { onTouchEndCaptured( _this, event ); },
  3032. touchmove: function ( event ) { onTouchMove( _this, event ); },
  3033. touchmovecaptured: function ( event ) { onTouchMoveCaptured( _this, event ); },
  3034. touchcancel: function ( event ) { onTouchCancel( _this, event ); },
  3035. gesturestart: function ( event ) { onGestureStart( _this, event ); },
  3036. gesturechange: function ( event ) { onGestureChange( _this, event ); },
  3037. pointerover: function ( event ) { onPointerOver( _this, event ); },
  3038. MSPointerOver: function ( event ) { onPointerOver( _this, event ); },
  3039. pointerout: function ( event ) { onPointerOut( _this, event ); },
  3040. MSPointerOut: function ( event ) { onPointerOut( _this, event ); },
  3041. pointerdown: function ( event ) { onPointerDown( _this, event ); },
  3042. MSPointerDown: function ( event ) { onPointerDown( _this, event ); },
  3043. pointerup: function ( event ) { onPointerUp( _this, event ); },
  3044. MSPointerUp: function ( event ) { onPointerUp( _this, event ); },
  3045. pointermove: function ( event ) { onPointerMove( _this, event ); },
  3046. MSPointerMove: function ( event ) { onPointerMove( _this, event ); },
  3047. pointercancel: function ( event ) { onPointerCancel( _this, event ); },
  3048. MSPointerCancel: function ( event ) { onPointerCancel( _this, event ); },
  3049. pointerupcaptured: function ( event ) { onPointerUpCaptured( _this, event ); },
  3050. pointermovecaptured: function ( event ) { onPointerMoveCaptured( _this, event ); },
  3051. tracking: false,
  3052. // Active pointers lists. Array of GesturePointList objects, one for each pointer device type.
  3053. // GesturePointList objects are added each time a pointer is tracked by a new pointer device type (see getActivePointersListByType()).
  3054. // Active pointers are any pointer being tracked for this element which are in the hit-test area
  3055. // of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
  3056. activePointersLists: [],
  3057. // Tracking for double-click gesture
  3058. lastClickPos: null,
  3059. dblClickTimeOut: null,
  3060. // Tracking for pinch gesture
  3061. pinchGPoints: [],
  3062. lastPinchDist: 0,
  3063. currentPinchDist: 0,
  3064. lastPinchCenter: null,
  3065. currentPinchCenter: null
  3066. };
  3067. if ( !options.startDisabled ) {
  3068. this.setTracking( true );
  3069. }
  3070. };
  3071. /** @lends OpenSeadragon.MouseTracker.prototype */
  3072. $.MouseTracker.prototype = {
  3073. /**
  3074. * Clean up any events or objects created by the tracker.
  3075. * @function
  3076. */
  3077. destroy: function () {
  3078. var i;
  3079. stopTracking( this );
  3080. this.element = null;
  3081. for ( i = 0; i < MOUSETRACKERS.length; i++ ) {
  3082. if ( MOUSETRACKERS[ i ] === this ) {
  3083. MOUSETRACKERS.splice( i, 1 );
  3084. break;
  3085. }
  3086. }
  3087. THIS[ this.hash ] = null;
  3088. delete THIS[ this.hash ];
  3089. },
  3090. /**
  3091. * Are we currently tracking events on this element.
  3092. * @deprecated Just use this.tracking
  3093. * @function
  3094. * @returns {Boolean} Are we currently tracking events on this element.
  3095. */
  3096. isTracking: function () {
  3097. return THIS[ this.hash ].tracking;
  3098. },
  3099. /**
  3100. * Enable or disable whether or not we are tracking events on this element.
  3101. * @function
  3102. * @param {Boolean} track True to start tracking, false to stop tracking.
  3103. * @returns {OpenSeadragon.MouseTracker} Chainable.
  3104. */
  3105. setTracking: function ( track ) {
  3106. if ( track ) {
  3107. startTracking( this );
  3108. } else {
  3109. stopTracking( this );
  3110. }
  3111. //chain
  3112. return this;
  3113. },
  3114. /**
  3115. * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for all but the given pointer device type.
  3116. * @function
  3117. * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
  3118. * @returns {Array.<OpenSeadragon.MouseTracker.GesturePointList>}
  3119. */
  3120. getActivePointersListsExceptType: function ( type ) {
  3121. var delegate = THIS[ this.hash ];
  3122. var listArray = [];
  3123. for (var i = 0; i < delegate.activePointersLists.length; ++i) {
  3124. if (delegate.activePointersLists[i].type !== type) {
  3125. listArray.push(delegate.activePointersLists[i]);
  3126. }
  3127. }
  3128. return listArray;
  3129. },
  3130. /**
  3131. * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for the given pointer device type,
  3132. * creating and caching a new {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} if one doesn't already exist for the type.
  3133. * @function
  3134. * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
  3135. * @returns {OpenSeadragon.MouseTracker.GesturePointList}
  3136. */
  3137. getActivePointersListByType: function ( type ) {
  3138. var delegate = THIS[ this.hash ],
  3139. i,
  3140. len = delegate.activePointersLists.length,
  3141. list;
  3142. for ( i = 0; i < len; i++ ) {
  3143. if ( delegate.activePointersLists[ i ].type === type ) {
  3144. return delegate.activePointersLists[ i ];
  3145. }
  3146. }
  3147. list = new $.MouseTracker.GesturePointList( type );
  3148. delegate.activePointersLists.push( list );
  3149. return list;
  3150. },
  3151. /**
  3152. * Returns the total number of pointers currently active on the tracked element.
  3153. * @function
  3154. * @returns {Number}
  3155. */
  3156. getActivePointerCount: function () {
  3157. var delegate = THIS[ this.hash ],
  3158. i,
  3159. len = delegate.activePointersLists.length,
  3160. count = 0;
  3161. for ( i = 0; i < len; i++ ) {
  3162. count += delegate.activePointersLists[ i ].getLength();
  3163. }
  3164. return count;
  3165. },
  3166. /**
  3167. * Implement or assign implementation to these handlers during or after
  3168. * calling the constructor.
  3169. * @function
  3170. * @param {Object} event
  3171. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3172. * A reference to the tracker instance.
  3173. * @param {String} event.pointerType
  3174. * "mouse", "touch", "pen", etc.
  3175. * @param {OpenSeadragon.Point} event.position
  3176. * The position of the event relative to the tracked element.
  3177. * @param {Number} event.buttons
  3178. * Current buttons pressed.
  3179. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  3180. * @param {Number} event.pointers
  3181. * Number of pointers (all types) active in the tracked element.
  3182. * @param {Boolean} event.insideElementPressed
  3183. * True if the left mouse button is currently being pressed and was
  3184. * initiated inside the tracked element, otherwise false.
  3185. * @param {Boolean} event.buttonDownAny
  3186. * Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
  3187. * @param {Boolean} event.isTouchEvent
  3188. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
  3189. * @param {Object} event.originalEvent
  3190. * The original event object.
  3191. * @param {Boolean} event.preventDefaultAction
  3192. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3193. * @param {Object} event.userData
  3194. * Arbitrary user-defined object.
  3195. */
  3196. enterHandler: function () { },
  3197. /**
  3198. * Implement or assign implementation to these handlers during or after
  3199. * calling the constructor.
  3200. * @function
  3201. * @param {Object} event
  3202. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3203. * A reference to the tracker instance.
  3204. * @param {String} event.pointerType
  3205. * "mouse", "touch", "pen", etc.
  3206. * @param {OpenSeadragon.Point} event.position
  3207. * The position of the event relative to the tracked element.
  3208. * @param {Number} event.buttons
  3209. * Current buttons pressed.
  3210. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  3211. * @param {Number} event.pointers
  3212. * Number of pointers (all types) active in the tracked element.
  3213. * @param {Boolean} event.insideElementPressed
  3214. * True if the left mouse button is currently being pressed and was
  3215. * initiated inside the tracked element, otherwise false.
  3216. * @param {Boolean} event.buttonDownAny
  3217. * Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
  3218. * @param {Boolean} event.isTouchEvent
  3219. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
  3220. * @param {Object} event.originalEvent
  3221. * The original event object.
  3222. * @param {Boolean} event.preventDefaultAction
  3223. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3224. * @param {Object} event.userData
  3225. * Arbitrary user-defined object.
  3226. */
  3227. exitHandler: function () { },
  3228. /**
  3229. * Implement or assign implementation to these handlers during or after
  3230. * calling the constructor.
  3231. * @function
  3232. * @param {Object} event
  3233. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3234. * A reference to the tracker instance.
  3235. * @param {String} event.pointerType
  3236. * "mouse", "touch", "pen", etc.
  3237. * @param {OpenSeadragon.Point} event.position
  3238. * The position of the event relative to the tracked element.
  3239. * @param {Number} event.buttons
  3240. * Current buttons pressed.
  3241. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  3242. * @param {Boolean} event.isTouchEvent
  3243. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
  3244. * @param {Object} event.originalEvent
  3245. * The original event object.
  3246. * @param {Boolean} event.preventDefaultAction
  3247. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3248. * @param {Object} event.userData
  3249. * Arbitrary user-defined object.
  3250. */
  3251. pressHandler: function () { },
  3252. /**
  3253. * Implement or assign implementation to these handlers during or after
  3254. * calling the constructor.
  3255. * @function
  3256. * @param {Object} event
  3257. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3258. * A reference to the tracker instance.
  3259. * @param {String} event.pointerType
  3260. * "mouse", "touch", "pen", etc.
  3261. * @param {OpenSeadragon.Point} event.position
  3262. * The position of the event relative to the tracked element.
  3263. * @param {Number} event.button
  3264. * Button which caused the event.
  3265. * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
  3266. * @param {Number} event.buttons
  3267. * Current buttons pressed.
  3268. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  3269. * @param {Boolean} event.isTouchEvent
  3270. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
  3271. * @param {Object} event.originalEvent
  3272. * The original event object.
  3273. * @param {Boolean} event.preventDefaultAction
  3274. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3275. * @param {Object} event.userData
  3276. * Arbitrary user-defined object.
  3277. */
  3278. nonPrimaryPressHandler: function () { },
  3279. /**
  3280. * Implement or assign implementation to these handlers during or after
  3281. * calling the constructor.
  3282. * @function
  3283. * @param {Object} event
  3284. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3285. * A reference to the tracker instance.
  3286. * @param {String} event.pointerType
  3287. * "mouse", "touch", "pen", etc.
  3288. * @param {OpenSeadragon.Point} event.position
  3289. * The position of the event relative to the tracked element.
  3290. * @param {Number} event.buttons
  3291. * Current buttons pressed.
  3292. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  3293. * @param {Boolean} event.insideElementPressed
  3294. * True if the left mouse button is currently being pressed and was
  3295. * initiated inside the tracked element, otherwise false.
  3296. * @param {Boolean} event.insideElementReleased
  3297. * True if the cursor inside the tracked element when the button was released.
  3298. * @param {Boolean} event.isTouchEvent
  3299. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
  3300. * @param {Object} event.originalEvent
  3301. * The original event object.
  3302. * @param {Boolean} event.preventDefaultAction
  3303. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3304. * @param {Object} event.userData
  3305. * Arbitrary user-defined object.
  3306. */
  3307. releaseHandler: function () { },
  3308. /**
  3309. * Implement or assign implementation to these handlers during or after
  3310. * calling the constructor.
  3311. * @function
  3312. * @param {Object} event
  3313. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3314. * A reference to the tracker instance.
  3315. * @param {String} event.pointerType
  3316. * "mouse", "touch", "pen", etc.
  3317. * @param {OpenSeadragon.Point} event.position
  3318. * The position of the event relative to the tracked element.
  3319. * @param {Number} event.button
  3320. * Button which caused the event.
  3321. * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
  3322. * @param {Number} event.buttons
  3323. * Current buttons pressed.
  3324. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  3325. * @param {Boolean} event.isTouchEvent
  3326. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
  3327. * @param {Object} event.originalEvent
  3328. * The original event object.
  3329. * @param {Boolean} event.preventDefaultAction
  3330. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3331. * @param {Object} event.userData
  3332. * Arbitrary user-defined object.
  3333. */
  3334. nonPrimaryReleaseHandler: function () { },
  3335. /**
  3336. * Implement or assign implementation to these handlers during or after
  3337. * calling the constructor.
  3338. * @function
  3339. * @param {Object} event
  3340. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3341. * A reference to the tracker instance.
  3342. * @param {String} event.pointerType
  3343. * "mouse", "touch", "pen", etc.
  3344. * @param {OpenSeadragon.Point} event.position
  3345. * The position of the event relative to the tracked element.
  3346. * @param {Number} event.buttons
  3347. * Current buttons pressed.
  3348. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  3349. * @param {Boolean} event.isTouchEvent
  3350. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
  3351. * @param {Object} event.originalEvent
  3352. * The original event object.
  3353. * @param {Boolean} event.preventDefaultAction
  3354. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3355. * @param {Object} event.userData
  3356. * Arbitrary user-defined object.
  3357. */
  3358. moveHandler: function () { },
  3359. /**
  3360. * Implement or assign implementation to these handlers during or after
  3361. * calling the constructor.
  3362. * @function
  3363. * @param {Object} event
  3364. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3365. * A reference to the tracker instance.
  3366. * @param {String} event.pointerType
  3367. * "mouse", "touch", "pen", etc.
  3368. * @param {OpenSeadragon.Point} event.position
  3369. * The position of the event relative to the tracked element.
  3370. * @param {Number} event.scroll
  3371. * The scroll delta for the event.
  3372. * @param {Boolean} event.shift
  3373. * True if the shift key was pressed during this event.
  3374. * @param {Boolean} event.isTouchEvent
  3375. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead. Touch devices no longer generate scroll event.</span>
  3376. * @param {Object} event.originalEvent
  3377. * The original event object.
  3378. * @param {Boolean} event.preventDefaultAction
  3379. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3380. * @param {Object} event.userData
  3381. * Arbitrary user-defined object.
  3382. */
  3383. scrollHandler: function () { },
  3384. /**
  3385. * Implement or assign implementation to these handlers during or after
  3386. * calling the constructor.
  3387. * @function
  3388. * @param {Object} event
  3389. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3390. * A reference to the tracker instance.
  3391. * @param {String} event.pointerType
  3392. * "mouse", "touch", "pen", etc.
  3393. * @param {OpenSeadragon.Point} event.position
  3394. * The position of the event relative to the tracked element.
  3395. * @param {Boolean} event.quick
  3396. * True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for ignoring drag events.
  3397. * @param {Boolean} event.shift
  3398. * True if the shift key was pressed during this event.
  3399. * @param {Boolean} event.isTouchEvent
  3400. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
  3401. * @param {Object} event.originalEvent
  3402. * The original event object.
  3403. * @param {Boolean} event.preventDefaultAction
  3404. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3405. * @param {Object} event.userData
  3406. * Arbitrary user-defined object.
  3407. */
  3408. clickHandler: function () { },
  3409. /**
  3410. * Implement or assign implementation to these handlers during or after
  3411. * calling the constructor.
  3412. * @function
  3413. * @param {Object} event
  3414. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3415. * A reference to the tracker instance.
  3416. * @param {String} event.pointerType
  3417. * "mouse", "touch", "pen", etc.
  3418. * @param {OpenSeadragon.Point} event.position
  3419. * The position of the event relative to the tracked element.
  3420. * @param {Boolean} event.shift
  3421. * True if the shift key was pressed during this event.
  3422. * @param {Boolean} event.isTouchEvent
  3423. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
  3424. * @param {Object} event.originalEvent
  3425. * The original event object.
  3426. * @param {Boolean} event.preventDefaultAction
  3427. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3428. * @param {Object} event.userData
  3429. * Arbitrary user-defined object.
  3430. */
  3431. dblClickHandler: function () { },
  3432. /**
  3433. * Implement or assign implementation to these handlers during or after
  3434. * calling the constructor.
  3435. * @function
  3436. * @param {Object} event
  3437. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3438. * A reference to the tracker instance.
  3439. * @param {String} event.pointerType
  3440. * "mouse", "touch", "pen", etc.
  3441. * @param {OpenSeadragon.Point} event.position
  3442. * The position of the event relative to the tracked element.
  3443. * @param {Number} event.buttons
  3444. * Current buttons pressed.
  3445. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  3446. * @param {OpenSeadragon.Point} event.delta
  3447. * The x,y components of the difference between the current position and the last drag event position. Useful for ignoring or weighting the events.
  3448. * @param {Number} event.speed
  3449. * Current computed speed, in pixels per second.
  3450. * @param {Number} event.direction
  3451. * Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
  3452. * @param {Boolean} event.shift
  3453. * True if the shift key was pressed during this event.
  3454. * @param {Boolean} event.isTouchEvent
  3455. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
  3456. * @param {Object} event.originalEvent
  3457. * The original event object.
  3458. * @param {Boolean} event.preventDefaultAction
  3459. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3460. * @param {Object} event.userData
  3461. * Arbitrary user-defined object.
  3462. */
  3463. dragHandler: function () { },
  3464. /**
  3465. * Implement or assign implementation to these handlers during or after
  3466. * calling the constructor.
  3467. * @function
  3468. * @param {Object} event
  3469. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3470. * A reference to the tracker instance.
  3471. * @param {String} event.pointerType
  3472. * "mouse", "touch", "pen", etc.
  3473. * @param {OpenSeadragon.Point} event.position
  3474. * The position of the event relative to the tracked element.
  3475. * @param {Number} event.speed
  3476. * Speed at the end of a drag gesture, in pixels per second.
  3477. * @param {Number} event.direction
  3478. * Direction at the end of a drag gesture, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
  3479. * @param {Boolean} event.shift
  3480. * True if the shift key was pressed during this event.
  3481. * @param {Boolean} event.isTouchEvent
  3482. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
  3483. * @param {Object} event.originalEvent
  3484. * The original event object.
  3485. * @param {Boolean} event.preventDefaultAction
  3486. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3487. * @param {Object} event.userData
  3488. * Arbitrary user-defined object.
  3489. */
  3490. dragEndHandler: function () { },
  3491. /**
  3492. * Implement or assign implementation to these handlers during or after
  3493. * calling the constructor.
  3494. * @function
  3495. * @param {Object} event
  3496. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3497. * A reference to the tracker instance.
  3498. * @param {String} event.pointerType
  3499. * "mouse", "touch", "pen", etc.
  3500. * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} event.gesturePoints
  3501. * Gesture points associated with the gesture. Velocity data can be found here.
  3502. * @param {OpenSeadragon.Point} event.lastCenter
  3503. * The previous center point of the two pinch contact points relative to the tracked element.
  3504. * @param {OpenSeadragon.Point} event.center
  3505. * The center point of the two pinch contact points relative to the tracked element.
  3506. * @param {Number} event.lastDistance
  3507. * The previous distance between the two pinch contact points in CSS pixels.
  3508. * @param {Number} event.distance
  3509. * The distance between the two pinch contact points in CSS pixels.
  3510. * @param {Boolean} event.shift
  3511. * True if the shift key was pressed during this event.
  3512. * @param {Object} event.originalEvent
  3513. * The original event object.
  3514. * @param {Boolean} event.preventDefaultAction
  3515. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3516. * @param {Object} event.userData
  3517. * Arbitrary user-defined object.
  3518. */
  3519. pinchHandler: function () { },
  3520. /**
  3521. * Implement or assign implementation to these handlers during or after
  3522. * calling the constructor.
  3523. * @function
  3524. * @param {Object} event
  3525. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3526. * A reference to the tracker instance.
  3527. * @param {String} event.pointerType
  3528. * "mouse", "touch", "pen", etc.
  3529. * @param {OpenSeadragon.Point} event.position
  3530. * The position of the event relative to the tracked element.
  3531. * @param {Number} event.buttons
  3532. * Current buttons pressed.
  3533. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  3534. * @param {Boolean} event.isTouchEvent
  3535. * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
  3536. * @param {Object} event.originalEvent
  3537. * The original event object.
  3538. * @param {Boolean} event.preventDefaultAction
  3539. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3540. * @param {Object} event.userData
  3541. * Arbitrary user-defined object.
  3542. */
  3543. stopHandler: function () { },
  3544. /**
  3545. * Implement or assign implementation to these handlers during or after
  3546. * calling the constructor.
  3547. * @function
  3548. * @param {Object} event
  3549. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3550. * A reference to the tracker instance.
  3551. * @param {Number} event.keyCode
  3552. * The key code that was pressed.
  3553. * @param {Boolean} event.ctrl
  3554. * True if the ctrl key was pressed during this event.
  3555. * @param {Boolean} event.shift
  3556. * True if the shift key was pressed during this event.
  3557. * @param {Boolean} event.alt
  3558. * True if the alt key was pressed during this event.
  3559. * @param {Boolean} event.meta
  3560. * True if the meta key was pressed during this event.
  3561. * @param {Object} event.originalEvent
  3562. * The original event object.
  3563. * @param {Boolean} event.preventDefaultAction
  3564. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3565. * @param {Object} event.userData
  3566. * Arbitrary user-defined object.
  3567. */
  3568. keyDownHandler: function () { },
  3569. /**
  3570. * Implement or assign implementation to these handlers during or after
  3571. * calling the constructor.
  3572. * @function
  3573. * @param {Object} event
  3574. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3575. * A reference to the tracker instance.
  3576. * @param {Number} event.keyCode
  3577. * The key code that was pressed.
  3578. * @param {Boolean} event.ctrl
  3579. * True if the ctrl key was pressed during this event.
  3580. * @param {Boolean} event.shift
  3581. * True if the shift key was pressed during this event.
  3582. * @param {Boolean} event.alt
  3583. * True if the alt key was pressed during this event.
  3584. * @param {Boolean} event.meta
  3585. * True if the meta key was pressed during this event.
  3586. * @param {Object} event.originalEvent
  3587. * The original event object.
  3588. * @param {Boolean} event.preventDefaultAction
  3589. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3590. * @param {Object} event.userData
  3591. * Arbitrary user-defined object.
  3592. */
  3593. keyUpHandler: function () { },
  3594. /**
  3595. * Implement or assign implementation to these handlers during or after
  3596. * calling the constructor.
  3597. * @function
  3598. * @param {Object} event
  3599. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3600. * A reference to the tracker instance.
  3601. * @param {Number} event.keyCode
  3602. * The key code that was pressed.
  3603. * @param {Boolean} event.ctrl
  3604. * True if the ctrl key was pressed during this event.
  3605. * @param {Boolean} event.shift
  3606. * True if the shift key was pressed during this event.
  3607. * @param {Boolean} event.alt
  3608. * True if the alt key was pressed during this event.
  3609. * @param {Boolean} event.meta
  3610. * True if the meta key was pressed during this event.
  3611. * @param {Object} event.originalEvent
  3612. * The original event object.
  3613. * @param {Boolean} event.preventDefaultAction
  3614. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3615. * @param {Object} event.userData
  3616. * Arbitrary user-defined object.
  3617. */
  3618. keyHandler: function () { },
  3619. /**
  3620. * Implement or assign implementation to these handlers during or after
  3621. * calling the constructor.
  3622. * @function
  3623. * @param {Object} event
  3624. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3625. * A reference to the tracker instance.
  3626. * @param {Object} event.originalEvent
  3627. * The original event object.
  3628. * @param {Boolean} event.preventDefaultAction
  3629. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3630. * @param {Object} event.userData
  3631. * Arbitrary user-defined object.
  3632. */
  3633. focusHandler: function () { },
  3634. /**
  3635. * Implement or assign implementation to these handlers during or after
  3636. * calling the constructor.
  3637. * @function
  3638. * @param {Object} event
  3639. * @param {OpenSeadragon.MouseTracker} event.eventSource
  3640. * A reference to the tracker instance.
  3641. * @param {Object} event.originalEvent
  3642. * The original event object.
  3643. * @param {Boolean} event.preventDefaultAction
  3644. * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
  3645. * @param {Object} event.userData
  3646. * Arbitrary user-defined object.
  3647. */
  3648. blurHandler: function () { }
  3649. };
  3650. /**
  3651. * Resets all active mousetrakers. (Added to patch issue #697 "Mouse up outside map will cause "canvas-drag" event to stick")
  3652. *
  3653. * @private
  3654. * @member resetAllMouseTrackers
  3655. * @memberof OpenSeadragon.MouseTracker
  3656. */
  3657. $.MouseTracker.resetAllMouseTrackers = function(){
  3658. for(var i = 0; i < MOUSETRACKERS.length; i++){
  3659. if (MOUSETRACKERS[i].isTracking()){
  3660. MOUSETRACKERS[i].setTracking(false);
  3661. MOUSETRACKERS[i].setTracking(true);
  3662. }
  3663. }
  3664. };
  3665. /**
  3666. * Provides continuous computation of velocity (speed and direction) of active pointers.
  3667. * This is a singleton, used by all MouseTracker instances, as it is unlikely there will ever be more than
  3668. * two active gesture pointers at a time.
  3669. *
  3670. * @private
  3671. * @member gesturePointVelocityTracker
  3672. * @memberof OpenSeadragon.MouseTracker
  3673. */
  3674. $.MouseTracker.gesturePointVelocityTracker = (function () {
  3675. var trackerPoints = [],
  3676. intervalId = 0,
  3677. lastTime = 0;
  3678. // Generates a unique identifier for a tracked gesture point
  3679. var _generateGuid = function ( tracker, gPoint ) {
  3680. return tracker.hash.toString() + gPoint.type + gPoint.id.toString();
  3681. };
  3682. // Interval timer callback. Computes velocity for all tracked gesture points.
  3683. var _doTracking = function () {
  3684. var i,
  3685. len = trackerPoints.length,
  3686. trackPoint,
  3687. gPoint,
  3688. now = $.now(),
  3689. elapsedTime,
  3690. distance,
  3691. speed;
  3692. elapsedTime = now - lastTime;
  3693. lastTime = now;
  3694. for ( i = 0; i < len; i++ ) {
  3695. trackPoint = trackerPoints[ i ];
  3696. gPoint = trackPoint.gPoint;
  3697. // Math.atan2 gives us just what we need for a velocity vector, as we can simply
  3698. // use cos()/sin() to extract the x/y velocity components.
  3699. gPoint.direction = Math.atan2( gPoint.currentPos.y - trackPoint.lastPos.y, gPoint.currentPos.x - trackPoint.lastPos.x );
  3700. // speed = distance / elapsed time
  3701. distance = trackPoint.lastPos.distanceTo( gPoint.currentPos );
  3702. trackPoint.lastPos = gPoint.currentPos;
  3703. speed = 1000 * distance / ( elapsedTime + 1 );
  3704. // Simple biased average, favors the most recent speed computation. Smooths out erratic gestures a bit.
  3705. gPoint.speed = 0.75 * speed + 0.25 * gPoint.speed;
  3706. }
  3707. };
  3708. // Public. Add a gesture point to be tracked
  3709. var addPoint = function ( tracker, gPoint ) {
  3710. var guid = _generateGuid( tracker, gPoint );
  3711. trackerPoints.push(
  3712. {
  3713. guid: guid,
  3714. gPoint: gPoint,
  3715. lastPos: gPoint.currentPos
  3716. } );
  3717. // Only fire up the interval timer when there's gesture pointers to track
  3718. if ( trackerPoints.length === 1 ) {
  3719. lastTime = $.now();
  3720. intervalId = window.setInterval( _doTracking, 50 );
  3721. }
  3722. };
  3723. // Public. Stop tracking a gesture point
  3724. var removePoint = function ( tracker, gPoint ) {
  3725. var guid = _generateGuid( tracker, gPoint ),
  3726. i,
  3727. len = trackerPoints.length;
  3728. for ( i = 0; i < len; i++ ) {
  3729. if ( trackerPoints[ i ].guid === guid ) {
  3730. trackerPoints.splice( i, 1 );
  3731. // Only run the interval timer if theres gesture pointers to track
  3732. len--;
  3733. if ( len === 0 ) {
  3734. window.clearInterval( intervalId );
  3735. }
  3736. break;
  3737. }
  3738. }
  3739. };
  3740. return {
  3741. addPoint: addPoint,
  3742. removePoint: removePoint
  3743. };
  3744. } )();
  3745. ///////////////////////////////////////////////////////////////////////////////
  3746. // Pointer event model and feature detection
  3747. ///////////////////////////////////////////////////////////////////////////////
  3748. $.MouseTracker.captureElement = document;
  3749. /**
  3750. * Detect available mouse wheel event name.
  3751. */
  3752. $.MouseTracker.wheelEventName = ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version > 8 ) ||
  3753. ( 'onwheel' in document.createElement( 'div' ) ) ? 'wheel' : // Modern browsers support 'wheel'
  3754. document.onmousewheel !== undefined ? 'mousewheel' : // Webkit and IE support at least 'mousewheel'
  3755. 'DOMMouseScroll'; // Assume old Firefox
  3756. /**
  3757. * Detect legacy mouse capture support.
  3758. */
  3759. $.MouseTracker.supportsMouseCapture = (function () {
  3760. var divElement = document.createElement( 'div' );
  3761. return $.isFunction( divElement.setCapture ) && $.isFunction( divElement.releaseCapture );
  3762. }());
  3763. /**
  3764. * Detect browser pointer device event model(s) and build appropriate list of events to subscribe to.
  3765. */
  3766. $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keydown", "keyup", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ];
  3767. if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
  3768. // Older Firefox
  3769. $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" );
  3770. }
  3771. // Note: window.navigator.pointerEnable is deprecated on IE 11 and not part of W3C spec.
  3772. if ( window.PointerEvent && ( window.navigator.pointerEnabled || $.Browser.vendor !== $.BROWSERS.IE ) ) {
  3773. // IE11 and other W3C Pointer Event implementations (see http://www.w3.org/TR/pointerevents)
  3774. $.MouseTracker.havePointerEvents = true;
  3775. $.MouseTracker.subscribeEvents.push( "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel" );
  3776. $.MouseTracker.unprefixedPointerEvents = true;
  3777. if( navigator.maxTouchPoints ) {
  3778. $.MouseTracker.maxTouchPoints = navigator.maxTouchPoints;
  3779. } else {
  3780. $.MouseTracker.maxTouchPoints = 0;
  3781. }
  3782. $.MouseTracker.haveMouseEnter = false;
  3783. } else if ( window.MSPointerEvent && window.navigator.msPointerEnabled ) {
  3784. // IE10
  3785. $.MouseTracker.havePointerEvents = true;
  3786. $.MouseTracker.subscribeEvents.push( "MSPointerOver", "MSPointerOut", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" );
  3787. $.MouseTracker.unprefixedPointerEvents = false;
  3788. if( navigator.msMaxTouchPoints ) {
  3789. $.MouseTracker.maxTouchPoints = navigator.msMaxTouchPoints;
  3790. } else {
  3791. $.MouseTracker.maxTouchPoints = 0;
  3792. }
  3793. $.MouseTracker.haveMouseEnter = false;
  3794. } else {
  3795. // Legacy W3C mouse events
  3796. $.MouseTracker.havePointerEvents = false;
  3797. if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
  3798. $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" );
  3799. $.MouseTracker.haveMouseEnter = true;
  3800. } else {
  3801. $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" );
  3802. $.MouseTracker.haveMouseEnter = false;
  3803. }
  3804. $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" );
  3805. if ( 'ontouchstart' in window ) {
  3806. // iOS, Android, and other W3c Touch Event implementations
  3807. // (see http://www.w3.org/TR/touch-events/)
  3808. // (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
  3809. // (see https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
  3810. $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" );
  3811. }
  3812. if ( 'ongesturestart' in window ) {
  3813. // iOS (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
  3814. // Subscribe to these to prevent default gesture handling
  3815. $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" );
  3816. }
  3817. $.MouseTracker.mousePointerId = "legacy-mouse";
  3818. $.MouseTracker.maxTouchPoints = 10;
  3819. }
  3820. ///////////////////////////////////////////////////////////////////////////////
  3821. // Classes and typedefs
  3822. ///////////////////////////////////////////////////////////////////////////////
  3823. /**
  3824. * Represents a point of contact on the screen made by a mouse cursor, pen, touch, or other pointer device.
  3825. *
  3826. * @typedef {Object} GesturePoint
  3827. * @memberof OpenSeadragon.MouseTracker
  3828. *
  3829. * @property {Number} id
  3830. * Identifier unique from all other active GesturePoints for a given pointer device.
  3831. * @property {String} type
  3832. * The pointer device type: "mouse", "touch", "pen", etc.
  3833. * @property {Boolean} captured
  3834. * True if events for the gesture point are captured to the tracked element.
  3835. * @property {Boolean} isPrimary
  3836. * True if the gesture point is a master pointer amongst the set of active pointers for each pointer type. True for mouse and primary (first) touch/pen pointers.
  3837. * @property {Boolean} insideElementPressed
  3838. * True if button pressed or contact point initiated inside the screen area of the tracked element.
  3839. * @property {Boolean} insideElement
  3840. * True if pointer or contact point is currently inside the bounds of the tracked element.
  3841. * @property {Number} speed
  3842. * Current computed speed, in pixels per second.
  3843. * @property {Number} direction
  3844. * Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
  3845. * @property {OpenSeadragon.Point} contactPos
  3846. * The initial pointer contact position, relative to the page including any scrolling. Only valid if the pointer has contact (pressed, touch contact, pen contact).
  3847. * @property {Number} contactTime
  3848. * The initial pointer contact time, in milliseconds. Only valid if the pointer has contact (pressed, touch contact, pen contact).
  3849. * @property {OpenSeadragon.Point} lastPos
  3850. * The last pointer position, relative to the page including any scrolling.
  3851. * @property {Number} lastTime
  3852. * The last pointer contact time, in milliseconds.
  3853. * @property {OpenSeadragon.Point} currentPos
  3854. * The current pointer position, relative to the page including any scrolling.
  3855. * @property {Number} currentTime
  3856. * The current pointer contact time, in milliseconds.
  3857. */
  3858. /**
  3859. * @class GesturePointList
  3860. * @classdesc Provides an abstraction for a set of active {@link OpenSeadragon.MouseTracker.GesturePoint|GesturePoint} objects for a given pointer device type.
  3861. * Active pointers are any pointer being tracked for this element which are in the hit-test area
  3862. * of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
  3863. * @memberof OpenSeadragon.MouseTracker
  3864. * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
  3865. */
  3866. $.MouseTracker.GesturePointList = function ( type ) {
  3867. this._gPoints = [];
  3868. /**
  3869. * The pointer device type: "mouse", "touch", "pen", etc.
  3870. * @member {String} type
  3871. * @memberof OpenSeadragon.MouseTracker.GesturePointList#
  3872. */
  3873. this.type = type;
  3874. /**
  3875. * Current buttons pressed for the device.
  3876. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  3877. * @member {Number} buttons
  3878. * @memberof OpenSeadragon.MouseTracker.GesturePointList#
  3879. */
  3880. this.buttons = 0;
  3881. /**
  3882. * Current number of contact points (touch points, mouse down, etc.) for the device.
  3883. * @member {Number} contacts
  3884. * @memberof OpenSeadragon.MouseTracker.GesturePointList#
  3885. */
  3886. this.contacts = 0;
  3887. /**
  3888. * Current number of clicks for the device. Used for multiple click gesture tracking.
  3889. * @member {Number} clicks
  3890. * @memberof OpenSeadragon.MouseTracker.GesturePointList#
  3891. */
  3892. this.clicks = 0;
  3893. /**
  3894. * Current number of captured pointers for the device.
  3895. * @member {Number} captureCount
  3896. * @memberof OpenSeadragon.MouseTracker.GesturePointList#
  3897. */
  3898. this.captureCount = 0;
  3899. };
  3900. /** @lends OpenSeadragon.MouseTracker.GesturePointList.prototype */
  3901. $.MouseTracker.GesturePointList.prototype = {
  3902. /**
  3903. * @function
  3904. * @returns {Number} Number of gesture points in the list.
  3905. */
  3906. getLength: function () {
  3907. return this._gPoints.length;
  3908. },
  3909. /**
  3910. * @function
  3911. * @returns {Array.<OpenSeadragon.MouseTracker.GesturePoint>} The list of gesture points in the list as an array (read-only).
  3912. */
  3913. asArray: function () {
  3914. return this._gPoints;
  3915. },
  3916. /**
  3917. * @function
  3918. * @param {OpenSeadragon.MouseTracker.GesturePoint} gesturePoint - A gesture point to add to the list.
  3919. * @returns {Number} Number of gesture points in the list.
  3920. */
  3921. add: function ( gp ) {
  3922. return this._gPoints.push( gp );
  3923. },
  3924. /**
  3925. * @function
  3926. * @param {Number} id - The id of the gesture point to remove from the list.
  3927. * @returns {Number} Number of gesture points in the list.
  3928. */
  3929. removeById: function ( id ) {
  3930. var i,
  3931. len = this._gPoints.length;
  3932. for ( i = 0; i < len; i++ ) {
  3933. if ( this._gPoints[ i ].id === id ) {
  3934. this._gPoints.splice( i, 1 );
  3935. break;
  3936. }
  3937. }
  3938. return this._gPoints.length;
  3939. },
  3940. /**
  3941. * @function
  3942. * @param {Number} index - The index of the gesture point to retrieve from the list.
  3943. * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The gesture point at the given index, or null if not found.
  3944. */
  3945. getByIndex: function ( index ) {
  3946. if ( index < this._gPoints.length) {
  3947. return this._gPoints[ index ];
  3948. }
  3949. return null;
  3950. },
  3951. /**
  3952. * @function
  3953. * @param {Number} id - The id of the gesture point to retrieve from the list.
  3954. * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The gesture point with the given id, or null if not found.
  3955. */
  3956. getById: function ( id ) {
  3957. var i,
  3958. len = this._gPoints.length;
  3959. for ( i = 0; i < len; i++ ) {
  3960. if ( this._gPoints[ i ].id === id ) {
  3961. return this._gPoints[ i ];
  3962. }
  3963. }
  3964. return null;
  3965. },
  3966. /**
  3967. * @function
  3968. * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The primary gesture point in the list, or null if not found.
  3969. */
  3970. getPrimary: function ( id ) {
  3971. var i,
  3972. len = this._gPoints.length;
  3973. for ( i = 0; i < len; i++ ) {
  3974. if ( this._gPoints[ i ].isPrimary ) {
  3975. return this._gPoints[ i ];
  3976. }
  3977. }
  3978. return null;
  3979. },
  3980. /**
  3981. * Increment this pointer's contact count.
  3982. * It will evaluate whether this pointer type is allowed to have multiple contacts.
  3983. * @function
  3984. */
  3985. addContact: function() {
  3986. ++this.contacts;
  3987. if (this.contacts > 1 && (this.type === "mouse" || this.type === "pen")) {
  3988. this.contacts = 1;
  3989. }
  3990. },
  3991. /**
  3992. * Decrement this pointer's contact count.
  3993. * It will make sure the count does not go below 0.
  3994. * @function
  3995. */
  3996. removeContact: function() {
  3997. --this.contacts;
  3998. if (this.contacts < 0) {
  3999. this.contacts = 0;
  4000. }
  4001. }
  4002. };
  4003. ///////////////////////////////////////////////////////////////////////////////
  4004. // Utility functions
  4005. ///////////////////////////////////////////////////////////////////////////////
  4006. /**
  4007. * Removes all tracked pointers.
  4008. * @private
  4009. * @inner
  4010. */
  4011. function clearTrackedPointers( tracker ) {
  4012. var delegate = THIS[ tracker.hash ],
  4013. i,
  4014. pointerListCount = delegate.activePointersLists.length;
  4015. for ( i = 0; i < pointerListCount; i++ ) {
  4016. if ( delegate.activePointersLists[ i ].captureCount > 0 ) {
  4017. $.removeEvent(
  4018. $.MouseTracker.captureElement,
  4019. 'mousemove',
  4020. delegate.mousemovecaptured,
  4021. true
  4022. );
  4023. $.removeEvent(
  4024. $.MouseTracker.captureElement,
  4025. 'mouseup',
  4026. delegate.mouseupcaptured,
  4027. true
  4028. );
  4029. $.removeEvent(
  4030. $.MouseTracker.captureElement,
  4031. $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove',
  4032. delegate.pointermovecaptured,
  4033. true
  4034. );
  4035. $.removeEvent(
  4036. $.MouseTracker.captureElement,
  4037. $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp',
  4038. delegate.pointerupcaptured,
  4039. true
  4040. );
  4041. $.removeEvent(
  4042. $.MouseTracker.captureElement,
  4043. 'touchmove',
  4044. delegate.touchmovecaptured,
  4045. true
  4046. );
  4047. $.removeEvent(
  4048. $.MouseTracker.captureElement,
  4049. 'touchend',
  4050. delegate.touchendcaptured,
  4051. true
  4052. );
  4053. delegate.activePointersLists[ i ].captureCount = 0;
  4054. }
  4055. }
  4056. for ( i = 0; i < pointerListCount; i++ ) {
  4057. delegate.activePointersLists.pop();
  4058. }
  4059. }
  4060. /**
  4061. * Starts tracking pointer events on the tracked element.
  4062. * @private
  4063. * @inner
  4064. */
  4065. function startTracking( tracker ) {
  4066. var delegate = THIS[ tracker.hash ],
  4067. event,
  4068. i;
  4069. if ( !delegate.tracking ) {
  4070. for ( i = 0; i < $.MouseTracker.subscribeEvents.length; i++ ) {
  4071. event = $.MouseTracker.subscribeEvents[ i ];
  4072. $.addEvent(
  4073. tracker.element,
  4074. event,
  4075. delegate[ event ],
  4076. false
  4077. );
  4078. }
  4079. clearTrackedPointers( tracker );
  4080. delegate.tracking = true;
  4081. }
  4082. }
  4083. /**
  4084. * Stops tracking pointer events on the tracked element.
  4085. * @private
  4086. * @inner
  4087. */
  4088. function stopTracking( tracker ) {
  4089. var delegate = THIS[ tracker.hash ],
  4090. event,
  4091. i;
  4092. if ( delegate.tracking ) {
  4093. for ( i = 0; i < $.MouseTracker.subscribeEvents.length; i++ ) {
  4094. event = $.MouseTracker.subscribeEvents[ i ];
  4095. $.removeEvent(
  4096. tracker.element,
  4097. event,
  4098. delegate[ event ],
  4099. false
  4100. );
  4101. }
  4102. clearTrackedPointers( tracker );
  4103. delegate.tracking = false;
  4104. }
  4105. }
  4106. /**
  4107. * @private
  4108. * @inner
  4109. */
  4110. function getCaptureEventParams( tracker, pointerType ) {
  4111. var delegate = THIS[ tracker.hash ];
  4112. if ( pointerType === 'pointerevent' ) {
  4113. return {
  4114. upName: $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp',
  4115. upHandler: delegate.pointerupcaptured,
  4116. moveName: $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove',
  4117. moveHandler: delegate.pointermovecaptured
  4118. };
  4119. } else if ( pointerType === 'mouse' ) {
  4120. return {
  4121. upName: 'mouseup',
  4122. upHandler: delegate.mouseupcaptured,
  4123. moveName: 'mousemove',
  4124. moveHandler: delegate.mousemovecaptured
  4125. };
  4126. } else if ( pointerType === 'touch' ) {
  4127. return {
  4128. upName: 'touchend',
  4129. upHandler: delegate.touchendcaptured,
  4130. moveName: 'touchmove',
  4131. moveHandler: delegate.touchmovecaptured
  4132. };
  4133. } else {
  4134. throw new Error( "MouseTracker.getCaptureEventParams: Unknown pointer type." );
  4135. }
  4136. }
  4137. /**
  4138. * Begin capturing pointer events to the tracked element.
  4139. * @private
  4140. * @inner
  4141. */
  4142. function capturePointer( tracker, pointerType, pointerCount ) {
  4143. var pointsList = tracker.getActivePointersListByType( pointerType ),
  4144. eventParams;
  4145. pointsList.captureCount += (pointerCount || 1);
  4146. if ( pointsList.captureCount === 1 ) {
  4147. if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
  4148. tracker.element.setCapture( true );
  4149. } else {
  4150. eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
  4151. // We emulate mouse capture by hanging listeners on the document object.
  4152. // (Note we listen on the capture phase so the captured handlers will get called first)
  4153. // eslint-disable-next-line no-use-before-define
  4154. if (isInIframe && canAccessEvents(window.top)) {
  4155. $.addEvent(
  4156. window.top,
  4157. eventParams.upName,
  4158. eventParams.upHandler,
  4159. true
  4160. );
  4161. }
  4162. $.addEvent(
  4163. $.MouseTracker.captureElement,
  4164. eventParams.upName,
  4165. eventParams.upHandler,
  4166. true
  4167. );
  4168. $.addEvent(
  4169. $.MouseTracker.captureElement,
  4170. eventParams.moveName,
  4171. eventParams.moveHandler,
  4172. true
  4173. );
  4174. }
  4175. }
  4176. }
  4177. /**
  4178. * Stop capturing pointer events to the tracked element.
  4179. * @private
  4180. * @inner
  4181. */
  4182. function releasePointer( tracker, pointerType, pointerCount ) {
  4183. var pointsList = tracker.getActivePointersListByType( pointerType ),
  4184. eventParams;
  4185. pointsList.captureCount -= (pointerCount || 1);
  4186. if ( pointsList.captureCount === 0 ) {
  4187. if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
  4188. tracker.element.releaseCapture();
  4189. } else {
  4190. eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
  4191. // We emulate mouse capture by hanging listeners on the document object.
  4192. // (Note we listen on the capture phase so the captured handlers will get called first)
  4193. // eslint-disable-next-line no-use-before-define
  4194. if (isInIframe && canAccessEvents(window.top)) {
  4195. $.removeEvent(
  4196. window.top,
  4197. eventParams.upName,
  4198. eventParams.upHandler,
  4199. true
  4200. );
  4201. }
  4202. $.removeEvent(
  4203. $.MouseTracker.captureElement,
  4204. eventParams.moveName,
  4205. eventParams.moveHandler,
  4206. true
  4207. );
  4208. $.removeEvent(
  4209. $.MouseTracker.captureElement,
  4210. eventParams.upName,
  4211. eventParams.upHandler,
  4212. true
  4213. );
  4214. }
  4215. }
  4216. }
  4217. /**
  4218. * Gets a W3C Pointer Events model compatible pointer type string from a DOM pointer event.
  4219. * IE10 used a long integer value, but the W3C specification (and IE11+) use a string "mouse", "touch", "pen", etc.
  4220. * @private
  4221. * @inner
  4222. */
  4223. function getPointerType( event ) {
  4224. var pointerTypeStr;
  4225. if ( $.MouseTracker.unprefixedPointerEvents ) {
  4226. pointerTypeStr = event.pointerType;
  4227. } else {
  4228. // IE10
  4229. // MSPOINTER_TYPE_TOUCH: 0x00000002
  4230. // MSPOINTER_TYPE_PEN: 0x00000003
  4231. // MSPOINTER_TYPE_MOUSE: 0x00000004
  4232. switch( event.pointerType )
  4233. {
  4234. case 0x00000002:
  4235. pointerTypeStr = 'touch';
  4236. break;
  4237. case 0x00000003:
  4238. pointerTypeStr = 'pen';
  4239. break;
  4240. case 0x00000004:
  4241. pointerTypeStr = 'mouse';
  4242. break;
  4243. default:
  4244. pointerTypeStr = '';
  4245. }
  4246. }
  4247. return pointerTypeStr;
  4248. }
  4249. /**
  4250. * @private
  4251. * @inner
  4252. */
  4253. function getMouseAbsolute( event ) {
  4254. return $.getMousePosition( event );
  4255. }
  4256. /**
  4257. * @private
  4258. * @inner
  4259. */
  4260. function getMouseRelative( event, element ) {
  4261. return getPointRelativeToAbsolute( getMouseAbsolute( event ), element );
  4262. }
  4263. /**
  4264. * @private
  4265. * @inner
  4266. */
  4267. function getPointRelativeToAbsolute( point, element ) {
  4268. var offset = $.getElementOffset( element );
  4269. return point.minus( offset );
  4270. }
  4271. /**
  4272. * @private
  4273. * @inner
  4274. */
  4275. function getCenterPoint( point1, point2 ) {
  4276. return new $.Point( ( point1.x + point2.x ) / 2, ( point1.y + point2.y ) / 2 );
  4277. }
  4278. ///////////////////////////////////////////////////////////////////////////////
  4279. // Device-specific DOM event handlers
  4280. ///////////////////////////////////////////////////////////////////////////////
  4281. /**
  4282. * @private
  4283. * @inner
  4284. */
  4285. function onClick( tracker, event ) {
  4286. if ( tracker.clickHandler ) {
  4287. $.cancelEvent( event );
  4288. }
  4289. }
  4290. /**
  4291. * @private
  4292. * @inner
  4293. */
  4294. function onDblClick( tracker, event ) {
  4295. if ( tracker.dblClickHandler ) {
  4296. $.cancelEvent( event );
  4297. }
  4298. }
  4299. /**
  4300. * @private
  4301. * @inner
  4302. */
  4303. function onKeyDown( tracker, event ) {
  4304. //$.console.log( "keydown %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
  4305. var propagate;
  4306. if ( tracker.keyDownHandler ) {
  4307. event = $.getEvent( event );
  4308. propagate = tracker.keyDownHandler(
  4309. {
  4310. eventSource: tracker,
  4311. keyCode: event.keyCode ? event.keyCode : event.charCode,
  4312. ctrl: event.ctrlKey,
  4313. shift: event.shiftKey,
  4314. alt: event.altKey,
  4315. meta: event.metaKey,
  4316. originalEvent: event,
  4317. preventDefaultAction: false,
  4318. userData: tracker.userData
  4319. }
  4320. );
  4321. if ( !propagate ) {
  4322. $.cancelEvent( event );
  4323. }
  4324. }
  4325. }
  4326. /**
  4327. * @private
  4328. * @inner
  4329. */
  4330. function onKeyUp( tracker, event ) {
  4331. //$.console.log( "keyup %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
  4332. var propagate;
  4333. if ( tracker.keyUpHandler ) {
  4334. event = $.getEvent( event );
  4335. propagate = tracker.keyUpHandler(
  4336. {
  4337. eventSource: tracker,
  4338. keyCode: event.keyCode ? event.keyCode : event.charCode,
  4339. ctrl: event.ctrlKey,
  4340. shift: event.shiftKey,
  4341. alt: event.altKey,
  4342. meta: event.metaKey,
  4343. originalEvent: event,
  4344. preventDefaultAction: false,
  4345. userData: tracker.userData
  4346. }
  4347. );
  4348. if ( !propagate ) {
  4349. $.cancelEvent( event );
  4350. }
  4351. }
  4352. }
  4353. /**
  4354. * @private
  4355. * @inner
  4356. */
  4357. function onKeyPress( tracker, event ) {
  4358. //$.console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
  4359. var propagate;
  4360. if ( tracker.keyHandler ) {
  4361. event = $.getEvent( event );
  4362. propagate = tracker.keyHandler(
  4363. {
  4364. eventSource: tracker,
  4365. keyCode: event.keyCode ? event.keyCode : event.charCode,
  4366. ctrl: event.ctrlKey,
  4367. shift: event.shiftKey,
  4368. alt: event.altKey,
  4369. meta: event.metaKey,
  4370. originalEvent: event,
  4371. preventDefaultAction: false,
  4372. userData: tracker.userData
  4373. }
  4374. );
  4375. if ( !propagate ) {
  4376. $.cancelEvent( event );
  4377. }
  4378. }
  4379. }
  4380. /**
  4381. * @private
  4382. * @inner
  4383. */
  4384. function onFocus( tracker, event ) {
  4385. //console.log( "focus %s", event );
  4386. var propagate;
  4387. if ( tracker.focusHandler ) {
  4388. event = $.getEvent( event );
  4389. propagate = tracker.focusHandler(
  4390. {
  4391. eventSource: tracker,
  4392. originalEvent: event,
  4393. preventDefaultAction: false,
  4394. userData: tracker.userData
  4395. }
  4396. );
  4397. if ( propagate === false ) {
  4398. $.cancelEvent( event );
  4399. }
  4400. }
  4401. }
  4402. /**
  4403. * @private
  4404. * @inner
  4405. */
  4406. function onBlur( tracker, event ) {
  4407. //console.log( "blur %s", event );
  4408. var propagate;
  4409. if ( tracker.blurHandler ) {
  4410. event = $.getEvent( event );
  4411. propagate = tracker.blurHandler(
  4412. {
  4413. eventSource: tracker,
  4414. originalEvent: event,
  4415. preventDefaultAction: false,
  4416. userData: tracker.userData
  4417. }
  4418. );
  4419. if ( propagate === false ) {
  4420. $.cancelEvent( event );
  4421. }
  4422. }
  4423. }
  4424. /**
  4425. * Handler for 'wheel' events
  4426. *
  4427. * @private
  4428. * @inner
  4429. */
  4430. function onWheel( tracker, event ) {
  4431. handleWheelEvent( tracker, event, event );
  4432. }
  4433. /**
  4434. * Handler for 'mousewheel', 'DOMMouseScroll', and 'MozMousePixelScroll' events
  4435. *
  4436. * @private
  4437. * @inner
  4438. */
  4439. function onMouseWheel( tracker, event ) {
  4440. event = $.getEvent( event );
  4441. // Simulate a 'wheel' event
  4442. var simulatedEvent = {
  4443. target: event.target || event.srcElement,
  4444. type: "wheel",
  4445. shiftKey: event.shiftKey || false,
  4446. clientX: event.clientX,
  4447. clientY: event.clientY,
  4448. pageX: event.pageX ? event.pageX : event.clientX,
  4449. pageY: event.pageY ? event.pageY : event.clientY,
  4450. deltaMode: event.type == "MozMousePixelScroll" ? 0 : 1, // 0=pixel, 1=line, 2=page
  4451. deltaX: 0,
  4452. deltaZ: 0
  4453. };
  4454. // Calculate deltaY
  4455. if ( $.MouseTracker.wheelEventName == "mousewheel" ) {
  4456. simulatedEvent.deltaY = -event.wheelDelta / $.DEFAULT_SETTINGS.pixelsPerWheelLine;
  4457. } else {
  4458. simulatedEvent.deltaY = event.detail;
  4459. }
  4460. handleWheelEvent( tracker, simulatedEvent, event );
  4461. }
  4462. /**
  4463. * Handles 'wheel' events.
  4464. * The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()).
  4465. *
  4466. * @private
  4467. * @inner
  4468. */
  4469. function handleWheelEvent( tracker, event, originalEvent ) {
  4470. var nDelta = 0,
  4471. propagate;
  4472. // The nDelta variable is gated to provide smooth z-index scrolling
  4473. // since the mouse wheel allows for substantial deltas meant for rapid
  4474. // y-index scrolling.
  4475. // event.deltaMode: 0=pixel, 1=line, 2=page
  4476. // TODO: Deltas in pixel mode should be accumulated then a scroll value computed after $.DEFAULT_SETTINGS.pixelsPerWheelLine threshold reached
  4477. nDelta = event.deltaY < 0 ? 1 : -1;
  4478. if ( tracker.scrollHandler ) {
  4479. propagate = tracker.scrollHandler(
  4480. {
  4481. eventSource: tracker,
  4482. pointerType: 'mouse',
  4483. position: getMouseRelative( event, tracker.element ),
  4484. scroll: nDelta,
  4485. shift: event.shiftKey,
  4486. isTouchEvent: false,
  4487. originalEvent: originalEvent,
  4488. preventDefaultAction: false,
  4489. userData: tracker.userData
  4490. }
  4491. );
  4492. if ( propagate === false ) {
  4493. $.cancelEvent( originalEvent );
  4494. }
  4495. }
  4496. }
  4497. /**
  4498. * @private
  4499. * @inner
  4500. */
  4501. function isParentChild( parent, child )
  4502. {
  4503. if ( parent === child ) {
  4504. return false;
  4505. }
  4506. while ( child && child !== parent ) {
  4507. child = child.parentNode;
  4508. }
  4509. return child === parent;
  4510. }
  4511. /**
  4512. * Only used on IE 8
  4513. *
  4514. * @private
  4515. * @inner
  4516. */
  4517. function onMouseEnter( tracker, event ) {
  4518. event = $.getEvent( event );
  4519. handleMouseEnter( tracker, event );
  4520. }
  4521. /**
  4522. * @private
  4523. * @inner
  4524. */
  4525. function onMouseOver( tracker, event ) {
  4526. event = $.getEvent( event );
  4527. if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
  4528. return;
  4529. }
  4530. handleMouseEnter( tracker, event );
  4531. }
  4532. /**
  4533. * @private
  4534. * @inner
  4535. */
  4536. function handleMouseEnter( tracker, event ) {
  4537. var gPoint = {
  4538. id: $.MouseTracker.mousePointerId,
  4539. type: 'mouse',
  4540. isPrimary: true,
  4541. currentPos: getMouseAbsolute( event ),
  4542. currentTime: $.now()
  4543. };
  4544. updatePointersEnter( tracker, event, [ gPoint ] );
  4545. }
  4546. /**
  4547. * Only used on IE 8
  4548. *
  4549. * @private
  4550. * @inner
  4551. */
  4552. function onMouseLeave( tracker, event ) {
  4553. event = $.getEvent( event );
  4554. handleMouseExit( tracker, event );
  4555. }
  4556. /**
  4557. * @private
  4558. * @inner
  4559. */
  4560. function onMouseOut( tracker, event ) {
  4561. event = $.getEvent( event );
  4562. if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
  4563. return;
  4564. }
  4565. handleMouseExit( tracker, event );
  4566. }
  4567. /**
  4568. * @private
  4569. * @inner
  4570. */
  4571. function handleMouseExit( tracker, event ) {
  4572. var gPoint = {
  4573. id: $.MouseTracker.mousePointerId,
  4574. type: 'mouse',
  4575. isPrimary: true,
  4576. currentPos: getMouseAbsolute( event ),
  4577. currentTime: $.now()
  4578. };
  4579. updatePointersExit( tracker, event, [ gPoint ] );
  4580. }
  4581. /**
  4582. * Returns a W3C DOM level 3 standard button value given an event.button property:
  4583. * -1 == none, 0 == primary/left, 1 == middle, 2 == secondary/right, 3 == X1/back, 4 == X2/forward, 5 == eraser (pen)
  4584. * @private
  4585. * @inner
  4586. */
  4587. function getStandardizedButton( button ) {
  4588. if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
  4589. // On IE 8, 0 == none, 1 == left, 2 == right, 3 == left and right, 4 == middle, 5 == left and middle, 6 == right and middle, 7 == all three
  4590. // TODO: Support chorded (multiple) button presses on IE 8?
  4591. if ( button === 1 ) {
  4592. return 0;
  4593. } else if ( button === 2 ) {
  4594. return 2;
  4595. } else if ( button === 4 ) {
  4596. return 1;
  4597. } else {
  4598. return -1;
  4599. }
  4600. } else {
  4601. return button;
  4602. }
  4603. }
  4604. /**
  4605. * @private
  4606. * @inner
  4607. */
  4608. function onMouseDown( tracker, event ) {
  4609. var gPoint;
  4610. event = $.getEvent( event );
  4611. gPoint = {
  4612. id: $.MouseTracker.mousePointerId,
  4613. type: 'mouse',
  4614. isPrimary: true,
  4615. currentPos: getMouseAbsolute( event ),
  4616. currentTime: $.now()
  4617. };
  4618. if ( updatePointersDown( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) {
  4619. $.stopEvent( event );
  4620. capturePointer( tracker, 'mouse' );
  4621. }
  4622. if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) {
  4623. $.cancelEvent( event );
  4624. }
  4625. }
  4626. /**
  4627. * @private
  4628. * @inner
  4629. */
  4630. function onMouseUp( tracker, event ) {
  4631. handleMouseUp( tracker, event );
  4632. }
  4633. /**
  4634. * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
  4635. * onMouseUp is still attached to the tracked element, so stop propagation to avoid processing twice.
  4636. *
  4637. * @private
  4638. * @inner
  4639. */
  4640. function onMouseUpCaptured( tracker, event ) {
  4641. handleMouseUp( tracker, event );
  4642. $.stopEvent( event );
  4643. }
  4644. /**
  4645. * @private
  4646. * @inner
  4647. */
  4648. function handleMouseUp( tracker, event ) {
  4649. var gPoint;
  4650. event = $.getEvent( event );
  4651. gPoint = {
  4652. id: $.MouseTracker.mousePointerId,
  4653. type: 'mouse',
  4654. isPrimary: true,
  4655. currentPos: getMouseAbsolute( event ),
  4656. currentTime: $.now()
  4657. };
  4658. if ( updatePointersUp( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) {
  4659. releasePointer( tracker, 'mouse' );
  4660. }
  4661. }
  4662. /**
  4663. * @private
  4664. * @inner
  4665. */
  4666. function onMouseMove( tracker, event ) {
  4667. handleMouseMove( tracker, event );
  4668. }
  4669. /**
  4670. * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
  4671. * onMouseMove is still attached to the tracked element, so stop propagation to avoid processing twice.
  4672. *
  4673. * @private
  4674. * @inner
  4675. */
  4676. function onMouseMoveCaptured( tracker, event ) {
  4677. handleMouseMove( tracker, event );
  4678. $.stopEvent( event );
  4679. }
  4680. /**
  4681. * @private
  4682. * @inner
  4683. */
  4684. function handleMouseMove( tracker, event ) {
  4685. var gPoint;
  4686. event = $.getEvent( event );
  4687. gPoint = {
  4688. id: $.MouseTracker.mousePointerId,
  4689. type: 'mouse',
  4690. isPrimary: true,
  4691. currentPos: getMouseAbsolute( event ),
  4692. currentTime: $.now()
  4693. };
  4694. updatePointersMove( tracker, event, [ gPoint ] );
  4695. }
  4696. /**
  4697. * @private
  4698. * @inner
  4699. */
  4700. function abortContacts( tracker, event, pointsList ) {
  4701. var i,
  4702. gPointCount = pointsList.getLength(),
  4703. abortGPoints = [];
  4704. // Check contact count for hoverable pointer types before aborting
  4705. if (pointsList.type === 'touch' || pointsList.contacts > 0) {
  4706. for ( i = 0; i < gPointCount; i++ ) {
  4707. abortGPoints.push( pointsList.getByIndex( i ) );
  4708. }
  4709. if ( abortGPoints.length > 0 ) {
  4710. // simulate touchend/mouseup
  4711. updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact
  4712. // release pointer capture
  4713. pointsList.captureCount = 1;
  4714. releasePointer( tracker, pointsList.type );
  4715. // simulate touchleave/mouseout
  4716. updatePointersExit( tracker, event, abortGPoints );
  4717. }
  4718. }
  4719. }
  4720. /**
  4721. * @private
  4722. * @inner
  4723. */
  4724. function onTouchStart( tracker, event ) {
  4725. var time,
  4726. i,
  4727. j,
  4728. touchCount = event.changedTouches.length,
  4729. gPoints = [],
  4730. parentGPoints,
  4731. pointsList = tracker.getActivePointersListByType( 'touch' );
  4732. time = $.now();
  4733. if ( pointsList.getLength() > event.touches.length - touchCount ) {
  4734. $.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.');
  4735. abortContacts( tracker, event, pointsList );
  4736. }
  4737. for ( i = 0; i < touchCount; i++ ) {
  4738. gPoints.push( {
  4739. id: event.changedTouches[ i ].identifier,
  4740. type: 'touch',
  4741. // isPrimary not set - let the updatePointers functions determine it
  4742. currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
  4743. currentTime: time
  4744. } );
  4745. }
  4746. // simulate touchenter on our tracked element
  4747. updatePointersEnter( tracker, event, gPoints );
  4748. // simulate touchenter on our tracked element's tracked ancestor elements
  4749. for ( i = 0; i < MOUSETRACKERS.length; i++ ) {
  4750. if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) {
  4751. parentGPoints = [];
  4752. for ( j = 0; j < touchCount; j++ ) {
  4753. parentGPoints.push( {
  4754. id: event.changedTouches[ j ].identifier,
  4755. type: 'touch',
  4756. // isPrimary not set - let the updatePointers functions determine it
  4757. currentPos: getMouseAbsolute( event.changedTouches[ j ] ),
  4758. currentTime: time
  4759. } );
  4760. }
  4761. updatePointersEnter( MOUSETRACKERS[ i ], event, parentGPoints );
  4762. }
  4763. }
  4764. if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact
  4765. $.stopEvent( event );
  4766. capturePointer( tracker, 'touch', touchCount );
  4767. }
  4768. $.cancelEvent( event );
  4769. }
  4770. /**
  4771. * @private
  4772. * @inner
  4773. */
  4774. function onTouchEnd( tracker, event ) {
  4775. handleTouchEnd( tracker, event );
  4776. }
  4777. /**
  4778. * This handler is attached to the window object (on the capture phase) to emulate pointer capture.
  4779. * onTouchEnd is still attached to the tracked element, so stop propagation to avoid processing twice.
  4780. *
  4781. * @private
  4782. * @inner
  4783. */
  4784. function onTouchEndCaptured( tracker, event ) {
  4785. handleTouchEnd( tracker, event );
  4786. $.stopEvent( event );
  4787. }
  4788. /**
  4789. * @private
  4790. * @inner
  4791. */
  4792. function handleTouchEnd( tracker, event ) {
  4793. var time,
  4794. i,
  4795. j,
  4796. touchCount = event.changedTouches.length,
  4797. gPoints = [],
  4798. parentGPoints;
  4799. time = $.now();
  4800. for ( i = 0; i < touchCount; i++ ) {
  4801. gPoints.push( {
  4802. id: event.changedTouches[ i ].identifier,
  4803. type: 'touch',
  4804. // isPrimary not set - let the updatePointers functions determine it
  4805. currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
  4806. currentTime: time
  4807. } );
  4808. }
  4809. if ( updatePointersUp( tracker, event, gPoints, 0 ) ) {
  4810. releasePointer( tracker, 'touch', touchCount );
  4811. }
  4812. // simulate touchleave on our tracked element
  4813. updatePointersExit( tracker, event, gPoints );
  4814. // simulate touchleave on our tracked element's tracked ancestor elements
  4815. for ( i = 0; i < MOUSETRACKERS.length; i++ ) {
  4816. if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) {
  4817. parentGPoints = [];
  4818. for ( j = 0; j < touchCount; j++ ) {
  4819. parentGPoints.push( {
  4820. id: event.changedTouches[ j ].identifier,
  4821. type: 'touch',
  4822. // isPrimary not set - let the updatePointers functions determine it
  4823. currentPos: getMouseAbsolute( event.changedTouches[ j ] ),
  4824. currentTime: time
  4825. } );
  4826. }
  4827. updatePointersExit( MOUSETRACKERS[ i ], event, parentGPoints );
  4828. }
  4829. }
  4830. $.cancelEvent( event );
  4831. }
  4832. /**
  4833. * @private
  4834. * @inner
  4835. */
  4836. function onTouchMove( tracker, event ) {
  4837. handleTouchMove( tracker, event );
  4838. }
  4839. /**
  4840. * This handler is attached to the window object (on the capture phase) to emulate pointer capture.
  4841. * onTouchMove is still attached to the tracked element, so stop propagation to avoid processing twice.
  4842. *
  4843. * @private
  4844. * @inner
  4845. */
  4846. function onTouchMoveCaptured( tracker, event ) {
  4847. handleTouchMove( tracker, event );
  4848. $.stopEvent( event );
  4849. }
  4850. /**
  4851. * @private
  4852. * @inner
  4853. */
  4854. function handleTouchMove( tracker, event ) {
  4855. var i,
  4856. touchCount = event.changedTouches.length,
  4857. gPoints = [];
  4858. for ( i = 0; i < touchCount; i++ ) {
  4859. gPoints.push( {
  4860. id: event.changedTouches[ i ].identifier,
  4861. type: 'touch',
  4862. // isPrimary not set - let the updatePointers functions determine it
  4863. currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
  4864. currentTime: $.now()
  4865. } );
  4866. }
  4867. updatePointersMove( tracker, event, gPoints );
  4868. $.cancelEvent( event );
  4869. }
  4870. /**
  4871. * @private
  4872. * @inner
  4873. */
  4874. function onTouchCancel( tracker, event ) {
  4875. var pointsList = tracker.getActivePointersListByType('touch');
  4876. abortContacts( tracker, event, pointsList );
  4877. }
  4878. /**
  4879. * @private
  4880. * @inner
  4881. */
  4882. function onGestureStart( tracker, event ) {
  4883. event.stopPropagation();
  4884. event.preventDefault();
  4885. return false;
  4886. }
  4887. /**
  4888. * @private
  4889. * @inner
  4890. */
  4891. function onGestureChange( tracker, event ) {
  4892. event.stopPropagation();
  4893. event.preventDefault();
  4894. return false;
  4895. }
  4896. /**
  4897. * @private
  4898. * @inner
  4899. */
  4900. function onPointerOver( tracker, event ) {
  4901. var gPoint;
  4902. if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
  4903. return;
  4904. }
  4905. gPoint = {
  4906. id: event.pointerId,
  4907. type: getPointerType( event ),
  4908. isPrimary: event.isPrimary,
  4909. currentPos: getMouseAbsolute( event ),
  4910. currentTime: $.now()
  4911. };
  4912. updatePointersEnter( tracker, event, [ gPoint ] );
  4913. }
  4914. /**
  4915. * @private
  4916. * @inner
  4917. */
  4918. function onPointerOut( tracker, event ) {
  4919. var gPoint;
  4920. if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
  4921. return;
  4922. }
  4923. gPoint = {
  4924. id: event.pointerId,
  4925. type: getPointerType( event ),
  4926. isPrimary: event.isPrimary,
  4927. currentPos: getMouseAbsolute( event ),
  4928. currentTime: $.now()
  4929. };
  4930. updatePointersExit( tracker, event, [ gPoint ] );
  4931. }
  4932. /**
  4933. * @private
  4934. * @inner
  4935. */
  4936. function onPointerDown( tracker, event ) {
  4937. var gPoint;
  4938. gPoint = {
  4939. id: event.pointerId,
  4940. type: getPointerType( event ),
  4941. isPrimary: event.isPrimary,
  4942. currentPos: getMouseAbsolute( event ),
  4943. currentTime: $.now()
  4944. };
  4945. if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) {
  4946. $.stopEvent( event );
  4947. capturePointer( tracker, gPoint.type );
  4948. }
  4949. if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
  4950. $.cancelEvent( event );
  4951. }
  4952. }
  4953. /**
  4954. * @private
  4955. * @inner
  4956. */
  4957. function onPointerUp( tracker, event ) {
  4958. handlePointerUp( tracker, event );
  4959. }
  4960. /**
  4961. * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
  4962. * onPointerUp is still attached to the tracked element, so stop propagation to avoid processing twice.
  4963. *
  4964. * @private
  4965. * @inner
  4966. */
  4967. function onPointerUpCaptured( tracker, event ) {
  4968. var pointsList = tracker.getActivePointersListByType( getPointerType( event ) );
  4969. if ( pointsList.getById( event.pointerId ) ) {
  4970. handlePointerUp( tracker, event );
  4971. }
  4972. $.stopEvent( event );
  4973. }
  4974. /**
  4975. * @private
  4976. * @inner
  4977. */
  4978. function handlePointerUp( tracker, event ) {
  4979. var gPoint;
  4980. gPoint = {
  4981. id: event.pointerId,
  4982. type: getPointerType( event ),
  4983. isPrimary: event.isPrimary,
  4984. currentPos: getMouseAbsolute( event ),
  4985. currentTime: $.now()
  4986. };
  4987. if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) {
  4988. releasePointer( tracker, gPoint.type );
  4989. }
  4990. }
  4991. /**
  4992. * @private
  4993. * @inner
  4994. */
  4995. function onPointerMove( tracker, event ) {
  4996. handlePointerMove( tracker, event );
  4997. }
  4998. /**
  4999. * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
  5000. * onPointerMove is still attached to the tracked element, so stop propagation to avoid processing twice.
  5001. *
  5002. * @private
  5003. * @inner
  5004. */
  5005. function onPointerMoveCaptured( tracker, event ) {
  5006. var pointsList = tracker.getActivePointersListByType( getPointerType( event ) );
  5007. if ( pointsList.getById( event.pointerId ) ) {
  5008. handlePointerMove( tracker, event );
  5009. }
  5010. $.stopEvent( event );
  5011. }
  5012. /**
  5013. * @private
  5014. * @inner
  5015. */
  5016. function handlePointerMove( tracker, event ) {
  5017. // Pointer changed coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height)
  5018. var gPoint;
  5019. gPoint = {
  5020. id: event.pointerId,
  5021. type: getPointerType( event ),
  5022. isPrimary: event.isPrimary,
  5023. currentPos: getMouseAbsolute( event ),
  5024. currentTime: $.now()
  5025. };
  5026. updatePointersMove( tracker, event, [ gPoint ] );
  5027. }
  5028. /**
  5029. * @private
  5030. * @inner
  5031. */
  5032. function onPointerCancel( tracker, event ) {
  5033. var gPoint;
  5034. gPoint = {
  5035. id: event.pointerId,
  5036. type: getPointerType( event )
  5037. };
  5038. updatePointersCancel( tracker, event, [ gPoint ] );
  5039. }
  5040. ///////////////////////////////////////////////////////////////////////////////
  5041. // Device-agnostic DOM event handlers
  5042. ///////////////////////////////////////////////////////////////////////////////
  5043. /**
  5044. * @function
  5045. * @private
  5046. * @inner
  5047. * @param {OpenSeadragon.MouseTracker.GesturePointList} pointsList
  5048. * The GesturePointList to track the pointer in.
  5049. * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint
  5050. * Gesture point to track.
  5051. * @returns {Number} Number of gesture points in pointsList.
  5052. */
  5053. function startTrackingPointer( pointsList, gPoint ) {
  5054. // If isPrimary is not known for the pointer then set it according to our rules:
  5055. // true if the first pointer in the gesture, otherwise false
  5056. if ( !gPoint.hasOwnProperty( 'isPrimary' ) ) {
  5057. if ( pointsList.getLength() === 0 ) {
  5058. gPoint.isPrimary = true;
  5059. } else {
  5060. gPoint.isPrimary = false;
  5061. }
  5062. }
  5063. gPoint.speed = 0;
  5064. gPoint.direction = 0;
  5065. gPoint.contactPos = gPoint.currentPos;
  5066. gPoint.contactTime = gPoint.currentTime;
  5067. gPoint.lastPos = gPoint.currentPos;
  5068. gPoint.lastTime = gPoint.currentTime;
  5069. return pointsList.add( gPoint );
  5070. }
  5071. /**
  5072. * @function
  5073. * @private
  5074. * @inner
  5075. * @param {OpenSeadragon.MouseTracker.GesturePointList} pointsList
  5076. * The GesturePointList to stop tracking the pointer on.
  5077. * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint
  5078. * Gesture point to stop tracking.
  5079. * @returns {Number} Number of gesture points in pointsList.
  5080. */
  5081. function stopTrackingPointer( pointsList, gPoint ) {
  5082. var listLength,
  5083. primaryPoint;
  5084. if ( pointsList.getById( gPoint.id ) ) {
  5085. listLength = pointsList.removeById( gPoint.id );
  5086. // If isPrimary is not known for the pointer and we just removed the primary pointer from the list then we need to set another pointer as primary
  5087. if ( !gPoint.hasOwnProperty( 'isPrimary' ) ) {
  5088. primaryPoint = pointsList.getPrimary();
  5089. if ( !primaryPoint ) {
  5090. primaryPoint = pointsList.getByIndex( 0 );
  5091. if ( primaryPoint ) {
  5092. primaryPoint.isPrimary = true;
  5093. }
  5094. }
  5095. }
  5096. } else {
  5097. listLength = pointsList.getLength();
  5098. }
  5099. return listLength;
  5100. }
  5101. /**
  5102. * @function
  5103. * @private
  5104. * @inner
  5105. * @param {OpenSeadragon.MouseTracker} tracker
  5106. * A reference to the MouseTracker instance.
  5107. * @param {Object} event
  5108. * A reference to the originating DOM event.
  5109. * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
  5110. * Gesture points associated with the event.
  5111. */
  5112. function updatePointersEnter( tracker, event, gPoints ) {
  5113. var pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
  5114. i,
  5115. gPointCount = gPoints.length,
  5116. curGPoint,
  5117. updateGPoint,
  5118. propagate;
  5119. for ( i = 0; i < gPointCount; i++ ) {
  5120. curGPoint = gPoints[ i ];
  5121. updateGPoint = pointsList.getById( curGPoint.id );
  5122. if ( updateGPoint ) {
  5123. // Already tracking the pointer...update it
  5124. updateGPoint.insideElement = true;
  5125. updateGPoint.lastPos = updateGPoint.currentPos;
  5126. updateGPoint.lastTime = updateGPoint.currentTime;
  5127. updateGPoint.currentPos = curGPoint.currentPos;
  5128. updateGPoint.currentTime = curGPoint.currentTime;
  5129. curGPoint = updateGPoint;
  5130. } else {
  5131. // Initialize for tracking and add to the tracking list
  5132. curGPoint.captured = false;
  5133. curGPoint.insideElementPressed = false;
  5134. curGPoint.insideElement = true;
  5135. startTrackingPointer( pointsList, curGPoint );
  5136. }
  5137. // Enter
  5138. if ( tracker.enterHandler ) {
  5139. propagate = tracker.enterHandler(
  5140. {
  5141. eventSource: tracker,
  5142. pointerType: curGPoint.type,
  5143. position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
  5144. buttons: pointsList.buttons,
  5145. pointers: tracker.getActivePointerCount(),
  5146. insideElementPressed: curGPoint.insideElementPressed,
  5147. buttonDownAny: pointsList.buttons !== 0,
  5148. isTouchEvent: curGPoint.type === 'touch',
  5149. originalEvent: event,
  5150. preventDefaultAction: false,
  5151. userData: tracker.userData
  5152. }
  5153. );
  5154. if ( propagate === false ) {
  5155. $.cancelEvent( event );
  5156. }
  5157. }
  5158. }
  5159. }
  5160. /**
  5161. * @function
  5162. * @private
  5163. * @inner
  5164. * @param {OpenSeadragon.MouseTracker} tracker
  5165. * A reference to the MouseTracker instance.
  5166. * @param {Object} event
  5167. * A reference to the originating DOM event.
  5168. * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
  5169. * Gesture points associated with the event.
  5170. */
  5171. function updatePointersExit( tracker, event, gPoints ) {
  5172. var pointsList = tracker.getActivePointersListByType(gPoints[0].type),
  5173. i,
  5174. gPointCount = gPoints.length,
  5175. curGPoint,
  5176. updateGPoint,
  5177. propagate;
  5178. for ( i = 0; i < gPointCount; i++ ) {
  5179. curGPoint = gPoints[ i ];
  5180. updateGPoint = pointsList.getById( curGPoint.id );
  5181. if ( updateGPoint ) {
  5182. // Already tracking the pointer. If captured then update it, else stop tracking it
  5183. if ( updateGPoint.captured ) {
  5184. updateGPoint.insideElement = false;
  5185. updateGPoint.lastPos = updateGPoint.currentPos;
  5186. updateGPoint.lastTime = updateGPoint.currentTime;
  5187. updateGPoint.currentPos = curGPoint.currentPos;
  5188. updateGPoint.currentTime = curGPoint.currentTime;
  5189. } else {
  5190. stopTrackingPointer( pointsList, updateGPoint );
  5191. }
  5192. curGPoint = updateGPoint;
  5193. }
  5194. // Exit
  5195. if ( tracker.exitHandler ) {
  5196. propagate = tracker.exitHandler(
  5197. {
  5198. eventSource: tracker,
  5199. pointerType: curGPoint.type,
  5200. position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
  5201. buttons: pointsList.buttons,
  5202. pointers: tracker.getActivePointerCount(),
  5203. insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false,
  5204. buttonDownAny: pointsList.buttons !== 0,
  5205. isTouchEvent: curGPoint.type === 'touch',
  5206. originalEvent: event,
  5207. preventDefaultAction: false,
  5208. userData: tracker.userData
  5209. }
  5210. );
  5211. if ( propagate === false ) {
  5212. $.cancelEvent( event );
  5213. }
  5214. }
  5215. }
  5216. }
  5217. /**
  5218. * @function
  5219. * @private
  5220. * @inner
  5221. * @param {OpenSeadragon.MouseTracker} tracker
  5222. * A reference to the MouseTracker instance.
  5223. * @param {Object} event
  5224. * A reference to the originating DOM event.
  5225. * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
  5226. * Gesture points associated with the event.
  5227. * @param {Number} buttonChanged
  5228. * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
  5229. * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
  5230. * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
  5231. *
  5232. * @returns {Boolean} True if pointers should be captured to the tracked element, otherwise false.
  5233. */
  5234. function updatePointersDown( tracker, event, gPoints, buttonChanged ) {
  5235. var delegate = THIS[ tracker.hash ],
  5236. propagate,
  5237. pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
  5238. i,
  5239. gPointCount = gPoints.length,
  5240. curGPoint,
  5241. updateGPoint;
  5242. if ( typeof event.buttons !== 'undefined' ) {
  5243. pointsList.buttons = event.buttons;
  5244. } else {
  5245. if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
  5246. if ( buttonChanged === 0 ) {
  5247. // Primary
  5248. pointsList.buttons += 1;
  5249. } else if ( buttonChanged === 1 ) {
  5250. // Aux
  5251. pointsList.buttons += 4;
  5252. } else if ( buttonChanged === 2 ) {
  5253. // Secondary
  5254. pointsList.buttons += 2;
  5255. } else if ( buttonChanged === 3 ) {
  5256. // X1 (Back)
  5257. pointsList.buttons += 8;
  5258. } else if ( buttonChanged === 4 ) {
  5259. // X2 (Forward)
  5260. pointsList.buttons += 16;
  5261. } else if ( buttonChanged === 5 ) {
  5262. // Pen Eraser
  5263. pointsList.buttons += 32;
  5264. }
  5265. } else {
  5266. if ( buttonChanged === 0 ) {
  5267. // Primary
  5268. pointsList.buttons |= 1;
  5269. } else if ( buttonChanged === 1 ) {
  5270. // Aux
  5271. pointsList.buttons |= 4;
  5272. } else if ( buttonChanged === 2 ) {
  5273. // Secondary
  5274. pointsList.buttons |= 2;
  5275. } else if ( buttonChanged === 3 ) {
  5276. // X1 (Back)
  5277. pointsList.buttons |= 8;
  5278. } else if ( buttonChanged === 4 ) {
  5279. // X2 (Forward)
  5280. pointsList.buttons |= 16;
  5281. } else if ( buttonChanged === 5 ) {
  5282. // Pen Eraser
  5283. pointsList.buttons |= 32;
  5284. }
  5285. }
  5286. }
  5287. // Some pointers may steal control from another pointer without firing the appropriate release events
  5288. // e.g. Touching a screen while click-dragging with certain mice.
  5289. var otherPointsLists = tracker.getActivePointersListsExceptType(gPoints[ 0 ].type);
  5290. for (i = 0; i < otherPointsLists.length; i++) {
  5291. //If another pointer has contact, simulate the release
  5292. abortContacts(tracker, event, otherPointsLists[i]); // No-op if no active pointer
  5293. }
  5294. // Only capture and track primary button, pen, and touch contacts
  5295. if ( buttonChanged !== 0 ) {
  5296. // Aux Press
  5297. if ( tracker.nonPrimaryPressHandler ) {
  5298. propagate = tracker.nonPrimaryPressHandler(
  5299. {
  5300. eventSource: tracker,
  5301. pointerType: gPoints[ 0 ].type,
  5302. position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ),
  5303. button: buttonChanged,
  5304. buttons: pointsList.buttons,
  5305. isTouchEvent: gPoints[ 0 ].type === 'touch',
  5306. originalEvent: event,
  5307. preventDefaultAction: false,
  5308. userData: tracker.userData
  5309. }
  5310. );
  5311. if ( propagate === false ) {
  5312. $.cancelEvent( event );
  5313. }
  5314. }
  5315. return false;
  5316. }
  5317. for ( i = 0; i < gPointCount; i++ ) {
  5318. curGPoint = gPoints[ i ];
  5319. updateGPoint = pointsList.getById( curGPoint.id );
  5320. if ( updateGPoint ) {
  5321. // Already tracking the pointer...update it
  5322. updateGPoint.captured = true;
  5323. updateGPoint.insideElementPressed = true;
  5324. updateGPoint.insideElement = true;
  5325. updateGPoint.contactPos = curGPoint.currentPos;
  5326. updateGPoint.contactTime = curGPoint.currentTime;
  5327. updateGPoint.lastPos = updateGPoint.currentPos;
  5328. updateGPoint.lastTime = updateGPoint.currentTime;
  5329. updateGPoint.currentPos = curGPoint.currentPos;
  5330. updateGPoint.currentTime = curGPoint.currentTime;
  5331. curGPoint = updateGPoint;
  5332. } else {
  5333. // Initialize for tracking and add to the tracking list (no pointerover or pointermove event occurred before this)
  5334. curGPoint.captured = true;
  5335. curGPoint.insideElementPressed = true;
  5336. curGPoint.insideElement = true;
  5337. startTrackingPointer( pointsList, curGPoint );
  5338. }
  5339. pointsList.addContact();
  5340. //$.console.log('contacts++ ', pointsList.contacts);
  5341. if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
  5342. $.MouseTracker.gesturePointVelocityTracker.addPoint( tracker, curGPoint );
  5343. }
  5344. if ( pointsList.contacts === 1 ) {
  5345. // Press
  5346. if ( tracker.pressHandler ) {
  5347. propagate = tracker.pressHandler(
  5348. {
  5349. eventSource: tracker,
  5350. pointerType: curGPoint.type,
  5351. position: getPointRelativeToAbsolute( curGPoint.contactPos, tracker.element ),
  5352. buttons: pointsList.buttons,
  5353. isTouchEvent: curGPoint.type === 'touch',
  5354. originalEvent: event,
  5355. preventDefaultAction: false,
  5356. userData: tracker.userData
  5357. }
  5358. );
  5359. if ( propagate === false ) {
  5360. $.cancelEvent( event );
  5361. }
  5362. }
  5363. } else if ( pointsList.contacts === 2 ) {
  5364. if ( tracker.pinchHandler && curGPoint.type === 'touch' ) {
  5365. // Initialize for pinch
  5366. delegate.pinchGPoints = pointsList.asArray();
  5367. delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos );
  5368. delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos );
  5369. }
  5370. }
  5371. }
  5372. return true;
  5373. }
  5374. /**
  5375. * @function
  5376. * @private
  5377. * @inner
  5378. * @param {OpenSeadragon.MouseTracker} tracker
  5379. * A reference to the MouseTracker instance.
  5380. * @param {Object} event
  5381. * A reference to the originating DOM event.
  5382. * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
  5383. * Gesture points associated with the event.
  5384. * @param {Number} buttonChanged
  5385. * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
  5386. * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
  5387. * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
  5388. *
  5389. * @returns {Boolean} True if pointer capture should be released from the tracked element, otherwise false.
  5390. */
  5391. function updatePointersUp( tracker, event, gPoints, buttonChanged ) {
  5392. var delegate = THIS[ tracker.hash ],
  5393. pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
  5394. propagate,
  5395. releasePoint,
  5396. releaseTime,
  5397. i,
  5398. gPointCount = gPoints.length,
  5399. curGPoint,
  5400. updateGPoint,
  5401. releaseCapture = false,
  5402. wasCaptured = false,
  5403. quick;
  5404. if ( typeof event.buttons !== 'undefined' ) {
  5405. pointsList.buttons = event.buttons;
  5406. } else {
  5407. if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
  5408. if ( buttonChanged === 0 ) {
  5409. // Primary
  5410. pointsList.buttons -= 1;
  5411. } else if ( buttonChanged === 1 ) {
  5412. // Aux
  5413. pointsList.buttons -= 4;
  5414. } else if ( buttonChanged === 2 ) {
  5415. // Secondary
  5416. pointsList.buttons -= 2;
  5417. } else if ( buttonChanged === 3 ) {
  5418. // X1 (Back)
  5419. pointsList.buttons -= 8;
  5420. } else if ( buttonChanged === 4 ) {
  5421. // X2 (Forward)
  5422. pointsList.buttons -= 16;
  5423. } else if ( buttonChanged === 5 ) {
  5424. // Pen Eraser
  5425. pointsList.buttons -= 32;
  5426. }
  5427. } else {
  5428. if ( buttonChanged === 0 ) {
  5429. // Primary
  5430. pointsList.buttons ^= ~1;
  5431. } else if ( buttonChanged === 1 ) {
  5432. // Aux
  5433. pointsList.buttons ^= ~4;
  5434. } else if ( buttonChanged === 2 ) {
  5435. // Secondary
  5436. pointsList.buttons ^= ~2;
  5437. } else if ( buttonChanged === 3 ) {
  5438. // X1 (Back)
  5439. pointsList.buttons ^= ~8;
  5440. } else if ( buttonChanged === 4 ) {
  5441. // X2 (Forward)
  5442. pointsList.buttons ^= ~16;
  5443. } else if ( buttonChanged === 5 ) {
  5444. // Pen Eraser
  5445. pointsList.buttons ^= ~32;
  5446. }
  5447. }
  5448. }
  5449. // Only capture and track primary button, pen, and touch contacts
  5450. if ( buttonChanged !== 0 ) {
  5451. // Aux Release
  5452. if ( tracker.nonPrimaryReleaseHandler ) {
  5453. propagate = tracker.nonPrimaryReleaseHandler(
  5454. {
  5455. eventSource: tracker,
  5456. pointerType: gPoints[ 0 ].type,
  5457. position: getPointRelativeToAbsolute(gPoints[0].currentPos, tracker.element),
  5458. button: buttonChanged,
  5459. buttons: pointsList.buttons,
  5460. isTouchEvent: gPoints[ 0 ].type === 'touch',
  5461. originalEvent: event,
  5462. preventDefaultAction: false,
  5463. userData: tracker.userData
  5464. }
  5465. );
  5466. if ( propagate === false ) {
  5467. $.cancelEvent( event );
  5468. }
  5469. }
  5470. // A primary mouse button may have been released while the non-primary button was down
  5471. var otherPointsList = tracker.getActivePointersListByType("mouse");
  5472. // Stop tracking the mouse; see https://github.com/openseadragon/openseadragon/pull/1223
  5473. abortContacts(tracker, event, otherPointsList); // No-op if no active pointer
  5474. return false;
  5475. }
  5476. for ( i = 0; i < gPointCount; i++ ) {
  5477. curGPoint = gPoints[ i ];
  5478. updateGPoint = pointsList.getById( curGPoint.id );
  5479. if ( updateGPoint ) {
  5480. // Update the pointer, stop tracking it if not still in this element
  5481. if ( updateGPoint.captured ) {
  5482. updateGPoint.captured = false;
  5483. releaseCapture = true;
  5484. wasCaptured = true;
  5485. }
  5486. updateGPoint.lastPos = updateGPoint.currentPos;
  5487. updateGPoint.lastTime = updateGPoint.currentTime;
  5488. updateGPoint.currentPos = curGPoint.currentPos;
  5489. updateGPoint.currentTime = curGPoint.currentTime;
  5490. if ( !updateGPoint.insideElement ) {
  5491. stopTrackingPointer( pointsList, updateGPoint );
  5492. }
  5493. releasePoint = updateGPoint.currentPos;
  5494. releaseTime = updateGPoint.currentTime;
  5495. if ( wasCaptured ) {
  5496. // Pointer was activated in our element but could have been removed in any element since events are captured to our element
  5497. pointsList.removeContact();
  5498. //$.console.log('contacts-- ', pointsList.contacts);
  5499. if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
  5500. $.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint );
  5501. }
  5502. if ( pointsList.contacts === 0 ) {
  5503. // Release (pressed in our element)
  5504. if ( tracker.releaseHandler ) {
  5505. propagate = tracker.releaseHandler(
  5506. {
  5507. eventSource: tracker,
  5508. pointerType: updateGPoint.type,
  5509. position: getPointRelativeToAbsolute( releasePoint, tracker.element ),
  5510. buttons: pointsList.buttons,
  5511. insideElementPressed: updateGPoint.insideElementPressed,
  5512. insideElementReleased: updateGPoint.insideElement,
  5513. isTouchEvent: updateGPoint.type === 'touch',
  5514. originalEvent: event,
  5515. preventDefaultAction: false,
  5516. userData: tracker.userData
  5517. }
  5518. );
  5519. if ( propagate === false ) {
  5520. $.cancelEvent( event );
  5521. }
  5522. }
  5523. // Drag End
  5524. if ( tracker.dragEndHandler && !updateGPoint.currentPos.equals( updateGPoint.contactPos ) ) {
  5525. propagate = tracker.dragEndHandler(
  5526. {
  5527. eventSource: tracker,
  5528. pointerType: updateGPoint.type,
  5529. position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
  5530. speed: updateGPoint.speed,
  5531. direction: updateGPoint.direction,
  5532. shift: event.shiftKey,
  5533. isTouchEvent: updateGPoint.type === 'touch',
  5534. originalEvent: event,
  5535. preventDefaultAction: false,
  5536. userData: tracker.userData
  5537. }
  5538. );
  5539. if ( propagate === false ) {
  5540. $.cancelEvent( event );
  5541. }
  5542. }
  5543. // Click / Double-Click
  5544. if ( ( tracker.clickHandler || tracker.dblClickHandler ) && updateGPoint.insideElement ) {
  5545. quick = releaseTime - updateGPoint.contactTime <= tracker.clickTimeThreshold &&
  5546. updateGPoint.contactPos.distanceTo( releasePoint ) <= tracker.clickDistThreshold;
  5547. // Click
  5548. if ( tracker.clickHandler ) {
  5549. propagate = tracker.clickHandler(
  5550. {
  5551. eventSource: tracker,
  5552. pointerType: updateGPoint.type,
  5553. position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
  5554. quick: quick,
  5555. shift: event.shiftKey,
  5556. isTouchEvent: updateGPoint.type === 'touch',
  5557. originalEvent: event,
  5558. preventDefaultAction: false,
  5559. userData: tracker.userData
  5560. }
  5561. );
  5562. if ( propagate === false ) {
  5563. $.cancelEvent( event );
  5564. }
  5565. }
  5566. // Double-Click
  5567. if ( tracker.dblClickHandler && quick ) {
  5568. pointsList.clicks++;
  5569. if ( pointsList.clicks === 1 ) {
  5570. delegate.lastClickPos = releasePoint;
  5571. /*jshint loopfunc:true*/
  5572. delegate.dblClickTimeOut = setTimeout( function() {
  5573. pointsList.clicks = 0;
  5574. }, tracker.dblClickTimeThreshold );
  5575. /*jshint loopfunc:false*/
  5576. } else if ( pointsList.clicks === 2 ) {
  5577. clearTimeout( delegate.dblClickTimeOut );
  5578. pointsList.clicks = 0;
  5579. if ( delegate.lastClickPos.distanceTo( releasePoint ) <= tracker.dblClickDistThreshold ) {
  5580. propagate = tracker.dblClickHandler(
  5581. {
  5582. eventSource: tracker,
  5583. pointerType: updateGPoint.type,
  5584. position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
  5585. shift: event.shiftKey,
  5586. isTouchEvent: updateGPoint.type === 'touch',
  5587. originalEvent: event,
  5588. preventDefaultAction: false,
  5589. userData: tracker.userData
  5590. }
  5591. );
  5592. if ( propagate === false ) {
  5593. $.cancelEvent( event );
  5594. }
  5595. }
  5596. delegate.lastClickPos = null;
  5597. }
  5598. }
  5599. }
  5600. } else if ( pointsList.contacts === 2 ) {
  5601. if ( tracker.pinchHandler && updateGPoint.type === 'touch' ) {
  5602. // Reset for pinch
  5603. delegate.pinchGPoints = pointsList.asArray();
  5604. delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos );
  5605. delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos );
  5606. }
  5607. }
  5608. } else {
  5609. // Pointer was activated in another element but removed in our element
  5610. // Release (pressed in another element)
  5611. if ( tracker.releaseHandler ) {
  5612. propagate = tracker.releaseHandler(
  5613. {
  5614. eventSource: tracker,
  5615. pointerType: updateGPoint.type,
  5616. position: getPointRelativeToAbsolute( releasePoint, tracker.element ),
  5617. buttons: pointsList.buttons,
  5618. insideElementPressed: updateGPoint.insideElementPressed,
  5619. insideElementReleased: updateGPoint.insideElement,
  5620. isTouchEvent: updateGPoint.type === 'touch',
  5621. originalEvent: event,
  5622. preventDefaultAction: false,
  5623. userData: tracker.userData
  5624. }
  5625. );
  5626. if ( propagate === false ) {
  5627. $.cancelEvent( event );
  5628. }
  5629. }
  5630. }
  5631. }
  5632. }
  5633. return releaseCapture;
  5634. }
  5635. /**
  5636. * Call when pointer(s) change coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height)
  5637. *
  5638. * @function
  5639. * @private
  5640. * @inner
  5641. * @param {OpenSeadragon.MouseTracker} tracker
  5642. * A reference to the MouseTracker instance.
  5643. * @param {Object} event
  5644. * A reference to the originating DOM event.
  5645. * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
  5646. * Gesture points associated with the event.
  5647. */
  5648. function updatePointersMove( tracker, event, gPoints ) {
  5649. var delegate = THIS[ tracker.hash ],
  5650. pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
  5651. i,
  5652. gPointCount = gPoints.length,
  5653. curGPoint,
  5654. updateGPoint,
  5655. gPointArray,
  5656. delta,
  5657. propagate;
  5658. if ( typeof event.buttons !== 'undefined' ) {
  5659. pointsList.buttons = event.buttons;
  5660. }
  5661. for ( i = 0; i < gPointCount; i++ ) {
  5662. curGPoint = gPoints[ i ];
  5663. updateGPoint = pointsList.getById( curGPoint.id );
  5664. if ( updateGPoint ) {
  5665. // Already tracking the pointer...update it
  5666. if ( curGPoint.hasOwnProperty( 'isPrimary' ) ) {
  5667. updateGPoint.isPrimary = curGPoint.isPrimary;
  5668. }
  5669. updateGPoint.lastPos = updateGPoint.currentPos;
  5670. updateGPoint.lastTime = updateGPoint.currentTime;
  5671. updateGPoint.currentPos = curGPoint.currentPos;
  5672. updateGPoint.currentTime = curGPoint.currentTime;
  5673. } else {
  5674. // Initialize for tracking and add to the tracking list (no pointerover or pointerdown event occurred before this)
  5675. curGPoint.captured = false;
  5676. curGPoint.insideElementPressed = false;
  5677. curGPoint.insideElement = true;
  5678. startTrackingPointer( pointsList, curGPoint );
  5679. }
  5680. }
  5681. // Stop (mouse only)
  5682. if ( tracker.stopHandler && gPoints[ 0 ].type === 'mouse' ) {
  5683. clearTimeout( tracker.stopTimeOut );
  5684. tracker.stopTimeOut = setTimeout( function() {
  5685. handlePointerStop( tracker, event, gPoints[ 0 ].type );
  5686. }, tracker.stopDelay );
  5687. }
  5688. if ( pointsList.contacts === 0 ) {
  5689. // Move (no contacts: hovering mouse or other hover-capable device)
  5690. if ( tracker.moveHandler ) {
  5691. propagate = tracker.moveHandler(
  5692. {
  5693. eventSource: tracker,
  5694. pointerType: gPoints[ 0 ].type,
  5695. position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ),
  5696. buttons: pointsList.buttons,
  5697. isTouchEvent: gPoints[ 0 ].type === 'touch',
  5698. originalEvent: event,
  5699. preventDefaultAction: false,
  5700. userData: tracker.userData
  5701. }
  5702. );
  5703. if ( propagate === false ) {
  5704. $.cancelEvent( event );
  5705. }
  5706. }
  5707. } else if ( pointsList.contacts === 1 ) {
  5708. // Move (1 contact)
  5709. if ( tracker.moveHandler ) {
  5710. updateGPoint = pointsList.asArray()[ 0 ];
  5711. propagate = tracker.moveHandler(
  5712. {
  5713. eventSource: tracker,
  5714. pointerType: updateGPoint.type,
  5715. position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
  5716. buttons: pointsList.buttons,
  5717. isTouchEvent: updateGPoint.type === 'touch',
  5718. originalEvent: event,
  5719. preventDefaultAction: false,
  5720. userData: tracker.userData
  5721. }
  5722. );
  5723. if ( propagate === false ) {
  5724. $.cancelEvent( event );
  5725. }
  5726. }
  5727. // Drag
  5728. if ( tracker.dragHandler ) {
  5729. updateGPoint = pointsList.asArray()[ 0 ];
  5730. delta = updateGPoint.currentPos.minus( updateGPoint.lastPos );
  5731. propagate = tracker.dragHandler(
  5732. {
  5733. eventSource: tracker,
  5734. pointerType: updateGPoint.type,
  5735. position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
  5736. buttons: pointsList.buttons,
  5737. delta: delta,
  5738. speed: updateGPoint.speed,
  5739. direction: updateGPoint.direction,
  5740. shift: event.shiftKey,
  5741. isTouchEvent: updateGPoint.type === 'touch',
  5742. originalEvent: event,
  5743. preventDefaultAction: false,
  5744. userData: tracker.userData
  5745. }
  5746. );
  5747. if ( propagate === false ) {
  5748. $.cancelEvent( event );
  5749. }
  5750. }
  5751. } else if ( pointsList.contacts === 2 ) {
  5752. // Move (2 contacts, use center)
  5753. if ( tracker.moveHandler ) {
  5754. gPointArray = pointsList.asArray();
  5755. propagate = tracker.moveHandler(
  5756. {
  5757. eventSource: tracker,
  5758. pointerType: gPointArray[ 0 ].type,
  5759. position: getPointRelativeToAbsolute( getCenterPoint( gPointArray[ 0 ].currentPos, gPointArray[ 1 ].currentPos ), tracker.element ),
  5760. buttons: pointsList.buttons,
  5761. isTouchEvent: gPointArray[ 0 ].type === 'touch',
  5762. originalEvent: event,
  5763. preventDefaultAction: false,
  5764. userData: tracker.userData
  5765. }
  5766. );
  5767. if ( propagate === false ) {
  5768. $.cancelEvent( event );
  5769. }
  5770. }
  5771. // Pinch
  5772. if ( tracker.pinchHandler && gPoints[ 0 ].type === 'touch' ) {
  5773. delta = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos );
  5774. if ( delta != delegate.currentPinchDist ) {
  5775. delegate.lastPinchDist = delegate.currentPinchDist;
  5776. delegate.currentPinchDist = delta;
  5777. delegate.lastPinchCenter = delegate.currentPinchCenter;
  5778. delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos );
  5779. propagate = tracker.pinchHandler(
  5780. {
  5781. eventSource: tracker,
  5782. pointerType: 'touch',
  5783. gesturePoints: delegate.pinchGPoints,
  5784. lastCenter: getPointRelativeToAbsolute( delegate.lastPinchCenter, tracker.element ),
  5785. center: getPointRelativeToAbsolute( delegate.currentPinchCenter, tracker.element ),
  5786. lastDistance: delegate.lastPinchDist,
  5787. distance: delegate.currentPinchDist,
  5788. shift: event.shiftKey,
  5789. originalEvent: event,
  5790. preventDefaultAction: false,
  5791. userData: tracker.userData
  5792. }
  5793. );
  5794. if ( propagate === false ) {
  5795. $.cancelEvent( event );
  5796. }
  5797. }
  5798. }
  5799. }
  5800. }
  5801. /**
  5802. * @function
  5803. * @private
  5804. * @inner
  5805. * @param {OpenSeadragon.MouseTracker} tracker
  5806. * A reference to the MouseTracker instance.
  5807. * @param {Object} event
  5808. * A reference to the originating DOM event.
  5809. * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
  5810. * Gesture points associated with the event.
  5811. */
  5812. function updatePointersCancel( tracker, event, gPoints ) {
  5813. updatePointersUp( tracker, event, gPoints, 0 );
  5814. updatePointersExit( tracker, event, gPoints );
  5815. }
  5816. /**
  5817. * @private
  5818. * @inner
  5819. */
  5820. function handlePointerStop( tracker, originalMoveEvent, pointerType ) {
  5821. if ( tracker.stopHandler ) {
  5822. tracker.stopHandler( {
  5823. eventSource: tracker,
  5824. pointerType: pointerType,
  5825. position: getMouseRelative( originalMoveEvent, tracker.element ),
  5826. buttons: tracker.getActivePointersListByType( pointerType ).buttons,
  5827. isTouchEvent: pointerType === 'touch',
  5828. originalEvent: originalMoveEvent,
  5829. preventDefaultAction: false,
  5830. userData: tracker.userData
  5831. } );
  5832. }
  5833. }
  5834. /**
  5835. * True if inside an iframe, otherwise false.
  5836. * @member {Boolean} isInIframe
  5837. * @private
  5838. * @inner
  5839. */
  5840. var isInIframe = (function() {
  5841. try {
  5842. return window.self !== window.top;
  5843. } catch (e) {
  5844. return true;
  5845. }
  5846. })();
  5847. /**
  5848. * @function
  5849. * @private
  5850. * @inner
  5851. * @returns {Boolean} True if the target has access rights to events, otherwise false.
  5852. */
  5853. function canAccessEvents (target) {
  5854. try {
  5855. return target.addEventListener && target.removeEventListener;
  5856. } catch (e) {
  5857. return false;
  5858. }
  5859. }
  5860. }(OpenSeadragon));
  5861. /*
  5862. * OpenSeadragon - Control
  5863. *
  5864. * Copyright (C) 2009 CodePlex Foundation
  5865. * Copyright (C) 2010-2013 OpenSeadragon contributors
  5866. *
  5867. * Redistribution and use in source and binary forms, with or without
  5868. * modification, are permitted provided that the following conditions are
  5869. * met:
  5870. *
  5871. * - Redistributions of source code must retain the above copyright notice,
  5872. * this list of conditions and the following disclaimer.
  5873. *
  5874. * - Redistributions in binary form must reproduce the above copyright
  5875. * notice, this list of conditions and the following disclaimer in the
  5876. * documentation and/or other materials provided with the distribution.
  5877. *
  5878. * - Neither the name of CodePlex Foundation nor the names of its
  5879. * contributors may be used to endorse or promote products derived from
  5880. * this software without specific prior written permission.
  5881. *
  5882. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  5883. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5884. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  5885. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  5886. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  5887. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  5888. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  5889. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  5890. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  5891. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  5892. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  5893. */
  5894. (function( $ ){
  5895. /**
  5896. * An enumeration of supported locations where controls can be anchored.
  5897. * The anchoring is always relative to the container.
  5898. * @member ControlAnchor
  5899. * @memberof OpenSeadragon
  5900. * @static
  5901. * @type {Object}
  5902. * @property {Number} NONE
  5903. * @property {Number} TOP_LEFT
  5904. * @property {Number} TOP_RIGHT
  5905. * @property {Number} BOTTOM_LEFT
  5906. * @property {Number} BOTTOM_RIGHT
  5907. * @property {Number} ABSOLUTE
  5908. */
  5909. $.ControlAnchor = {
  5910. NONE: 0,
  5911. TOP_LEFT: 1,
  5912. TOP_RIGHT: 2,
  5913. BOTTOM_RIGHT: 3,
  5914. BOTTOM_LEFT: 4,
  5915. ABSOLUTE: 5
  5916. };
  5917. /**
  5918. * @class Control
  5919. * @classdesc A Control represents any interface element which is meant to allow the user
  5920. * to interact with the zoomable interface. Any control can be anchored to any
  5921. * element.
  5922. *
  5923. * @memberof OpenSeadragon
  5924. * @param {Element} element - the control element to be anchored in the container.
  5925. * @param {Object } options - All required and optional settings for configuring a control element.
  5926. * @param {OpenSeadragon.ControlAnchor} [options.anchor=OpenSeadragon.ControlAnchor.NONE] - the position of the control
  5927. * relative to the container.
  5928. * @param {Boolean} [options.attachToViewer=true] - Whether the control should be added directly to the viewer, or
  5929. * directly to the container
  5930. * @param {Boolean} [options.autoFade=true] - Whether the control should have the autofade behavior
  5931. * @param {Element} container - the element to control will be anchored too.
  5932. */
  5933. $.Control = function ( element, options, container ) {
  5934. var parent = element.parentNode;
  5935. if (typeof options === 'number')
  5936. {
  5937. $.console.error("Passing an anchor directly into the OpenSeadragon.Control constructor is deprecated; " +
  5938. "please use an options object instead. " +
  5939. "Support for this deprecated variant is scheduled for removal in December 2013");
  5940. options = {anchor: options};
  5941. }
  5942. options.attachToViewer = (typeof options.attachToViewer === 'undefined') ? true : options.attachToViewer;
  5943. /**
  5944. * True if the control should have autofade behavior.
  5945. * @member {Boolean} autoFade
  5946. * @memberof OpenSeadragon.Control#
  5947. */
  5948. this.autoFade = (typeof options.autoFade === 'undefined') ? true : options.autoFade;
  5949. /**
  5950. * The element providing the user interface with some type of control (e.g. a zoom-in button).
  5951. * @member {Element} element
  5952. * @memberof OpenSeadragon.Control#
  5953. */
  5954. this.element = element;
  5955. /**
  5956. * The position of the Control relative to its container.
  5957. * @member {OpenSeadragon.ControlAnchor} anchor
  5958. * @memberof OpenSeadragon.Control#
  5959. */
  5960. this.anchor = options.anchor;
  5961. /**
  5962. * The Control's containing element.
  5963. * @member {Element} container
  5964. * @memberof OpenSeadragon.Control#
  5965. */
  5966. this.container = container;
  5967. /**
  5968. * A neutral element surrounding the control element.
  5969. * @member {Element} wrapper
  5970. * @memberof OpenSeadragon.Control#
  5971. */
  5972. if ( this.anchor == $.ControlAnchor.ABSOLUTE ) {
  5973. this.wrapper = $.makeNeutralElement( "div" );
  5974. this.wrapper.style.position = "absolute";
  5975. this.wrapper.style.top = typeof (options.top) == "number" ? (options.top + 'px') : options.top;
  5976. this.wrapper.style.left = typeof (options.left) == "number" ? (options.left + 'px') : options.left;
  5977. this.wrapper.style.height = typeof (options.height) == "number" ? (options.height + 'px') : options.height;
  5978. this.wrapper.style.width = typeof (options.width) == "number" ? (options.width + 'px') : options.width;
  5979. this.wrapper.style.margin = "0px";
  5980. this.wrapper.style.padding = "0px";
  5981. this.element.style.position = "relative";
  5982. this.element.style.top = "0px";
  5983. this.element.style.left = "0px";
  5984. this.element.style.height = "100%";
  5985. this.element.style.width = "100%";
  5986. } else {
  5987. this.wrapper = $.makeNeutralElement( "div" );
  5988. this.wrapper.style.display = "inline-block";
  5989. if ( this.anchor == $.ControlAnchor.NONE ) {
  5990. // IE6 fix
  5991. this.wrapper.style.width = this.wrapper.style.height = "100%";
  5992. }
  5993. }
  5994. this.wrapper.appendChild( this.element );
  5995. if (options.attachToViewer ) {
  5996. if ( this.anchor == $.ControlAnchor.TOP_RIGHT ||
  5997. this.anchor == $.ControlAnchor.BOTTOM_RIGHT ) {
  5998. this.container.insertBefore(
  5999. this.wrapper,
  6000. this.container.firstChild
  6001. );
  6002. } else {
  6003. this.container.appendChild( this.wrapper );
  6004. }
  6005. } else {
  6006. parent.appendChild( this.wrapper );
  6007. }
  6008. };
  6009. /** @lends OpenSeadragon.Control.prototype */
  6010. $.Control.prototype = {
  6011. /**
  6012. * Removes the control from the container.
  6013. * @function
  6014. */
  6015. destroy: function() {
  6016. this.wrapper.removeChild( this.element );
  6017. this.container.removeChild( this.wrapper );
  6018. },
  6019. /**
  6020. * Determines if the control is currently visible.
  6021. * @function
  6022. * @return {Boolean} true if currently visible, false otherwise.
  6023. */
  6024. isVisible: function() {
  6025. return this.wrapper.style.display != "none";
  6026. },
  6027. /**
  6028. * Toggles the visibility of the control.
  6029. * @function
  6030. * @param {Boolean} visible - true to make visible, false to hide.
  6031. */
  6032. setVisible: function( visible ) {
  6033. this.wrapper.style.display = visible ?
  6034. ( this.anchor == $.ControlAnchor.ABSOLUTE ? 'block' : 'inline-block' ) :
  6035. "none";
  6036. },
  6037. /**
  6038. * Sets the opacity level for the control.
  6039. * @function
  6040. * @param {Number} opactiy - a value between 1 and 0 inclusively.
  6041. */
  6042. setOpacity: function( opacity ) {
  6043. if ( this.element[ $.SIGNAL ] && $.Browser.vendor == $.BROWSERS.IE ) {
  6044. $.setElementOpacity( this.element, opacity, true );
  6045. } else {
  6046. $.setElementOpacity( this.wrapper, opacity, true );
  6047. }
  6048. }
  6049. };
  6050. }( OpenSeadragon ));
  6051. /*
  6052. * OpenSeadragon - ControlDock
  6053. *
  6054. * Copyright (C) 2009 CodePlex Foundation
  6055. * Copyright (C) 2010-2013 OpenSeadragon contributors
  6056. *
  6057. * Redistribution and use in source and binary forms, with or without
  6058. * modification, are permitted provided that the following conditions are
  6059. * met:
  6060. *
  6061. * - Redistributions of source code must retain the above copyright notice,
  6062. * this list of conditions and the following disclaimer.
  6063. *
  6064. * - Redistributions in binary form must reproduce the above copyright
  6065. * notice, this list of conditions and the following disclaimer in the
  6066. * documentation and/or other materials provided with the distribution.
  6067. *
  6068. * - Neither the name of CodePlex Foundation nor the names of its
  6069. * contributors may be used to endorse or promote products derived from
  6070. * this software without specific prior written permission.
  6071. *
  6072. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6073. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  6074. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6075. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  6076. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  6077. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  6078. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  6079. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  6080. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  6081. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  6082. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  6083. */
  6084. (function( $ ){
  6085. /**
  6086. * @class ControlDock
  6087. * @classdesc Provides a container element (a &lt;form&gt; element) with support for the layout of control elements.
  6088. *
  6089. * @memberof OpenSeadragon
  6090. */
  6091. $.ControlDock = function( options ){
  6092. var layouts = [ 'topleft', 'topright', 'bottomright', 'bottomleft'],
  6093. layout,
  6094. i;
  6095. $.extend( true, this, {
  6096. id: 'controldock-' + $.now() + '-' + Math.floor(Math.random() * 1000000),
  6097. container: $.makeNeutralElement( 'div' ),
  6098. controls: []
  6099. }, options );
  6100. // Disable the form's submit; otherwise button clicks and return keys
  6101. // can trigger it.
  6102. this.container.onsubmit = function() {
  6103. return false;
  6104. };
  6105. if( this.element ){
  6106. this.element = $.getElement( this.element );
  6107. this.element.appendChild( this.container );
  6108. this.element.style.position = 'relative';
  6109. this.container.style.width = '100%';
  6110. this.container.style.height = '100%';
  6111. }
  6112. for( i = 0; i < layouts.length; i++ ){
  6113. layout = layouts[ i ];
  6114. this.controls[ layout ] = $.makeNeutralElement( "div" );
  6115. this.controls[ layout ].style.position = 'absolute';
  6116. if ( layout.match( 'left' ) ){
  6117. this.controls[ layout ].style.left = '0px';
  6118. }
  6119. if ( layout.match( 'right' ) ){
  6120. this.controls[ layout ].style.right = '0px';
  6121. }
  6122. if ( layout.match( 'top' ) ){
  6123. this.controls[ layout ].style.top = '0px';
  6124. }
  6125. if ( layout.match( 'bottom' ) ){
  6126. this.controls[ layout ].style.bottom = '0px';
  6127. }
  6128. }
  6129. this.container.appendChild( this.controls.topleft );
  6130. this.container.appendChild( this.controls.topright );
  6131. this.container.appendChild( this.controls.bottomright );
  6132. this.container.appendChild( this.controls.bottomleft );
  6133. };
  6134. /** @lends OpenSeadragon.ControlDock.prototype */
  6135. $.ControlDock.prototype = {
  6136. /**
  6137. * @function
  6138. */
  6139. addControl: function ( element, controlOptions ) {
  6140. element = $.getElement( element );
  6141. var div = null;
  6142. if ( getControlIndex( this, element ) >= 0 ) {
  6143. return; // they're trying to add a duplicate control
  6144. }
  6145. switch ( controlOptions.anchor ) {
  6146. case $.ControlAnchor.TOP_RIGHT:
  6147. div = this.controls.topright;
  6148. element.style.position = "relative";
  6149. element.style.paddingRight = "0px";
  6150. element.style.paddingTop = "0px";
  6151. break;
  6152. case $.ControlAnchor.BOTTOM_RIGHT:
  6153. div = this.controls.bottomright;
  6154. element.style.position = "relative";
  6155. element.style.paddingRight = "0px";
  6156. element.style.paddingBottom = "0px";
  6157. break;
  6158. case $.ControlAnchor.BOTTOM_LEFT:
  6159. div = this.controls.bottomleft;
  6160. element.style.position = "relative";
  6161. element.style.paddingLeft = "0px";
  6162. element.style.paddingBottom = "0px";
  6163. break;
  6164. case $.ControlAnchor.TOP_LEFT:
  6165. div = this.controls.topleft;
  6166. element.style.position = "relative";
  6167. element.style.paddingLeft = "0px";
  6168. element.style.paddingTop = "0px";
  6169. break;
  6170. case $.ControlAnchor.ABSOLUTE:
  6171. div = this.container;
  6172. element.style.margin = "0px";
  6173. element.style.padding = "0px";
  6174. break;
  6175. default:
  6176. case $.ControlAnchor.NONE:
  6177. div = this.container;
  6178. element.style.margin = "0px";
  6179. element.style.padding = "0px";
  6180. break;
  6181. }
  6182. this.controls.push(
  6183. new $.Control( element, controlOptions, div )
  6184. );
  6185. element.style.display = "inline-block";
  6186. },
  6187. /**
  6188. * @function
  6189. * @return {OpenSeadragon.ControlDock} Chainable.
  6190. */
  6191. removeControl: function ( element ) {
  6192. element = $.getElement( element );
  6193. var i = getControlIndex( this, element );
  6194. if ( i >= 0 ) {
  6195. this.controls[ i ].destroy();
  6196. this.controls.splice( i, 1 );
  6197. }
  6198. return this;
  6199. },
  6200. /**
  6201. * @function
  6202. * @return {OpenSeadragon.ControlDock} Chainable.
  6203. */
  6204. clearControls: function () {
  6205. while ( this.controls.length > 0 ) {
  6206. this.controls.pop().destroy();
  6207. }
  6208. return this;
  6209. },
  6210. /**
  6211. * @function
  6212. * @return {Boolean}
  6213. */
  6214. areControlsEnabled: function () {
  6215. var i;
  6216. for ( i = this.controls.length - 1; i >= 0; i-- ) {
  6217. if ( this.controls[ i ].isVisible() ) {
  6218. return true;
  6219. }
  6220. }
  6221. return false;
  6222. },
  6223. /**
  6224. * @function
  6225. * @return {OpenSeadragon.ControlDock} Chainable.
  6226. */
  6227. setControlsEnabled: function( enabled ) {
  6228. var i;
  6229. for ( i = this.controls.length - 1; i >= 0; i-- ) {
  6230. this.controls[ i ].setVisible( enabled );
  6231. }
  6232. return this;
  6233. }
  6234. };
  6235. ///////////////////////////////////////////////////////////////////////////////
  6236. // Utility methods
  6237. ///////////////////////////////////////////////////////////////////////////////
  6238. function getControlIndex( dock, element ) {
  6239. var controls = dock.controls,
  6240. i;
  6241. for ( i = controls.length - 1; i >= 0; i-- ) {
  6242. if ( controls[ i ].element == element ) {
  6243. return i;
  6244. }
  6245. }
  6246. return -1;
  6247. }
  6248. }( OpenSeadragon ));
  6249. /*
  6250. * OpenSeadragon - Placement
  6251. *
  6252. * Copyright (C) 2010-2016 OpenSeadragon contributors
  6253. *
  6254. * Redistribution and use in source and binary forms, with or without
  6255. * modification, are permitted provided that the following conditions are
  6256. * met:
  6257. *
  6258. * - Redistributions of source code must retain the above copyright notice,
  6259. * this list of conditions and the following disclaimer.
  6260. *
  6261. * - Redistributions in binary form must reproduce the above copyright
  6262. * notice, this list of conditions and the following disclaimer in the
  6263. * documentation and/or other materials provided with the distribution.
  6264. *
  6265. * - Neither the name of CodePlex Foundation nor the names of its
  6266. * contributors may be used to endorse or promote products derived from
  6267. * this software without specific prior written permission.
  6268. *
  6269. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6270. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  6271. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6272. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  6273. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  6274. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  6275. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  6276. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  6277. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  6278. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  6279. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  6280. */
  6281. (function($) {
  6282. /**
  6283. * An enumeration of positions to anchor an element.
  6284. * @member Placement
  6285. * @memberOf OpenSeadragon
  6286. * @static
  6287. * @readonly
  6288. * @property {OpenSeadragon.Placement} CENTER
  6289. * @property {OpenSeadragon.Placement} TOP_LEFT
  6290. * @property {OpenSeadragon.Placement} TOP
  6291. * @property {OpenSeadragon.Placement} TOP_RIGHT
  6292. * @property {OpenSeadragon.Placement} RIGHT
  6293. * @property {OpenSeadragon.Placement} BOTTOM_RIGHT
  6294. * @property {OpenSeadragon.Placement} BOTTOM
  6295. * @property {OpenSeadragon.Placement} BOTTOM_LEFT
  6296. * @property {OpenSeadragon.Placement} LEFT
  6297. */
  6298. $.Placement = $.freezeObject({
  6299. CENTER: 0,
  6300. TOP_LEFT: 1,
  6301. TOP: 2,
  6302. TOP_RIGHT: 3,
  6303. RIGHT: 4,
  6304. BOTTOM_RIGHT: 5,
  6305. BOTTOM: 6,
  6306. BOTTOM_LEFT: 7,
  6307. LEFT: 8,
  6308. properties: {
  6309. 0: {
  6310. isLeft: false,
  6311. isHorizontallyCentered: true,
  6312. isRight: false,
  6313. isTop: false,
  6314. isVerticallyCentered: true,
  6315. isBottom: false
  6316. },
  6317. 1: {
  6318. isLeft: true,
  6319. isHorizontallyCentered: false,
  6320. isRight: false,
  6321. isTop: true,
  6322. isVerticallyCentered: false,
  6323. isBottom: false
  6324. },
  6325. 2: {
  6326. isLeft: false,
  6327. isHorizontallyCentered: true,
  6328. isRight: false,
  6329. isTop: true,
  6330. isVerticallyCentered: false,
  6331. isBottom: false
  6332. },
  6333. 3: {
  6334. isLeft: false,
  6335. isHorizontallyCentered: false,
  6336. isRight: true,
  6337. isTop: true,
  6338. isVerticallyCentered: false,
  6339. isBottom: false
  6340. },
  6341. 4: {
  6342. isLeft: false,
  6343. isHorizontallyCentered: false,
  6344. isRight: true,
  6345. isTop: false,
  6346. isVerticallyCentered: true,
  6347. isBottom: false
  6348. },
  6349. 5: {
  6350. isLeft: false,
  6351. isHorizontallyCentered: false,
  6352. isRight: true,
  6353. isTop: false,
  6354. isVerticallyCentered: false,
  6355. isBottom: true
  6356. },
  6357. 6: {
  6358. isLeft: false,
  6359. isHorizontallyCentered: true,
  6360. isRight: false,
  6361. isTop: false,
  6362. isVerticallyCentered: false,
  6363. isBottom: true
  6364. },
  6365. 7: {
  6366. isLeft: true,
  6367. isHorizontallyCentered: false,
  6368. isRight: false,
  6369. isTop: false,
  6370. isVerticallyCentered: false,
  6371. isBottom: true
  6372. },
  6373. 8: {
  6374. isLeft: true,
  6375. isHorizontallyCentered: false,
  6376. isRight: false,
  6377. isTop: false,
  6378. isVerticallyCentered: true,
  6379. isBottom: false
  6380. }
  6381. }
  6382. });
  6383. }(OpenSeadragon));
  6384. /*
  6385. * OpenSeadragon - Viewer
  6386. *
  6387. * Copyright (C) 2009 CodePlex Foundation
  6388. * Copyright (C) 2010-2013 OpenSeadragon contributors
  6389. *
  6390. * Redistribution and use in source and binary forms, with or without
  6391. * modification, are permitted provided that the following conditions are
  6392. * met:
  6393. *
  6394. * - Redistributions of source code must retain the above copyright notice,
  6395. * this list of conditions and the following disclaimer.
  6396. *
  6397. * - Redistributions in binary form must reproduce the above copyright
  6398. * notice, this list of conditions and the following disclaimer in the
  6399. * documentation and/or other materials provided with the distribution.
  6400. *
  6401. * - Neither the name of CodePlex Foundation nor the names of its
  6402. * contributors may be used to endorse or promote products derived from
  6403. * this software without specific prior written permission.
  6404. *
  6405. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6406. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  6407. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6408. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  6409. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  6410. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  6411. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  6412. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  6413. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  6414. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  6415. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  6416. */
  6417. (function( $ ){
  6418. // dictionary from hash to private properties
  6419. var THIS = {};
  6420. var nextHash = 1;
  6421. /**
  6422. *
  6423. * The main point of entry into creating a zoomable image on the page.<br>
  6424. * <br>
  6425. * We have provided an idiomatic javascript constructor which takes
  6426. * a single object, but still support the legacy positional arguments.<br>
  6427. * <br>
  6428. * The options below are given in order that they appeared in the constructor
  6429. * as arguments and we translate a positional call into an idiomatic call.<br>
  6430. * <br>
  6431. * To create a viewer, you can use either of this methods:<br>
  6432. * <ul>
  6433. * <li><code>var viewer = new OpenSeadragon.Viewer(options);</code></li>
  6434. * <li><code>var viewer = OpenSeadragon(options);</code></li>
  6435. * </ul>
  6436. * @class Viewer
  6437. * @classdesc The main OpenSeadragon viewer class.
  6438. *
  6439. * @memberof OpenSeadragon
  6440. * @extends OpenSeadragon.EventSource
  6441. * @extends OpenSeadragon.ControlDock
  6442. * @param {OpenSeadragon.Options} options - Viewer options.
  6443. *
  6444. **/
  6445. $.Viewer = function( options ) {
  6446. var args = arguments,
  6447. _this = this,
  6448. i;
  6449. //backward compatibility for positional args while preferring more
  6450. //idiomatic javascript options object as the only argument
  6451. if( !$.isPlainObject( options ) ){
  6452. options = {
  6453. id: args[ 0 ],
  6454. xmlPath: args.length > 1 ? args[ 1 ] : undefined,
  6455. prefixUrl: args.length > 2 ? args[ 2 ] : undefined,
  6456. controls: args.length > 3 ? args[ 3 ] : undefined,
  6457. overlays: args.length > 4 ? args[ 4 ] : undefined
  6458. };
  6459. }
  6460. //options.config and the general config argument are deprecated
  6461. //in favor of the more direct specification of optional settings
  6462. //being pass directly on the options object
  6463. if ( options.config ){
  6464. $.extend( true, options, options.config );
  6465. delete options.config;
  6466. }
  6467. //Public properties
  6468. //Allow the options object to override global defaults
  6469. $.extend( true, this, {
  6470. //internal state and dom identifiers
  6471. id: options.id,
  6472. hash: options.hash || nextHash++,
  6473. /**
  6474. * Index for page to be shown first next time open() is called (only used in sequenceMode).
  6475. * @member {Number} initialPage
  6476. * @memberof OpenSeadragon.Viewer#
  6477. */
  6478. initialPage: 0,
  6479. //dom nodes
  6480. /**
  6481. * The parent element of this Viewer instance, passed in when the Viewer was created.
  6482. * @member {Element} element
  6483. * @memberof OpenSeadragon.Viewer#
  6484. */
  6485. element: null,
  6486. /**
  6487. * A &lt;div&gt; element (provided by {@link OpenSeadragon.ControlDock}), the base element of this Viewer instance.<br><br>
  6488. * Child element of {@link OpenSeadragon.Viewer#element}.
  6489. * @member {Element} container
  6490. * @memberof OpenSeadragon.Viewer#
  6491. */
  6492. container: null,
  6493. /**
  6494. * A &lt;div&gt; element, the element where user-input events are handled for panning and zooming.<br><br>
  6495. * Child element of {@link OpenSeadragon.Viewer#container},
  6496. * positioned on top of {@link OpenSeadragon.Viewer#keyboardCommandArea}.<br><br>
  6497. * The parent of {@link OpenSeadragon.Drawer#canvas} instances.
  6498. * @member {Element} canvas
  6499. * @memberof OpenSeadragon.Viewer#
  6500. */
  6501. canvas: null,
  6502. // Overlays list. An overlay allows to add html on top of the viewer.
  6503. overlays: [],
  6504. // Container inside the canvas where overlays are drawn.
  6505. overlaysContainer: null,
  6506. //private state properties
  6507. previousBody: [],
  6508. //This was originally initialized in the constructor and so could never
  6509. //have anything in it. now it can because we allow it to be specified
  6510. //in the options and is only empty by default if not specified. Also
  6511. //this array was returned from get_controls which I find confusing
  6512. //since this object has a controls property which is treated in other
  6513. //functions like clearControls. I'm removing the accessors.
  6514. customControls: [],
  6515. //These are originally not part options but declared as members
  6516. //in initialize. It's still considered idiomatic to put them here
  6517. //source is here for backwards compatibility. It is not an official
  6518. //part of the API and should not be relied upon.
  6519. source: null,
  6520. /**
  6521. * Handles rendering of tiles in the viewer. Created for each TileSource opened.
  6522. * @member {OpenSeadragon.Drawer} drawer
  6523. * @memberof OpenSeadragon.Viewer#
  6524. */
  6525. drawer: null,
  6526. /**
  6527. * Keeps track of all of the tiled images in the scene.
  6528. * @member {OpenSeadragon.Drawer} world
  6529. * @memberof OpenSeadragon.Viewer#
  6530. */
  6531. world: null,
  6532. /**
  6533. * Handles coordinate-related functionality - zoom, pan, rotation, etc. Created for each TileSource opened.
  6534. * @member {OpenSeadragon.Viewport} viewport
  6535. * @memberof OpenSeadragon.Viewer#
  6536. */
  6537. viewport: null,
  6538. /**
  6539. * @member {OpenSeadragon.Navigator} navigator
  6540. * @memberof OpenSeadragon.Viewer#
  6541. */
  6542. navigator: null,
  6543. //A collection viewport is a separate viewport used to provide
  6544. //simultaneous rendering of sets of tiles
  6545. collectionViewport: null,
  6546. collectionDrawer: null,
  6547. //UI image resources
  6548. //TODO: rename navImages to uiImages
  6549. navImages: null,
  6550. //interface button controls
  6551. buttons: null,
  6552. //TODO: this is defunct so safely remove it
  6553. profiler: null
  6554. }, $.DEFAULT_SETTINGS, options );
  6555. if ( typeof ( this.hash) === "undefined" ) {
  6556. throw new Error("A hash must be defined, either by specifying options.id or options.hash.");
  6557. }
  6558. if ( typeof ( THIS[ this.hash ] ) !== "undefined" ) {
  6559. // We don't want to throw an error here, as the user might have discarded
  6560. // the previous viewer with the same hash and now want to recreate it.
  6561. $.console.warn("Hash " + this.hash + " has already been used.");
  6562. }
  6563. //Private state properties
  6564. THIS[ this.hash ] = {
  6565. "fsBoundsDelta": new $.Point( 1, 1 ),
  6566. "prevContainerSize": null,
  6567. "animating": false,
  6568. "forceRedraw": false,
  6569. "mouseInside": false,
  6570. "group": null,
  6571. // whether we should be continuously zooming
  6572. "zooming": false,
  6573. // how much we should be continuously zooming by
  6574. "zoomFactor": null,
  6575. "lastZoomTime": null,
  6576. "fullPage": false,
  6577. "onfullscreenchange": null
  6578. };
  6579. this._sequenceIndex = 0;
  6580. this._firstOpen = true;
  6581. this._updateRequestId = null;
  6582. this._loadQueue = [];
  6583. this.currentOverlays = [];
  6584. this._lastScrollTime = $.now(); // variable used to help normalize the scroll event speed of different devices
  6585. //Inherit some behaviors and properties
  6586. $.EventSource.call( this );
  6587. this.addHandler( 'open-failed', function ( event ) {
  6588. var msg = $.getString( "Errors.OpenFailed", event.eventSource, event.message);
  6589. _this._showMessage( msg );
  6590. });
  6591. $.ControlDock.call( this, options );
  6592. //Deal with tile sources
  6593. if (this.xmlPath) {
  6594. //Deprecated option. Now it is preferred to use the tileSources option
  6595. this.tileSources = [ this.xmlPath ];
  6596. }
  6597. this.element = this.element || document.getElementById( this.id );
  6598. this.canvas = $.makeNeutralElement( "div" );
  6599. this.canvas.className = "openseadragon-canvas";
  6600. (function( style ){
  6601. style.width = "100%";
  6602. style.height = "100%";
  6603. style.overflow = "hidden";
  6604. style.position = "absolute";
  6605. style.top = "0px";
  6606. style.left = "0px";
  6607. }(this.canvas.style));
  6608. $.setElementTouchActionNone( this.canvas );
  6609. if (options.tabIndex !== "") {
  6610. this.canvas.tabIndex = (options.tabIndex === undefined ? 0 : options.tabIndex);
  6611. }
  6612. //the container is created through applying the ControlDock constructor above
  6613. this.container.className = "openseadragon-container";
  6614. (function( style ){
  6615. style.width = "100%";
  6616. style.height = "100%";
  6617. style.position = "relative";
  6618. style.overflow = "hidden";
  6619. style.left = "0px";
  6620. style.top = "0px";
  6621. style.textAlign = "left"; // needed to protect against
  6622. }( this.container.style ));
  6623. this.container.insertBefore( this.canvas, this.container.firstChild );
  6624. this.element.appendChild( this.container );
  6625. //Used for toggling between fullscreen and default container size
  6626. //TODO: these can be closure private and shared across Viewer
  6627. // instances.
  6628. this.bodyWidth = document.body.style.width;
  6629. this.bodyHeight = document.body.style.height;
  6630. this.bodyOverflow = document.body.style.overflow;
  6631. this.docOverflow = document.documentElement.style.overflow;
  6632. this.innerTracker = new $.MouseTracker({
  6633. element: this.canvas,
  6634. startDisabled: !this.mouseNavEnabled,
  6635. clickTimeThreshold: this.clickTimeThreshold,
  6636. clickDistThreshold: this.clickDistThreshold,
  6637. dblClickTimeThreshold: this.dblClickTimeThreshold,
  6638. dblClickDistThreshold: this.dblClickDistThreshold,
  6639. keyDownHandler: $.delegate( this, onCanvasKeyDown ),
  6640. keyHandler: $.delegate( this, onCanvasKeyPress ),
  6641. clickHandler: $.delegate( this, onCanvasClick ),
  6642. dblClickHandler: $.delegate( this, onCanvasDblClick ),
  6643. dragHandler: $.delegate( this, onCanvasDrag ),
  6644. dragEndHandler: $.delegate( this, onCanvasDragEnd ),
  6645. enterHandler: $.delegate( this, onCanvasEnter ),
  6646. exitHandler: $.delegate( this, onCanvasExit ),
  6647. pressHandler: $.delegate( this, onCanvasPress ),
  6648. releaseHandler: $.delegate( this, onCanvasRelease ),
  6649. nonPrimaryPressHandler: $.delegate( this, onCanvasNonPrimaryPress ),
  6650. nonPrimaryReleaseHandler: $.delegate( this, onCanvasNonPrimaryRelease ),
  6651. scrollHandler: $.delegate( this, onCanvasScroll ),
  6652. pinchHandler: $.delegate( this, onCanvasPinch )
  6653. });
  6654. this.outerTracker = new $.MouseTracker({
  6655. element: this.container,
  6656. startDisabled: !this.mouseNavEnabled,
  6657. clickTimeThreshold: this.clickTimeThreshold,
  6658. clickDistThreshold: this.clickDistThreshold,
  6659. dblClickTimeThreshold: this.dblClickTimeThreshold,
  6660. dblClickDistThreshold: this.dblClickDistThreshold,
  6661. enterHandler: $.delegate( this, onContainerEnter ),
  6662. exitHandler: $.delegate( this, onContainerExit )
  6663. });
  6664. if( this.toolbar ){
  6665. this.toolbar = new $.ControlDock({ element: this.toolbar });
  6666. }
  6667. this.bindStandardControls();
  6668. THIS[ this.hash ].prevContainerSize = _getSafeElemSize( this.container );
  6669. // Create the world
  6670. this.world = new $.World({
  6671. viewer: this
  6672. });
  6673. this.world.addHandler('add-item', function(event) {
  6674. // For backwards compatibility, we maintain the source property
  6675. _this.source = _this.world.getItemAt(0).source;
  6676. THIS[ _this.hash ].forceRedraw = true;
  6677. if (!_this._updateRequestId) {
  6678. _this._updateRequestId = scheduleUpdate( _this, updateMulti );
  6679. }
  6680. });
  6681. this.world.addHandler('remove-item', function(event) {
  6682. // For backwards compatibility, we maintain the source property
  6683. if (_this.world.getItemCount()) {
  6684. _this.source = _this.world.getItemAt(0).source;
  6685. } else {
  6686. _this.source = null;
  6687. }
  6688. THIS[ _this.hash ].forceRedraw = true;
  6689. });
  6690. this.world.addHandler('metrics-change', function(event) {
  6691. if (_this.viewport) {
  6692. _this.viewport._setContentBounds(_this.world.getHomeBounds(), _this.world.getContentFactor());
  6693. }
  6694. });
  6695. this.world.addHandler('item-index-change', function(event) {
  6696. // For backwards compatibility, we maintain the source property
  6697. _this.source = _this.world.getItemAt(0).source;
  6698. });
  6699. // Create the viewport
  6700. this.viewport = new $.Viewport({
  6701. containerSize: THIS[ this.hash ].prevContainerSize,
  6702. springStiffness: this.springStiffness,
  6703. animationTime: this.animationTime,
  6704. minZoomImageRatio: this.minZoomImageRatio,
  6705. maxZoomPixelRatio: this.maxZoomPixelRatio,
  6706. visibilityRatio: this.visibilityRatio,
  6707. wrapHorizontal: this.wrapHorizontal,
  6708. wrapVertical: this.wrapVertical,
  6709. defaultZoomLevel: this.defaultZoomLevel,
  6710. minZoomLevel: this.minZoomLevel,
  6711. maxZoomLevel: this.maxZoomLevel,
  6712. viewer: this,
  6713. degrees: this.degrees,
  6714. flipped: this.flipped,
  6715. navigatorRotate: this.navigatorRotate,
  6716. homeFillsViewer: this.homeFillsViewer,
  6717. margins: this.viewportMargins
  6718. });
  6719. this.viewport._setContentBounds(this.world.getHomeBounds(), this.world.getContentFactor());
  6720. // Create the image loader
  6721. this.imageLoader = new $.ImageLoader({
  6722. jobLimit: this.imageLoaderLimit,
  6723. timeout: options.timeout
  6724. });
  6725. // Create the tile cache
  6726. this.tileCache = new $.TileCache({
  6727. maxImageCacheCount: this.maxImageCacheCount
  6728. });
  6729. // Create the drawer
  6730. this.drawer = new $.Drawer({
  6731. viewer: this,
  6732. viewport: this.viewport,
  6733. element: this.canvas,
  6734. debugGridColor: this.debugGridColor
  6735. });
  6736. // Overlay container
  6737. this.overlaysContainer = $.makeNeutralElement( "div" );
  6738. this.canvas.appendChild( this.overlaysContainer );
  6739. // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons
  6740. if (!this.drawer.canRotate()) {
  6741. // Disable/remove the rotate left/right buttons since they aren't supported
  6742. if (this.rotateLeft) {
  6743. i = this.buttons.buttons.indexOf(this.rotateLeft);
  6744. this.buttons.buttons.splice(i, 1);
  6745. this.buttons.element.removeChild(this.rotateLeft.element);
  6746. }
  6747. if (this.rotateRight) {
  6748. i = this.buttons.buttons.indexOf(this.rotateRight);
  6749. this.buttons.buttons.splice(i, 1);
  6750. this.buttons.element.removeChild(this.rotateRight.element);
  6751. }
  6752. }
  6753. //Instantiate a navigator if configured
  6754. if ( this.showNavigator){
  6755. this.navigator = new $.Navigator({
  6756. id: this.navigatorId,
  6757. position: this.navigatorPosition,
  6758. sizeRatio: this.navigatorSizeRatio,
  6759. maintainSizeRatio: this.navigatorMaintainSizeRatio,
  6760. top: this.navigatorTop,
  6761. left: this.navigatorLeft,
  6762. width: this.navigatorWidth,
  6763. height: this.navigatorHeight,
  6764. autoResize: this.navigatorAutoResize,
  6765. autoFade: this.navigatorAutoFade,
  6766. prefixUrl: this.prefixUrl,
  6767. viewer: this,
  6768. navigatorRotate: this.navigatorRotate,
  6769. background: this.navigatorBackground,
  6770. opacity: this.navigatorOpacity,
  6771. borderColor: this.navigatorBorderColor,
  6772. displayRegionColor: this.navigatorDisplayRegionColor,
  6773. crossOriginPolicy: this.crossOriginPolicy
  6774. });
  6775. }
  6776. // Sequence mode
  6777. if (this.sequenceMode) {
  6778. this.bindSequenceControls();
  6779. }
  6780. // Open initial tilesources
  6781. if (this.tileSources) {
  6782. this.open( this.tileSources );
  6783. }
  6784. // Add custom controls
  6785. for ( i = 0; i < this.customControls.length; i++ ) {
  6786. this.addControl(
  6787. this.customControls[ i ].id,
  6788. {anchor: this.customControls[ i ].anchor}
  6789. );
  6790. }
  6791. // Initial fade out
  6792. $.requestAnimationFrame( function(){
  6793. beginControlsAutoHide( _this );
  6794. } );
  6795. // Initial canvas options
  6796. if ( this.imageSmoothingEnabled !== undefined && !this.imageSmoothingEnabled){
  6797. this.drawer.setImageSmoothingEnabled(this.imageSmoothingEnabled);
  6798. }
  6799. };
  6800. $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, /** @lends OpenSeadragon.Viewer.prototype */{
  6801. /**
  6802. * @function
  6803. * @return {Boolean}
  6804. */
  6805. isOpen: function () {
  6806. return !!this.world.getItemCount();
  6807. },
  6808. // deprecated
  6809. openDzi: function ( dzi ) {
  6810. $.console.error( "[Viewer.openDzi] this function is deprecated; use Viewer.open() instead." );
  6811. return this.open( dzi );
  6812. },
  6813. // deprecated
  6814. openTileSource: function ( tileSource ) {
  6815. $.console.error( "[Viewer.openTileSource] this function is deprecated; use Viewer.open() instead." );
  6816. return this.open( tileSource );
  6817. },
  6818. /**
  6819. * Open tiled images into the viewer, closing any others.
  6820. * To get the TiledImage instance created by open, add an event listener for
  6821. * {@link OpenSeadragon.Viewer.html#.event:open}, which when fired can be used to get access
  6822. * to the instance, i.e., viewer.world.getItemAt(0).
  6823. * @function
  6824. * @param {Array|String|Object|Function} tileSources - This can be a TiledImage
  6825. * specifier, a TileSource specifier, or an array of either. A TiledImage specifier
  6826. * is the same as the options parameter for {@link OpenSeadragon.Viewer#addTiledImage},
  6827. * except for the index property; images are added in sequence.
  6828. * A TileSource specifier is anything you could pass as the tileSource property
  6829. * of the options parameter for {@link OpenSeadragon.Viewer#addTiledImage}.
  6830. * @param {Number} initialPage - If sequenceMode is true, display this page initially
  6831. * for the given tileSources. If specified, will overwrite the Viewer's existing initialPage property.
  6832. * @return {OpenSeadragon.Viewer} Chainable.
  6833. * @fires OpenSeadragon.Viewer.event:open
  6834. * @fires OpenSeadragon.Viewer.event:open-failed
  6835. */
  6836. open: function (tileSources, initialPage) {
  6837. var _this = this;
  6838. this.close();
  6839. if (!tileSources) {
  6840. return;
  6841. }
  6842. if (this.sequenceMode && $.isArray(tileSources)) {
  6843. if (this.referenceStrip) {
  6844. this.referenceStrip.destroy();
  6845. this.referenceStrip = null;
  6846. }
  6847. if (typeof initialPage != 'undefined' && !isNaN(initialPage)) {
  6848. this.initialPage = initialPage;
  6849. }
  6850. this.tileSources = tileSources;
  6851. this._sequenceIndex = Math.max(0, Math.min(this.tileSources.length - 1, this.initialPage));
  6852. if (this.tileSources.length) {
  6853. this.open(this.tileSources[this._sequenceIndex]);
  6854. if ( this.showReferenceStrip ){
  6855. this.addReferenceStrip();
  6856. }
  6857. }
  6858. this._updateSequenceButtons( this._sequenceIndex );
  6859. return;
  6860. }
  6861. if (!$.isArray(tileSources)) {
  6862. tileSources = [tileSources];
  6863. }
  6864. if (!tileSources.length) {
  6865. return;
  6866. }
  6867. this._opening = true;
  6868. var expected = tileSources.length;
  6869. var successes = 0;
  6870. var failures = 0;
  6871. var failEvent;
  6872. var checkCompletion = function() {
  6873. if (successes + failures === expected) {
  6874. if (successes) {
  6875. if (_this._firstOpen || !_this.preserveViewport) {
  6876. _this.viewport.goHome( true );
  6877. _this.viewport.update();
  6878. }
  6879. _this._firstOpen = false;
  6880. var source = tileSources[0];
  6881. if (source.tileSource) {
  6882. source = source.tileSource;
  6883. }
  6884. // Global overlays
  6885. if( _this.overlays && !_this.preserveOverlays ){
  6886. for ( var i = 0; i < _this.overlays.length; i++ ) {
  6887. _this.currentOverlays[ i ] = getOverlayObject( _this, _this.overlays[ i ] );
  6888. }
  6889. }
  6890. _this._drawOverlays();
  6891. _this._opening = false;
  6892. /**
  6893. * Raised when the viewer has opened and loaded one or more TileSources.
  6894. *
  6895. * @event open
  6896. * @memberof OpenSeadragon.Viewer
  6897. * @type {object}
  6898. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  6899. * @property {OpenSeadragon.TileSource} source - The tile source that was opened.
  6900. * @property {?Object} userData - Arbitrary subscriber-defined object.
  6901. */
  6902. // TODO: what if there are multiple sources?
  6903. _this.raiseEvent( 'open', { source: source } );
  6904. } else {
  6905. _this._opening = false;
  6906. /**
  6907. * Raised when an error occurs loading a TileSource.
  6908. *
  6909. * @event open-failed
  6910. * @memberof OpenSeadragon.Viewer
  6911. * @type {object}
  6912. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  6913. * @property {String} message - Information about what failed.
  6914. * @property {String} source - The tile source that failed.
  6915. * @property {?Object} userData - Arbitrary subscriber-defined object.
  6916. */
  6917. _this.raiseEvent( 'open-failed', failEvent );
  6918. }
  6919. }
  6920. };
  6921. var doOne = function(options) {
  6922. if (!$.isPlainObject(options) || !options.tileSource) {
  6923. options = {
  6924. tileSource: options
  6925. };
  6926. }
  6927. if (options.index !== undefined) {
  6928. $.console.error('[Viewer.open] setting indexes here is not supported; use addTiledImage instead');
  6929. delete options.index;
  6930. }
  6931. if (options.collectionImmediately === undefined) {
  6932. options.collectionImmediately = true;
  6933. }
  6934. var originalSuccess = options.success;
  6935. options.success = function(event) {
  6936. successes++;
  6937. // TODO: now that options has other things besides tileSource, the overlays
  6938. // should probably be at the options level, not the tileSource level.
  6939. if (options.tileSource.overlays) {
  6940. for (var i = 0; i < options.tileSource.overlays.length; i++) {
  6941. _this.addOverlay(options.tileSource.overlays[i]);
  6942. }
  6943. }
  6944. if (originalSuccess) {
  6945. originalSuccess(event);
  6946. }
  6947. checkCompletion();
  6948. };
  6949. var originalError = options.error;
  6950. options.error = function(event) {
  6951. failures++;
  6952. if (!failEvent) {
  6953. failEvent = event;
  6954. }
  6955. if (originalError) {
  6956. originalError(event);
  6957. }
  6958. checkCompletion();
  6959. };
  6960. _this.addTiledImage(options);
  6961. };
  6962. // TileSources
  6963. for (var i = 0; i < tileSources.length; i++) {
  6964. doOne(tileSources[i]);
  6965. }
  6966. return this;
  6967. },
  6968. /**
  6969. * @function
  6970. * @return {OpenSeadragon.Viewer} Chainable.
  6971. * @fires OpenSeadragon.Viewer.event:close
  6972. */
  6973. close: function ( ) {
  6974. if ( !THIS[ this.hash ] ) {
  6975. //this viewer has already been destroyed: returning immediately
  6976. return this;
  6977. }
  6978. this._opening = false;
  6979. if ( this.navigator ) {
  6980. this.navigator.close();
  6981. }
  6982. if (!this.preserveOverlays) {
  6983. this.clearOverlays();
  6984. this.overlaysContainer.innerHTML = "";
  6985. }
  6986. THIS[ this.hash ].animating = false;
  6987. this.world.removeAll();
  6988. this.imageLoader.clear();
  6989. /**
  6990. * Raised when the viewer is closed (see {@link OpenSeadragon.Viewer#close}).
  6991. *
  6992. * @event close
  6993. * @memberof OpenSeadragon.Viewer
  6994. * @type {object}
  6995. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  6996. * @property {?Object} userData - Arbitrary subscriber-defined object.
  6997. */
  6998. this.raiseEvent( 'close' );
  6999. return this;
  7000. },
  7001. /**
  7002. * Function to destroy the viewer and clean up everything created by OpenSeadragon.
  7003. *
  7004. * Example:
  7005. * var viewer = OpenSeadragon({
  7006. * [...]
  7007. * });
  7008. *
  7009. * //when you are done with the viewer:
  7010. * viewer.destroy();
  7011. * viewer = null; //important
  7012. *
  7013. * @function
  7014. */
  7015. destroy: function( ) {
  7016. if ( !THIS[ this.hash ] ) {
  7017. //this viewer has already been destroyed: returning immediately
  7018. return;
  7019. }
  7020. this.close();
  7021. this.clearOverlays();
  7022. this.overlaysContainer.innerHTML = "";
  7023. //TODO: implement this...
  7024. //this.unbindSequenceControls()
  7025. //this.unbindStandardControls()
  7026. if (this.referenceStrip) {
  7027. this.referenceStrip.destroy();
  7028. this.referenceStrip = null;
  7029. }
  7030. if ( this._updateRequestId !== null ) {
  7031. $.cancelAnimationFrame( this._updateRequestId );
  7032. this._updateRequestId = null;
  7033. }
  7034. if ( this.drawer ) {
  7035. this.drawer.destroy();
  7036. }
  7037. this.removeAllHandlers();
  7038. // Go through top element (passed to us) and remove all children
  7039. // Use removeChild to make sure it handles SVG or any non-html
  7040. // also it performs better - http://jsperf.com/innerhtml-vs-removechild/15
  7041. if (this.element){
  7042. while (this.element.firstChild) {
  7043. this.element.removeChild(this.element.firstChild);
  7044. }
  7045. }
  7046. // destroy the mouse trackers
  7047. if (this.innerTracker){
  7048. this.innerTracker.destroy();
  7049. }
  7050. if (this.outerTracker){
  7051. this.outerTracker.destroy();
  7052. }
  7053. THIS[ this.hash ] = null;
  7054. delete THIS[ this.hash ];
  7055. // clear all our references to dom objects
  7056. this.canvas = null;
  7057. this.container = null;
  7058. // clear our reference to the main element - they will need to pass it in again, creating a new viewer
  7059. this.element = null;
  7060. },
  7061. /**
  7062. * @function
  7063. * @return {Boolean}
  7064. */
  7065. isMouseNavEnabled: function () {
  7066. return this.innerTracker.isTracking();
  7067. },
  7068. /**
  7069. * @function
  7070. * @param {Boolean} enabled - true to enable, false to disable
  7071. * @return {OpenSeadragon.Viewer} Chainable.
  7072. * @fires OpenSeadragon.Viewer.event:mouse-enabled
  7073. */
  7074. setMouseNavEnabled: function( enabled ){
  7075. this.innerTracker.setTracking( enabled );
  7076. this.outerTracker.setTracking( enabled );
  7077. /**
  7078. * Raised when mouse/touch navigation is enabled or disabled (see {@link OpenSeadragon.Viewer#setMouseNavEnabled}).
  7079. *
  7080. * @event mouse-enabled
  7081. * @memberof OpenSeadragon.Viewer
  7082. * @type {object}
  7083. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  7084. * @property {Boolean} enabled
  7085. * @property {?Object} userData - Arbitrary subscriber-defined object.
  7086. */
  7087. this.raiseEvent( 'mouse-enabled', { enabled: enabled } );
  7088. return this;
  7089. },
  7090. /**
  7091. * @function
  7092. * @return {Boolean}
  7093. */
  7094. areControlsEnabled: function () {
  7095. var enabled = this.controls.length,
  7096. i;
  7097. for( i = 0; i < this.controls.length; i++ ){
  7098. enabled = enabled && this.controls[ i ].isVisible();
  7099. }
  7100. return enabled;
  7101. },
  7102. /**
  7103. * Shows or hides the controls (e.g. the default navigation buttons).
  7104. *
  7105. * @function
  7106. * @param {Boolean} true to show, false to hide.
  7107. * @return {OpenSeadragon.Viewer} Chainable.
  7108. * @fires OpenSeadragon.Viewer.event:controls-enabled
  7109. */
  7110. setControlsEnabled: function( enabled ) {
  7111. if( enabled ){
  7112. abortControlsAutoHide( this );
  7113. } else {
  7114. beginControlsAutoHide( this );
  7115. }
  7116. /**
  7117. * Raised when the navigation controls are shown or hidden (see {@link OpenSeadragon.Viewer#setControlsEnabled}).
  7118. *
  7119. * @event controls-enabled
  7120. * @memberof OpenSeadragon.Viewer
  7121. * @type {object}
  7122. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  7123. * @property {Boolean} enabled
  7124. * @property {?Object} userData - Arbitrary subscriber-defined object.
  7125. */
  7126. this.raiseEvent( 'controls-enabled', { enabled: enabled } );
  7127. return this;
  7128. },
  7129. /**
  7130. * Turns debugging mode on or off for this viewer.
  7131. *
  7132. * @function
  7133. * @param {Boolean} true to turn debug on, false to turn debug off.
  7134. */
  7135. setDebugMode: function(debugMode){
  7136. for (var i = 0; i < this.world.getItemCount(); i++) {
  7137. this.world.getItemAt(i).debugMode = debugMode;
  7138. }
  7139. this.debugMode = debugMode;
  7140. this.forceRedraw();
  7141. },
  7142. /**
  7143. * @function
  7144. * @return {Boolean}
  7145. */
  7146. isFullPage: function () {
  7147. return THIS[ this.hash ].fullPage;
  7148. },
  7149. /**
  7150. * Toggle full page mode.
  7151. * @function
  7152. * @param {Boolean} fullPage
  7153. * If true, enter full page mode. If false, exit full page mode.
  7154. * @return {OpenSeadragon.Viewer} Chainable.
  7155. * @fires OpenSeadragon.Viewer.event:pre-full-page
  7156. * @fires OpenSeadragon.Viewer.event:full-page
  7157. */
  7158. setFullPage: function( fullPage ) {
  7159. var body = document.body,
  7160. bodyStyle = body.style,
  7161. docStyle = document.documentElement.style,
  7162. _this = this,
  7163. nodes,
  7164. i;
  7165. //don't bother modifying the DOM if we are already in full page mode.
  7166. if ( fullPage == this.isFullPage() ) {
  7167. return this;
  7168. }
  7169. var fullPageEventArgs = {
  7170. fullPage: fullPage,
  7171. preventDefaultAction: false
  7172. };
  7173. /**
  7174. * Raised when the viewer is about to change to/from full-page mode (see {@link OpenSeadragon.Viewer#setFullPage}).
  7175. *
  7176. * @event pre-full-page
  7177. * @memberof OpenSeadragon.Viewer
  7178. * @type {object}
  7179. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  7180. * @property {Boolean} fullPage - True if entering full-page mode, false if exiting full-page mode.
  7181. * @property {Boolean} preventDefaultAction - Set to true to prevent full-page mode change. Default: false.
  7182. * @property {?Object} userData - Arbitrary subscriber-defined object.
  7183. */
  7184. this.raiseEvent( 'pre-full-page', fullPageEventArgs );
  7185. if ( fullPageEventArgs.preventDefaultAction ) {
  7186. return this;
  7187. }
  7188. if ( fullPage ) {
  7189. this.elementSize = $.getElementSize( this.element );
  7190. this.pageScroll = $.getPageScroll();
  7191. this.elementMargin = this.element.style.margin;
  7192. this.element.style.margin = "0";
  7193. this.elementPadding = this.element.style.padding;
  7194. this.element.style.padding = "0";
  7195. this.bodyMargin = bodyStyle.margin;
  7196. this.docMargin = docStyle.margin;
  7197. bodyStyle.margin = "0";
  7198. docStyle.margin = "0";
  7199. this.bodyPadding = bodyStyle.padding;
  7200. this.docPadding = docStyle.padding;
  7201. bodyStyle.padding = "0";
  7202. docStyle.padding = "0";
  7203. this.bodyWidth = bodyStyle.width;
  7204. this.docWidth = docStyle.width;
  7205. bodyStyle.width = "100%";
  7206. docStyle.width = "100%";
  7207. this.bodyHeight = bodyStyle.height;
  7208. this.docHeight = docStyle.height;
  7209. bodyStyle.height = "100%";
  7210. docStyle.height = "100%";
  7211. //when entering full screen on the ipad it wasn't sufficient to leave
  7212. //the body intact as only only the top half of the screen would
  7213. //respond to touch events on the canvas, while the bottom half treated
  7214. //them as touch events on the document body. Thus we remove and store
  7215. //the bodies elements and replace them when we leave full screen.
  7216. this.previousBody = [];
  7217. THIS[ this.hash ].prevElementParent = this.element.parentNode;
  7218. THIS[ this.hash ].prevNextSibling = this.element.nextSibling;
  7219. THIS[ this.hash ].prevElementWidth = this.element.style.width;
  7220. THIS[ this.hash ].prevElementHeight = this.element.style.height;
  7221. nodes = body.childNodes.length;
  7222. for ( i = 0; i < nodes; i++ ) {
  7223. this.previousBody.push( body.childNodes[ 0 ] );
  7224. body.removeChild( body.childNodes[ 0 ] );
  7225. }
  7226. //If we've got a toolbar, we need to enable the user to use css to
  7227. //preserve it in fullpage mode
  7228. if ( this.toolbar && this.toolbar.element ) {
  7229. //save a reference to the parent so we can put it back
  7230. //in the long run we need a better strategy
  7231. this.toolbar.parentNode = this.toolbar.element.parentNode;
  7232. this.toolbar.nextSibling = this.toolbar.element.nextSibling;
  7233. body.appendChild( this.toolbar.element );
  7234. //Make sure the user has some ability to style the toolbar based
  7235. //on the mode
  7236. $.addClass( this.toolbar.element, 'fullpage' );
  7237. }
  7238. $.addClass( this.element, 'fullpage' );
  7239. body.appendChild( this.element );
  7240. this.element.style.height = $.getWindowSize().y + 'px';
  7241. this.element.style.width = $.getWindowSize().x + 'px';
  7242. if ( this.toolbar && this.toolbar.element ) {
  7243. this.element.style.height = (
  7244. $.getElementSize( this.element ).y - $.getElementSize( this.toolbar.element ).y
  7245. ) + 'px';
  7246. }
  7247. THIS[ this.hash ].fullPage = true;
  7248. // mouse will be inside container now
  7249. $.delegate( this, onContainerEnter )( {} );
  7250. } else {
  7251. this.element.style.margin = this.elementMargin;
  7252. this.element.style.padding = this.elementPadding;
  7253. bodyStyle.margin = this.bodyMargin;
  7254. docStyle.margin = this.docMargin;
  7255. bodyStyle.padding = this.bodyPadding;
  7256. docStyle.padding = this.docPadding;
  7257. bodyStyle.width = this.bodyWidth;
  7258. docStyle.width = this.docWidth;
  7259. bodyStyle.height = this.bodyHeight;
  7260. docStyle.height = this.docHeight;
  7261. body.removeChild( this.element );
  7262. nodes = this.previousBody.length;
  7263. for ( i = 0; i < nodes; i++ ) {
  7264. body.appendChild( this.previousBody.shift() );
  7265. }
  7266. $.removeClass( this.element, 'fullpage' );
  7267. THIS[ this.hash ].prevElementParent.insertBefore(
  7268. this.element,
  7269. THIS[ this.hash ].prevNextSibling
  7270. );
  7271. //If we've got a toolbar, we need to enable the user to use css to
  7272. //reset it to its original state
  7273. if ( this.toolbar && this.toolbar.element ) {
  7274. body.removeChild( this.toolbar.element );
  7275. //Make sure the user has some ability to style the toolbar based
  7276. //on the mode
  7277. $.removeClass( this.toolbar.element, 'fullpage' );
  7278. this.toolbar.parentNode.insertBefore(
  7279. this.toolbar.element,
  7280. this.toolbar.nextSibling
  7281. );
  7282. delete this.toolbar.parentNode;
  7283. delete this.toolbar.nextSibling;
  7284. }
  7285. this.element.style.width = THIS[ this.hash ].prevElementWidth;
  7286. this.element.style.height = THIS[ this.hash ].prevElementHeight;
  7287. // After exiting fullPage or fullScreen, it can take some time
  7288. // before the browser can actually set the scroll.
  7289. var restoreScrollCounter = 0;
  7290. var restoreScroll = function() {
  7291. $.setPageScroll( _this.pageScroll );
  7292. var pageScroll = $.getPageScroll();
  7293. restoreScrollCounter++;
  7294. if (restoreScrollCounter < 10 &&
  7295. (pageScroll.x !== _this.pageScroll.x ||
  7296. pageScroll.y !== _this.pageScroll.y)) {
  7297. $.requestAnimationFrame( restoreScroll );
  7298. }
  7299. };
  7300. $.requestAnimationFrame( restoreScroll );
  7301. THIS[ this.hash ].fullPage = false;
  7302. // mouse will likely be outside now
  7303. $.delegate( this, onContainerExit )( { } );
  7304. }
  7305. if ( this.navigator && this.viewport ) {
  7306. this.navigator.update( this.viewport );
  7307. }
  7308. /**
  7309. * Raised when the viewer has changed to/from full-page mode (see {@link OpenSeadragon.Viewer#setFullPage}).
  7310. *
  7311. * @event full-page
  7312. * @memberof OpenSeadragon.Viewer
  7313. * @type {object}
  7314. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  7315. * @property {Boolean} fullPage - True if changed to full-page mode, false if exited full-page mode.
  7316. * @property {?Object} userData - Arbitrary subscriber-defined object.
  7317. */
  7318. this.raiseEvent( 'full-page', { fullPage: fullPage } );
  7319. return this;
  7320. },
  7321. /**
  7322. * Toggle full screen mode if supported. Toggle full page mode otherwise.
  7323. * @function
  7324. * @param {Boolean} fullScreen
  7325. * If true, enter full screen mode. If false, exit full screen mode.
  7326. * @return {OpenSeadragon.Viewer} Chainable.
  7327. * @fires OpenSeadragon.Viewer.event:pre-full-screen
  7328. * @fires OpenSeadragon.Viewer.event:full-screen
  7329. */
  7330. setFullScreen: function( fullScreen ) {
  7331. var _this = this;
  7332. if ( !$.supportsFullScreen ) {
  7333. return this.setFullPage( fullScreen );
  7334. }
  7335. if ( $.isFullScreen() === fullScreen ) {
  7336. return this;
  7337. }
  7338. var fullScreeEventArgs = {
  7339. fullScreen: fullScreen,
  7340. preventDefaultAction: false
  7341. };
  7342. /**
  7343. * Raised when the viewer is about to change to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}).
  7344. * Note: the pre-full-screen event is not raised when the user is exiting
  7345. * full-screen mode by pressing the Esc key. In that case, consider using
  7346. * the full-screen, pre-full-page or full-page events.
  7347. *
  7348. * @event pre-full-screen
  7349. * @memberof OpenSeadragon.Viewer
  7350. * @type {object}
  7351. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  7352. * @property {Boolean} fullScreen - True if entering full-screen mode, false if exiting full-screen mode.
  7353. * @property {Boolean} preventDefaultAction - Set to true to prevent full-screen mode change. Default: false.
  7354. * @property {?Object} userData - Arbitrary subscriber-defined object.
  7355. */
  7356. this.raiseEvent( 'pre-full-screen', fullScreeEventArgs );
  7357. if ( fullScreeEventArgs.preventDefaultAction ) {
  7358. return this;
  7359. }
  7360. if ( fullScreen ) {
  7361. this.setFullPage( true );
  7362. // If the full page mode is not actually entered, we need to prevent
  7363. // the full screen mode.
  7364. if ( !this.isFullPage() ) {
  7365. return this;
  7366. }
  7367. this.fullPageStyleWidth = this.element.style.width;
  7368. this.fullPageStyleHeight = this.element.style.height;
  7369. this.element.style.width = '100%';
  7370. this.element.style.height = '100%';
  7371. var onFullScreenChange = function() {
  7372. var isFullScreen = $.isFullScreen();
  7373. if ( !isFullScreen ) {
  7374. $.removeEvent( document, $.fullScreenEventName, onFullScreenChange );
  7375. $.removeEvent( document, $.fullScreenErrorEventName, onFullScreenChange );
  7376. _this.setFullPage( false );
  7377. if ( _this.isFullPage() ) {
  7378. _this.element.style.width = _this.fullPageStyleWidth;
  7379. _this.element.style.height = _this.fullPageStyleHeight;
  7380. }
  7381. }
  7382. if ( _this.navigator && _this.viewport ) {
  7383. //09/08/2018 - Fabroh : Fix issue #1504 : Ensure to get the navigator updated on fullscreen out with custom location with a timeout
  7384. setTimeout(function(){
  7385. _this.navigator.update( _this.viewport );
  7386. });
  7387. }
  7388. /**
  7389. * Raised when the viewer has changed to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}).
  7390. *
  7391. * @event full-screen
  7392. * @memberof OpenSeadragon.Viewer
  7393. * @type {object}
  7394. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  7395. * @property {Boolean} fullScreen - True if changed to full-screen mode, false if exited full-screen mode.
  7396. * @property {?Object} userData - Arbitrary subscriber-defined object.
  7397. */
  7398. _this.raiseEvent( 'full-screen', { fullScreen: isFullScreen } );
  7399. };
  7400. $.addEvent( document, $.fullScreenEventName, onFullScreenChange );
  7401. $.addEvent( document, $.fullScreenErrorEventName, onFullScreenChange );
  7402. $.requestFullScreen( document.body );
  7403. } else {
  7404. $.exitFullScreen();
  7405. }
  7406. return this;
  7407. },
  7408. /**
  7409. * @function
  7410. * @return {Boolean}
  7411. */
  7412. isVisible: function () {
  7413. return this.container.style.visibility != "hidden";
  7414. },
  7415. /**
  7416. * @function
  7417. * @param {Boolean} visible
  7418. * @return {OpenSeadragon.Viewer} Chainable.
  7419. * @fires OpenSeadragon.Viewer.event:visible
  7420. */
  7421. setVisible: function( visible ){
  7422. this.container.style.visibility = visible ? "" : "hidden";
  7423. /**
  7424. * Raised when the viewer is shown or hidden (see {@link OpenSeadragon.Viewer#setVisible}).
  7425. *
  7426. * @event visible
  7427. * @memberof OpenSeadragon.Viewer
  7428. * @type {object}
  7429. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  7430. * @property {Boolean} visible
  7431. * @property {?Object} userData - Arbitrary subscriber-defined object.
  7432. */
  7433. this.raiseEvent( 'visible', { visible: visible } );
  7434. return this;
  7435. },
  7436. /**
  7437. * Add a tiled image to the viewer.
  7438. * options.tileSource can be anything that {@link OpenSeadragon.Viewer#open}
  7439. * supports except arrays of images.
  7440. * Note that you can specify options.width or options.height, but not both.
  7441. * The other dimension will be calculated according to the item's aspect ratio.
  7442. * If collectionMode is on (see {@link OpenSeadragon.Options}), the new image is
  7443. * automatically arranged with the others.
  7444. * @function
  7445. * @param {Object} options
  7446. * @param {String|Object|Function} options.tileSource - The TileSource specifier.
  7447. * A String implies a url used to determine the tileSource implementation
  7448. * based on the file extension of url. JSONP is implied by *.js,
  7449. * otherwise the url is retrieved as text and the resulting text is
  7450. * introspected to determine if its json, xml, or text and parsed.
  7451. * An Object implies an inline configuration which has a single
  7452. * property sufficient for being able to determine tileSource
  7453. * implementation. If the object has a property which is a function
  7454. * named 'getTileUrl', it is treated as a custom TileSource.
  7455. * @param {Number} [options.index] The index of the item. Added on top of
  7456. * all other items if not specified.
  7457. * @param {Boolean} [options.replace=false] If true, the item at options.index will be
  7458. * removed and the new item is added in its place. options.tileSource will be
  7459. * interpreted and fetched if necessary before the old item is removed to avoid leaving
  7460. * a gap in the world.
  7461. * @param {Number} [options.x=0] The X position for the image in viewport coordinates.
  7462. * @param {Number} [options.y=0] The Y position for the image in viewport coordinates.
  7463. * @param {Number} [options.width=1] The width for the image in viewport coordinates.
  7464. * @param {Number} [options.height] The height for the image in viewport coordinates.
  7465. * @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates
  7466. * to fit the image into. If specified, x, y, width and height get ignored.
  7467. * @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER]
  7468. * How to anchor the image in the bounds if options.fitBounds is set.
  7469. * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to
  7470. * (portions of the image outside of this area will not be visible). Only works on
  7471. * browsers that support the HTML5 canvas.
  7472. * @param {Number} [options.opacity=1] Proportional opacity of the tiled images (1=opaque, 0=hidden)
  7473. * @param {Boolean} [options.preload=false] Default switch for loading hidden images (true loads, false blocks)
  7474. * @param {Number} [options.degrees=0] Initial rotation of the tiled image around
  7475. * its top left corner in degrees.
  7476. * @param {String} [options.compositeOperation] How the image is composited onto other images.
  7477. * @param {String} [options.crossOriginPolicy] The crossOriginPolicy for this specific image,
  7478. * overriding viewer.crossOriginPolicy.
  7479. * @param {Boolean} [options.ajaxWithCredentials] Whether to set withCredentials on tile AJAX
  7480. * @param {Boolean} [options.loadTilesWithAjax]
  7481. * Whether to load tile data using AJAX requests.
  7482. * Defaults to the setting in {@link OpenSeadragon.Options}.
  7483. * @param {Object} [options.ajaxHeaders]
  7484. * A set of headers to include when making tile AJAX requests.
  7485. * Note that these headers will be merged over any headers specified in {@link OpenSeadragon.Options}.
  7486. * Specifying a falsy value for a header will clear its existing value set at the Viewer level (if any).
  7487. * requests.
  7488. * @param {Function} [options.success] A function that gets called when the image is
  7489. * successfully added. It's passed the event object which contains a single property:
  7490. * "item", which is the resulting instance of TiledImage.
  7491. * @param {Function} [options.error] A function that gets called if the image is
  7492. * unable to be added. It's passed the error event object, which contains "message"
  7493. * and "source" properties.
  7494. * @param {Boolean} [options.collectionImmediately=false] If collectionMode is on,
  7495. * specifies whether to snap to the new arrangement immediately or to animate to it.
  7496. * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
  7497. * @fires OpenSeadragon.World.event:add-item
  7498. * @fires OpenSeadragon.Viewer.event:add-item-failed
  7499. */
  7500. addTiledImage: function( options ) {
  7501. $.console.assert(options, "[Viewer.addTiledImage] options is required");
  7502. $.console.assert(options.tileSource, "[Viewer.addTiledImage] options.tileSource is required");
  7503. $.console.assert(!options.replace || (options.index > -1 && options.index < this.world.getItemCount()),
  7504. "[Viewer.addTiledImage] if options.replace is used, options.index must be a valid index in Viewer.world");
  7505. var _this = this;
  7506. if (options.replace) {
  7507. options.replaceItem = _this.world.getItemAt(options.index);
  7508. }
  7509. this._hideMessage();
  7510. if (options.placeholderFillStyle === undefined) {
  7511. options.placeholderFillStyle = this.placeholderFillStyle;
  7512. }
  7513. if (options.opacity === undefined) {
  7514. options.opacity = this.opacity;
  7515. }
  7516. if (options.preload === undefined) {
  7517. options.preload = this.preload;
  7518. }
  7519. if (options.compositeOperation === undefined) {
  7520. options.compositeOperation = this.compositeOperation;
  7521. }
  7522. if (options.crossOriginPolicy === undefined) {
  7523. options.crossOriginPolicy = options.tileSource.crossOriginPolicy !== undefined ? options.tileSource.crossOriginPolicy : this.crossOriginPolicy;
  7524. }
  7525. if (options.ajaxWithCredentials === undefined) {
  7526. options.ajaxWithCredentials = this.ajaxWithCredentials;
  7527. }
  7528. if (options.loadTilesWithAjax === undefined) {
  7529. options.loadTilesWithAjax = this.loadTilesWithAjax;
  7530. }
  7531. if (options.ajaxHeaders === undefined || options.ajaxHeaders === null) {
  7532. options.ajaxHeaders = this.ajaxHeaders;
  7533. } else if ($.isPlainObject(options.ajaxHeaders) && $.isPlainObject(this.ajaxHeaders)) {
  7534. options.ajaxHeaders = $.extend({}, this.ajaxHeaders, options.ajaxHeaders);
  7535. }
  7536. var myQueueItem = {
  7537. options: options
  7538. };
  7539. function raiseAddItemFailed( event ) {
  7540. for (var i = 0; i < _this._loadQueue.length; i++) {
  7541. if (_this._loadQueue[i] === myQueueItem) {
  7542. _this._loadQueue.splice(i, 1);
  7543. break;
  7544. }
  7545. }
  7546. if (_this._loadQueue.length === 0) {
  7547. refreshWorld(myQueueItem);
  7548. }
  7549. /**
  7550. * Raised when an error occurs while adding a item.
  7551. * @event add-item-failed
  7552. * @memberOf OpenSeadragon.Viewer
  7553. * @type {object}
  7554. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  7555. * @property {String} message
  7556. * @property {String} source
  7557. * @property {Object} options The options passed to the addTiledImage method.
  7558. * @property {?Object} userData - Arbitrary subscriber-defined object.
  7559. */
  7560. _this.raiseEvent( 'add-item-failed', event );
  7561. if (options.error) {
  7562. options.error(event);
  7563. }
  7564. }
  7565. function refreshWorld(theItem) {
  7566. if (_this.collectionMode) {
  7567. _this.world.arrange({
  7568. immediately: theItem.options.collectionImmediately,
  7569. rows: _this.collectionRows,
  7570. columns: _this.collectionColumns,
  7571. layout: _this.collectionLayout,
  7572. tileSize: _this.collectionTileSize,
  7573. tileMargin: _this.collectionTileMargin
  7574. });
  7575. _this.world.setAutoRefigureSizes(true);
  7576. }
  7577. }
  7578. if ($.isArray(options.tileSource)) {
  7579. setTimeout(function() {
  7580. raiseAddItemFailed({
  7581. message: "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead.",
  7582. source: options.tileSource,
  7583. options: options
  7584. });
  7585. });
  7586. return;
  7587. }
  7588. this._loadQueue.push(myQueueItem);
  7589. function processReadyItems() {
  7590. var queueItem, tiledImage, optionsClone;
  7591. while (_this._loadQueue.length) {
  7592. queueItem = _this._loadQueue[0];
  7593. if (!queueItem.tileSource) {
  7594. break;
  7595. }
  7596. _this._loadQueue.splice(0, 1);
  7597. if (queueItem.options.replace) {
  7598. var newIndex = _this.world.getIndexOfItem(queueItem.options.replaceItem);
  7599. if (newIndex != -1) {
  7600. queueItem.options.index = newIndex;
  7601. }
  7602. _this.world.removeItem(queueItem.options.replaceItem);
  7603. }
  7604. tiledImage = new $.TiledImage({
  7605. viewer: _this,
  7606. source: queueItem.tileSource,
  7607. viewport: _this.viewport,
  7608. drawer: _this.drawer,
  7609. tileCache: _this.tileCache,
  7610. imageLoader: _this.imageLoader,
  7611. x: queueItem.options.x,
  7612. y: queueItem.options.y,
  7613. width: queueItem.options.width,
  7614. height: queueItem.options.height,
  7615. fitBounds: queueItem.options.fitBounds,
  7616. fitBoundsPlacement: queueItem.options.fitBoundsPlacement,
  7617. clip: queueItem.options.clip,
  7618. placeholderFillStyle: queueItem.options.placeholderFillStyle,
  7619. opacity: queueItem.options.opacity,
  7620. preload: queueItem.options.preload,
  7621. degrees: queueItem.options.degrees,
  7622. compositeOperation: queueItem.options.compositeOperation,
  7623. springStiffness: _this.springStiffness,
  7624. animationTime: _this.animationTime,
  7625. minZoomImageRatio: _this.minZoomImageRatio,
  7626. wrapHorizontal: _this.wrapHorizontal,
  7627. wrapVertical: _this.wrapVertical,
  7628. immediateRender: _this.immediateRender,
  7629. blendTime: _this.blendTime,
  7630. alwaysBlend: _this.alwaysBlend,
  7631. minPixelRatio: _this.minPixelRatio,
  7632. smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom,
  7633. iOSDevice: _this.iOSDevice,
  7634. crossOriginPolicy: queueItem.options.crossOriginPolicy,
  7635. ajaxWithCredentials: queueItem.options.ajaxWithCredentials,
  7636. loadTilesWithAjax: queueItem.options.loadTilesWithAjax,
  7637. ajaxHeaders: queueItem.options.ajaxHeaders,
  7638. debugMode: _this.debugMode
  7639. });
  7640. if (_this.collectionMode) {
  7641. _this.world.setAutoRefigureSizes(false);
  7642. }
  7643. _this.world.addItem( tiledImage, {
  7644. index: queueItem.options.index
  7645. });
  7646. if (_this._loadQueue.length === 0) {
  7647. //this restores the autoRefigureSizes flag to true.
  7648. refreshWorld(queueItem);
  7649. }
  7650. if (_this.world.getItemCount() === 1 && !_this.preserveViewport) {
  7651. _this.viewport.goHome(true);
  7652. }
  7653. if (_this.navigator) {
  7654. optionsClone = $.extend({}, queueItem.options, {
  7655. replace: false, // navigator already removed the layer, nothing to replace
  7656. originalTiledImage: tiledImage,
  7657. tileSource: queueItem.tileSource
  7658. });
  7659. _this.navigator.addTiledImage(optionsClone);
  7660. }
  7661. if (queueItem.options.success) {
  7662. queueItem.options.success({
  7663. item: tiledImage
  7664. });
  7665. }
  7666. }
  7667. }
  7668. getTileSourceImplementation( this, options.tileSource, options, function( tileSource ) {
  7669. myQueueItem.tileSource = tileSource;
  7670. // add everybody at the front of the queue that's ready to go
  7671. processReadyItems();
  7672. }, function( event ) {
  7673. event.options = options;
  7674. raiseAddItemFailed(event);
  7675. // add everybody at the front of the queue that's ready to go
  7676. processReadyItems();
  7677. } );
  7678. },
  7679. /**
  7680. * Add a simple image to the viewer.
  7681. * The options are the same as the ones in {@link OpenSeadragon.Viewer#addTiledImage}
  7682. * except for options.tileSource which is replaced by options.url.
  7683. * @function
  7684. * @param {Object} options - See {@link OpenSeadragon.Viewer#addTiledImage}
  7685. * for all the options
  7686. * @param {String} options.url - The URL of the image to add.
  7687. * @fires OpenSeadragon.World.event:add-item
  7688. * @fires OpenSeadragon.Viewer.event:add-item-failed
  7689. */
  7690. addSimpleImage: function(options) {
  7691. $.console.assert(options, "[Viewer.addSimpleImage] options is required");
  7692. $.console.assert(options.url, "[Viewer.addSimpleImage] options.url is required");
  7693. var opts = $.extend({}, options, {
  7694. tileSource: {
  7695. type: 'image',
  7696. url: options.url
  7697. }
  7698. });
  7699. delete opts.url;
  7700. this.addTiledImage(opts);
  7701. },
  7702. // deprecated
  7703. addLayer: function( options ) {
  7704. var _this = this;
  7705. $.console.error( "[Viewer.addLayer] this function is deprecated; use Viewer.addTiledImage() instead." );
  7706. var optionsClone = $.extend({}, options, {
  7707. success: function(event) {
  7708. _this.raiseEvent("add-layer", {
  7709. options: options,
  7710. drawer: event.item
  7711. });
  7712. },
  7713. error: function(event) {
  7714. _this.raiseEvent("add-layer-failed", event);
  7715. }
  7716. });
  7717. this.addTiledImage(optionsClone);
  7718. return this;
  7719. },
  7720. // deprecated
  7721. getLayerAtLevel: function( level ) {
  7722. $.console.error( "[Viewer.getLayerAtLevel] this function is deprecated; use World.getItemAt() instead." );
  7723. return this.world.getItemAt(level);
  7724. },
  7725. // deprecated
  7726. getLevelOfLayer: function( drawer ) {
  7727. $.console.error( "[Viewer.getLevelOfLayer] this function is deprecated; use World.getIndexOfItem() instead." );
  7728. return this.world.getIndexOfItem(drawer);
  7729. },
  7730. // deprecated
  7731. getLayersCount: function() {
  7732. $.console.error( "[Viewer.getLayersCount] this function is deprecated; use World.getItemCount() instead." );
  7733. return this.world.getItemCount();
  7734. },
  7735. // deprecated
  7736. setLayerLevel: function( drawer, level ) {
  7737. $.console.error( "[Viewer.setLayerLevel] this function is deprecated; use World.setItemIndex() instead." );
  7738. return this.world.setItemIndex(drawer, level);
  7739. },
  7740. // deprecated
  7741. removeLayer: function( drawer ) {
  7742. $.console.error( "[Viewer.removeLayer] this function is deprecated; use World.removeItem() instead." );
  7743. return this.world.removeItem(drawer);
  7744. },
  7745. /**
  7746. * Force the viewer to redraw its contents.
  7747. * @returns {OpenSeadragon.Viewer} Chainable.
  7748. */
  7749. forceRedraw: function() {
  7750. THIS[ this.hash ].forceRedraw = true;
  7751. return this;
  7752. },
  7753. /**
  7754. * @function
  7755. * @return {OpenSeadragon.Viewer} Chainable.
  7756. */
  7757. bindSequenceControls: function(){
  7758. //////////////////////////////////////////////////////////////////////////
  7759. // Image Sequence Controls
  7760. //////////////////////////////////////////////////////////////////////////
  7761. var onFocusHandler = $.delegate( this, onFocus ),
  7762. onBlurHandler = $.delegate( this, onBlur ),
  7763. onNextHandler = $.delegate( this, onNext ),
  7764. onPreviousHandler = $.delegate( this, onPrevious ),
  7765. navImages = this.navImages,
  7766. useGroup = true;
  7767. if( this.showSequenceControl ){
  7768. if( this.previousButton || this.nextButton ){
  7769. //if we are binding to custom buttons then layout and
  7770. //grouping is the responsibility of the page author
  7771. useGroup = false;
  7772. }
  7773. this.previousButton = new $.Button({
  7774. element: this.previousButton ? $.getElement( this.previousButton ) : null,
  7775. clickTimeThreshold: this.clickTimeThreshold,
  7776. clickDistThreshold: this.clickDistThreshold,
  7777. tooltip: $.getString( "Tooltips.PreviousPage" ),
  7778. srcRest: resolveUrl( this.prefixUrl, navImages.previous.REST ),
  7779. srcGroup: resolveUrl( this.prefixUrl, navImages.previous.GROUP ),
  7780. srcHover: resolveUrl( this.prefixUrl, navImages.previous.HOVER ),
  7781. srcDown: resolveUrl( this.prefixUrl, navImages.previous.DOWN ),
  7782. onRelease: onPreviousHandler,
  7783. onFocus: onFocusHandler,
  7784. onBlur: onBlurHandler
  7785. });
  7786. this.nextButton = new $.Button({
  7787. element: this.nextButton ? $.getElement( this.nextButton ) : null,
  7788. clickTimeThreshold: this.clickTimeThreshold,
  7789. clickDistThreshold: this.clickDistThreshold,
  7790. tooltip: $.getString( "Tooltips.NextPage" ),
  7791. srcRest: resolveUrl( this.prefixUrl, navImages.next.REST ),
  7792. srcGroup: resolveUrl( this.prefixUrl, navImages.next.GROUP ),
  7793. srcHover: resolveUrl( this.prefixUrl, navImages.next.HOVER ),
  7794. srcDown: resolveUrl( this.prefixUrl, navImages.next.DOWN ),
  7795. onRelease: onNextHandler,
  7796. onFocus: onFocusHandler,
  7797. onBlur: onBlurHandler
  7798. });
  7799. if( !this.navPrevNextWrap ){
  7800. this.previousButton.disable();
  7801. }
  7802. if (!this.tileSources || !this.tileSources.length) {
  7803. this.nextButton.disable();
  7804. }
  7805. if( useGroup ){
  7806. this.paging = new $.ButtonGroup({
  7807. buttons: [
  7808. this.previousButton,
  7809. this.nextButton
  7810. ],
  7811. clickTimeThreshold: this.clickTimeThreshold,
  7812. clickDistThreshold: this.clickDistThreshold
  7813. });
  7814. this.pagingControl = this.paging.element;
  7815. if( this.toolbar ){
  7816. this.toolbar.addControl(
  7817. this.pagingControl,
  7818. {anchor: $.ControlAnchor.BOTTOM_RIGHT}
  7819. );
  7820. }else{
  7821. this.addControl(
  7822. this.pagingControl,
  7823. {anchor: this.sequenceControlAnchor || $.ControlAnchor.TOP_LEFT}
  7824. );
  7825. }
  7826. }
  7827. }
  7828. return this;
  7829. },
  7830. /**
  7831. * @function
  7832. * @return {OpenSeadragon.Viewer} Chainable.
  7833. */
  7834. bindStandardControls: function(){
  7835. //////////////////////////////////////////////////////////////////////////
  7836. // Navigation Controls
  7837. //////////////////////////////////////////////////////////////////////////
  7838. var beginZoomingInHandler = $.delegate( this, beginZoomingIn ),
  7839. endZoomingHandler = $.delegate( this, endZooming ),
  7840. doSingleZoomInHandler = $.delegate( this, doSingleZoomIn ),
  7841. beginZoomingOutHandler = $.delegate( this, beginZoomingOut ),
  7842. doSingleZoomOutHandler = $.delegate( this, doSingleZoomOut ),
  7843. onHomeHandler = $.delegate( this, onHome ),
  7844. onFullScreenHandler = $.delegate( this, onFullScreen ),
  7845. onRotateLeftHandler = $.delegate( this, onRotateLeft ),
  7846. onRotateRightHandler = $.delegate( this, onRotateRight ),
  7847. onFlipHandler = $.delegate( this, onFlip),
  7848. onFocusHandler = $.delegate( this, onFocus ),
  7849. onBlurHandler = $.delegate( this, onBlur ),
  7850. navImages = this.navImages,
  7851. buttons = [],
  7852. useGroup = true;
  7853. if ( this.showNavigationControl ) {
  7854. if( this.zoomInButton || this.zoomOutButton ||
  7855. this.homeButton || this.fullPageButton ||
  7856. this.rotateLeftButton || this.rotateRightButton ||
  7857. this.flipButton ) {
  7858. //if we are binding to custom buttons then layout and
  7859. //grouping is the responsibility of the page author
  7860. useGroup = false;
  7861. }
  7862. if ( this.showZoomControl ) {
  7863. buttons.push( this.zoomInButton = new $.Button({
  7864. element: this.zoomInButton ? $.getElement( this.zoomInButton ) : null,
  7865. clickTimeThreshold: this.clickTimeThreshold,
  7866. clickDistThreshold: this.clickDistThreshold,
  7867. tooltip: $.getString( "Tooltips.ZoomIn" ),
  7868. srcRest: resolveUrl( this.prefixUrl, navImages.zoomIn.REST ),
  7869. srcGroup: resolveUrl( this.prefixUrl, navImages.zoomIn.GROUP ),
  7870. srcHover: resolveUrl( this.prefixUrl, navImages.zoomIn.HOVER ),
  7871. srcDown: resolveUrl( this.prefixUrl, navImages.zoomIn.DOWN ),
  7872. onPress: beginZoomingInHandler,
  7873. onRelease: endZoomingHandler,
  7874. onClick: doSingleZoomInHandler,
  7875. onEnter: beginZoomingInHandler,
  7876. onExit: endZoomingHandler,
  7877. onFocus: onFocusHandler,
  7878. onBlur: onBlurHandler
  7879. }));
  7880. buttons.push( this.zoomOutButton = new $.Button({
  7881. element: this.zoomOutButton ? $.getElement( this.zoomOutButton ) : null,
  7882. clickTimeThreshold: this.clickTimeThreshold,
  7883. clickDistThreshold: this.clickDistThreshold,
  7884. tooltip: $.getString( "Tooltips.ZoomOut" ),
  7885. srcRest: resolveUrl( this.prefixUrl, navImages.zoomOut.REST ),
  7886. srcGroup: resolveUrl( this.prefixUrl, navImages.zoomOut.GROUP ),
  7887. srcHover: resolveUrl( this.prefixUrl, navImages.zoomOut.HOVER ),
  7888. srcDown: resolveUrl( this.prefixUrl, navImages.zoomOut.DOWN ),
  7889. onPress: beginZoomingOutHandler,
  7890. onRelease: endZoomingHandler,
  7891. onClick: doSingleZoomOutHandler,
  7892. onEnter: beginZoomingOutHandler,
  7893. onExit: endZoomingHandler,
  7894. onFocus: onFocusHandler,
  7895. onBlur: onBlurHandler
  7896. }));
  7897. }
  7898. if ( this.showHomeControl ) {
  7899. buttons.push( this.homeButton = new $.Button({
  7900. element: this.homeButton ? $.getElement( this.homeButton ) : null,
  7901. clickTimeThreshold: this.clickTimeThreshold,
  7902. clickDistThreshold: this.clickDistThreshold,
  7903. tooltip: $.getString( "Tooltips.Home" ),
  7904. srcRest: resolveUrl( this.prefixUrl, navImages.home.REST ),
  7905. srcGroup: resolveUrl( this.prefixUrl, navImages.home.GROUP ),
  7906. srcHover: resolveUrl( this.prefixUrl, navImages.home.HOVER ),
  7907. srcDown: resolveUrl( this.prefixUrl, navImages.home.DOWN ),
  7908. onRelease: onHomeHandler,
  7909. onFocus: onFocusHandler,
  7910. onBlur: onBlurHandler
  7911. }));
  7912. }
  7913. if ( this.showFullPageControl ) {
  7914. buttons.push( this.fullPageButton = new $.Button({
  7915. element: this.fullPageButton ? $.getElement( this.fullPageButton ) : null,
  7916. clickTimeThreshold: this.clickTimeThreshold,
  7917. clickDistThreshold: this.clickDistThreshold,
  7918. tooltip: $.getString( "Tooltips.FullPage" ),
  7919. srcRest: resolveUrl( this.prefixUrl, navImages.fullpage.REST ),
  7920. srcGroup: resolveUrl( this.prefixUrl, navImages.fullpage.GROUP ),
  7921. srcHover: resolveUrl( this.prefixUrl, navImages.fullpage.HOVER ),
  7922. srcDown: resolveUrl( this.prefixUrl, navImages.fullpage.DOWN ),
  7923. onRelease: onFullScreenHandler,
  7924. onFocus: onFocusHandler,
  7925. onBlur: onBlurHandler
  7926. }));
  7927. }
  7928. if ( this.showRotationControl ) {
  7929. buttons.push( this.rotateLeftButton = new $.Button({
  7930. element: this.rotateLeftButton ? $.getElement( this.rotateLeftButton ) : null,
  7931. clickTimeThreshold: this.clickTimeThreshold,
  7932. clickDistThreshold: this.clickDistThreshold,
  7933. tooltip: $.getString( "Tooltips.RotateLeft" ),
  7934. srcRest: resolveUrl( this.prefixUrl, navImages.rotateleft.REST ),
  7935. srcGroup: resolveUrl( this.prefixUrl, navImages.rotateleft.GROUP ),
  7936. srcHover: resolveUrl( this.prefixUrl, navImages.rotateleft.HOVER ),
  7937. srcDown: resolveUrl( this.prefixUrl, navImages.rotateleft.DOWN ),
  7938. onRelease: onRotateLeftHandler,
  7939. onFocus: onFocusHandler,
  7940. onBlur: onBlurHandler
  7941. }));
  7942. buttons.push( this.rotateRightButton = new $.Button({
  7943. element: this.rotateRightButton ? $.getElement( this.rotateRightButton ) : null,
  7944. clickTimeThreshold: this.clickTimeThreshold,
  7945. clickDistThreshold: this.clickDistThreshold,
  7946. tooltip: $.getString( "Tooltips.RotateRight" ),
  7947. srcRest: resolveUrl( this.prefixUrl, navImages.rotateright.REST ),
  7948. srcGroup: resolveUrl( this.prefixUrl, navImages.rotateright.GROUP ),
  7949. srcHover: resolveUrl( this.prefixUrl, navImages.rotateright.HOVER ),
  7950. srcDown: resolveUrl( this.prefixUrl, navImages.rotateright.DOWN ),
  7951. onRelease: onRotateRightHandler,
  7952. onFocus: onFocusHandler,
  7953. onBlur: onBlurHandler
  7954. }));
  7955. }
  7956. if ( this.showFlipControl ) {
  7957. buttons.push( this.flipButton = new $.Button({
  7958. element: this.flipButton ? $.getElement( this.flipButton ) : null,
  7959. clickTimeThreshold: this.clickTimeThreshold,
  7960. clickDistThreshold: this.clickDistThreshold,
  7961. tooltip: $.getString( "Tooltips.Flip" ),
  7962. srcRest: resolveUrl( this.prefixUrl, navImages.flip.REST ),
  7963. srcGroup: resolveUrl( this.prefixUrl, navImages.flip.GROUP ),
  7964. srcHover: resolveUrl( this.prefixUrl, navImages.flip.HOVER ),
  7965. srcDown: resolveUrl( this.prefixUrl, navImages.flip.DOWN ),
  7966. onRelease: onFlipHandler,
  7967. onFocus: onFocusHandler,
  7968. onBlur: onBlurHandler
  7969. }));
  7970. }
  7971. if ( useGroup ) {
  7972. this.buttons = new $.ButtonGroup({
  7973. buttons: buttons,
  7974. clickTimeThreshold: this.clickTimeThreshold,
  7975. clickDistThreshold: this.clickDistThreshold
  7976. });
  7977. this.navControl = this.buttons.element;
  7978. this.addHandler( 'open', $.delegate( this, lightUp ) );
  7979. if( this.toolbar ){
  7980. this.toolbar.addControl(
  7981. this.navControl,
  7982. {anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT}
  7983. );
  7984. } else {
  7985. this.addControl(
  7986. this.navControl,
  7987. {anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT}
  7988. );
  7989. }
  7990. }
  7991. }
  7992. return this;
  7993. },
  7994. /**
  7995. * Gets the active page of a sequence
  7996. * @function
  7997. * @return {Number}
  7998. */
  7999. currentPage: function() {
  8000. return this._sequenceIndex;
  8001. },
  8002. /**
  8003. * @function
  8004. * @return {OpenSeadragon.Viewer} Chainable.
  8005. * @fires OpenSeadragon.Viewer.event:page
  8006. */
  8007. goToPage: function( page ){
  8008. if( this.tileSources && page >= 0 && page < this.tileSources.length ){
  8009. this._sequenceIndex = page;
  8010. this._updateSequenceButtons( page );
  8011. this.open( this.tileSources[ page ] );
  8012. if( this.referenceStrip ){
  8013. this.referenceStrip.setFocus( page );
  8014. }
  8015. /**
  8016. * Raised when the page is changed on a viewer configured with multiple image sources (see {@link OpenSeadragon.Viewer#goToPage}).
  8017. *
  8018. * @event page
  8019. * @memberof OpenSeadragon.Viewer
  8020. * @type {Object}
  8021. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  8022. * @property {Number} page - The page index.
  8023. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8024. */
  8025. this.raiseEvent( 'page', { page: page } );
  8026. }
  8027. return this;
  8028. },
  8029. /**
  8030. * Adds an html element as an overlay to the current viewport. Useful for
  8031. * highlighting words or areas of interest on an image or other zoomable
  8032. * interface. The overlays added via this method are removed when the viewport
  8033. * is closed which include when changing page.
  8034. * @method
  8035. * @param {Element|String|Object} element - A reference to an element or an id for
  8036. * the element which will be overlaid. Or an Object specifying the configuration for the overlay.
  8037. * If using an object, see {@link OpenSeadragon.Overlay} for a list of
  8038. * all available options.
  8039. * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
  8040. * rectangle which will be overlaid. This is a viewport relative location.
  8041. * @param {OpenSeadragon.Placement} placement - The position of the
  8042. * viewport which the location coordinates will be treated as relative
  8043. * to.
  8044. * @param {function} onDraw - If supplied the callback is called when the overlay
  8045. * needs to be drawn. It it the responsibility of the callback to do any drawing/positioning.
  8046. * It is passed position, size and element.
  8047. * @return {OpenSeadragon.Viewer} Chainable.
  8048. * @fires OpenSeadragon.Viewer.event:add-overlay
  8049. */
  8050. addOverlay: function( element, location, placement, onDraw ) {
  8051. var options;
  8052. if( $.isPlainObject( element ) ){
  8053. options = element;
  8054. } else {
  8055. options = {
  8056. element: element,
  8057. location: location,
  8058. placement: placement,
  8059. onDraw: onDraw
  8060. };
  8061. }
  8062. element = $.getElement( options.element );
  8063. if ( getOverlayIndex( this.currentOverlays, element ) >= 0 ) {
  8064. // they're trying to add a duplicate overlay
  8065. return this;
  8066. }
  8067. var overlay = getOverlayObject( this, options);
  8068. this.currentOverlays.push(overlay);
  8069. overlay.drawHTML( this.overlaysContainer, this.viewport );
  8070. /**
  8071. * Raised when an overlay is added to the viewer (see {@link OpenSeadragon.Viewer#addOverlay}).
  8072. *
  8073. * @event add-overlay
  8074. * @memberof OpenSeadragon.Viewer
  8075. * @type {object}
  8076. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  8077. * @property {Element} element - The overlay element.
  8078. * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location
  8079. * @property {OpenSeadragon.Placement} placement
  8080. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8081. */
  8082. this.raiseEvent( 'add-overlay', {
  8083. element: element,
  8084. location: options.location,
  8085. placement: options.placement
  8086. });
  8087. return this;
  8088. },
  8089. /**
  8090. * Updates the overlay represented by the reference to the element or
  8091. * element id moving it to the new location, relative to the new placement.
  8092. * @method
  8093. * @param {Element|String} element - A reference to an element or an id for
  8094. * the element which is overlaid.
  8095. * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
  8096. * rectangle which will be overlaid. This is a viewport relative location.
  8097. * @param {OpenSeadragon.Placement} placement - The position of the
  8098. * viewport which the location coordinates will be treated as relative
  8099. * to.
  8100. * @return {OpenSeadragon.Viewer} Chainable.
  8101. * @fires OpenSeadragon.Viewer.event:update-overlay
  8102. */
  8103. updateOverlay: function( element, location, placement ) {
  8104. var i;
  8105. element = $.getElement( element );
  8106. i = getOverlayIndex( this.currentOverlays, element );
  8107. if ( i >= 0 ) {
  8108. this.currentOverlays[ i ].update( location, placement );
  8109. THIS[ this.hash ].forceRedraw = true;
  8110. /**
  8111. * Raised when an overlay's location or placement changes
  8112. * (see {@link OpenSeadragon.Viewer#updateOverlay}).
  8113. *
  8114. * @event update-overlay
  8115. * @memberof OpenSeadragon.Viewer
  8116. * @type {object}
  8117. * @property {OpenSeadragon.Viewer} eventSource - A reference to the
  8118. * Viewer which raised the event.
  8119. * @property {Element} element
  8120. * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location
  8121. * @property {OpenSeadragon.Placement} placement
  8122. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8123. */
  8124. this.raiseEvent( 'update-overlay', {
  8125. element: element,
  8126. location: location,
  8127. placement: placement
  8128. });
  8129. }
  8130. return this;
  8131. },
  8132. /**
  8133. * Removes an overlay identified by the reference element or element id
  8134. * and schedules an update.
  8135. * @method
  8136. * @param {Element|String} element - A reference to the element or an
  8137. * element id which represent the ovelay content to be removed.
  8138. * @return {OpenSeadragon.Viewer} Chainable.
  8139. * @fires OpenSeadragon.Viewer.event:remove-overlay
  8140. */
  8141. removeOverlay: function( element ) {
  8142. var i;
  8143. element = $.getElement( element );
  8144. i = getOverlayIndex( this.currentOverlays, element );
  8145. if ( i >= 0 ) {
  8146. this.currentOverlays[ i ].destroy();
  8147. this.currentOverlays.splice( i, 1 );
  8148. THIS[ this.hash ].forceRedraw = true;
  8149. /**
  8150. * Raised when an overlay is removed from the viewer
  8151. * (see {@link OpenSeadragon.Viewer#removeOverlay}).
  8152. *
  8153. * @event remove-overlay
  8154. * @memberof OpenSeadragon.Viewer
  8155. * @type {object}
  8156. * @property {OpenSeadragon.Viewer} eventSource - A reference to the
  8157. * Viewer which raised the event.
  8158. * @property {Element} element - The overlay element.
  8159. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8160. */
  8161. this.raiseEvent( 'remove-overlay', {
  8162. element: element
  8163. });
  8164. }
  8165. return this;
  8166. },
  8167. /**
  8168. * Removes all currently configured Overlays from this Viewer and schedules
  8169. * an update.
  8170. * @method
  8171. * @return {OpenSeadragon.Viewer} Chainable.
  8172. * @fires OpenSeadragon.Viewer.event:clear-overlay
  8173. */
  8174. clearOverlays: function() {
  8175. while ( this.currentOverlays.length > 0 ) {
  8176. this.currentOverlays.pop().destroy();
  8177. }
  8178. THIS[ this.hash ].forceRedraw = true;
  8179. /**
  8180. * Raised when all overlays are removed from the viewer (see {@link OpenSeadragon.Drawer#clearOverlays}).
  8181. *
  8182. * @event clear-overlay
  8183. * @memberof OpenSeadragon.Viewer
  8184. * @type {object}
  8185. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  8186. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8187. */
  8188. this.raiseEvent( 'clear-overlay', {} );
  8189. return this;
  8190. },
  8191. /**
  8192. * Finds an overlay identified by the reference element or element id
  8193. * and returns it as an object, return null if not found.
  8194. * @method
  8195. * @param {Element|String} element - A reference to the element or an
  8196. * element id which represents the overlay content.
  8197. * @return {OpenSeadragon.Overlay} the matching overlay or null if none found.
  8198. */
  8199. getOverlayById: function( element ) {
  8200. var i;
  8201. element = $.getElement( element );
  8202. i = getOverlayIndex( this.currentOverlays, element );
  8203. if (i >= 0) {
  8204. return this.currentOverlays[i];
  8205. } else {
  8206. return null;
  8207. }
  8208. },
  8209. /**
  8210. * Updates the sequence buttons.
  8211. * @function OpenSeadragon.Viewer.prototype._updateSequenceButtons
  8212. * @private
  8213. * @param {Number} Sequence Value
  8214. */
  8215. _updateSequenceButtons: function( page ) {
  8216. if ( this.nextButton ) {
  8217. if(!this.tileSources || this.tileSources.length - 1 === page) {
  8218. //Disable next button
  8219. if ( !this.navPrevNextWrap ) {
  8220. this.nextButton.disable();
  8221. }
  8222. } else {
  8223. this.nextButton.enable();
  8224. }
  8225. }
  8226. if ( this.previousButton ) {
  8227. if ( page > 0 ) {
  8228. //Enable previous button
  8229. this.previousButton.enable();
  8230. } else {
  8231. if ( !this.navPrevNextWrap ) {
  8232. this.previousButton.disable();
  8233. }
  8234. }
  8235. }
  8236. },
  8237. /**
  8238. * Display a message in the viewport
  8239. * @function OpenSeadragon.Viewer.prototype._showMessage
  8240. * @private
  8241. * @param {String} text message
  8242. */
  8243. _showMessage: function ( message ) {
  8244. this._hideMessage();
  8245. var div = $.makeNeutralElement( "div" );
  8246. div.appendChild( document.createTextNode( message ) );
  8247. this.messageDiv = $.makeCenteredNode( div );
  8248. $.addClass(this.messageDiv, "openseadragon-message");
  8249. this.container.appendChild( this.messageDiv );
  8250. },
  8251. /**
  8252. * Hide any currently displayed viewport message
  8253. * @function OpenSeadragon.Viewer.prototype._hideMessage
  8254. * @private
  8255. */
  8256. _hideMessage: function () {
  8257. var div = this.messageDiv;
  8258. if (div) {
  8259. div.parentNode.removeChild(div);
  8260. delete this.messageDiv;
  8261. }
  8262. },
  8263. /**
  8264. * Gets this viewer's gesture settings for the given pointer device type.
  8265. * @method
  8266. * @param {String} type - The pointer device type to get the gesture settings for ("mouse", "touch", "pen", etc.).
  8267. * @return {OpenSeadragon.GestureSettings}
  8268. */
  8269. gestureSettingsByDeviceType: function ( type ) {
  8270. switch ( type ) {
  8271. case 'mouse':
  8272. return this.gestureSettingsMouse;
  8273. case 'touch':
  8274. return this.gestureSettingsTouch;
  8275. case 'pen':
  8276. return this.gestureSettingsPen;
  8277. default:
  8278. return this.gestureSettingsUnknown;
  8279. }
  8280. },
  8281. // private
  8282. _drawOverlays: function() {
  8283. var i,
  8284. length = this.currentOverlays.length;
  8285. for ( i = 0; i < length; i++ ) {
  8286. this.currentOverlays[ i ].drawHTML( this.overlaysContainer, this.viewport );
  8287. }
  8288. },
  8289. /**
  8290. * Cancel the "in flight" images.
  8291. */
  8292. _cancelPendingImages: function() {
  8293. this._loadQueue = [];
  8294. },
  8295. /**
  8296. * Removes the reference strip and disables displaying it.
  8297. * @function
  8298. */
  8299. removeReferenceStrip: function() {
  8300. this.showReferenceStrip = false;
  8301. if (this.referenceStrip) {
  8302. this.referenceStrip.destroy();
  8303. this.referenceStrip = null;
  8304. }
  8305. },
  8306. /**
  8307. * Enables and displays the reference strip based on the currently set tileSources.
  8308. * Works only when the Viewer has sequenceMode set to true.
  8309. * @function
  8310. */
  8311. addReferenceStrip: function() {
  8312. this.showReferenceStrip = true;
  8313. if (this.sequenceMode) {
  8314. if (this.referenceStrip) {
  8315. return;
  8316. }
  8317. if (this.tileSources.length && this.tileSources.length > 1) {
  8318. this.referenceStrip = new $.ReferenceStrip({
  8319. id: this.referenceStripElement,
  8320. position: this.referenceStripPosition,
  8321. sizeRatio: this.referenceStripSizeRatio,
  8322. scroll: this.referenceStripScroll,
  8323. height: this.referenceStripHeight,
  8324. width: this.referenceStripWidth,
  8325. tileSources: this.tileSources,
  8326. prefixUrl: this.prefixUrl,
  8327. viewer: this
  8328. });
  8329. this.referenceStrip.setFocus( this._sequenceIndex );
  8330. }
  8331. } else {
  8332. $.console.warn('Attempting to display a reference strip while "sequenceMode" is off.');
  8333. }
  8334. }
  8335. });
  8336. /**
  8337. * _getSafeElemSize is like getElementSize(), but refuses to return 0 for x or y,
  8338. * which was causing some calling operations to return NaN.
  8339. * @returns {Point}
  8340. * @private
  8341. */
  8342. function _getSafeElemSize (oElement) {
  8343. oElement = $.getElement( oElement );
  8344. return new $.Point(
  8345. (oElement.clientWidth === 0 ? 1 : oElement.clientWidth),
  8346. (oElement.clientHeight === 0 ? 1 : oElement.clientHeight)
  8347. );
  8348. }
  8349. /**
  8350. * @function
  8351. * @private
  8352. */
  8353. function getTileSourceImplementation( viewer, tileSource, imgOptions, successCallback,
  8354. failCallback ) {
  8355. var _this = viewer;
  8356. //allow plain xml strings or json strings to be parsed here
  8357. if ( $.type( tileSource ) == 'string' ) {
  8358. //xml should start with "<" and end with ">"
  8359. if ( tileSource.match( /^\s*<.*>\s*$/ ) ) {
  8360. tileSource = $.parseXml( tileSource );
  8361. //json should start with "{" or "[" and end with "}" or "]"
  8362. } else if ( tileSource.match(/^\s*[\{\[].*[\}\]]\s*$/ ) ) {
  8363. try {
  8364. var tileSourceJ = $.parseJSON(tileSource);
  8365. tileSource = tileSourceJ;
  8366. } catch (e) {
  8367. //tileSource = tileSource;
  8368. }
  8369. }
  8370. }
  8371. function waitUntilReady(tileSource, originalTileSource) {
  8372. if (tileSource.ready) {
  8373. successCallback(tileSource);
  8374. } else {
  8375. tileSource.addHandler('ready', function () {
  8376. successCallback(tileSource);
  8377. });
  8378. tileSource.addHandler('open-failed', function (event) {
  8379. failCallback({
  8380. message: event.message,
  8381. source: originalTileSource
  8382. });
  8383. });
  8384. }
  8385. }
  8386. setTimeout( function() {
  8387. if ( $.type( tileSource ) == 'string' ) {
  8388. //If its still a string it means it must be a url at this point
  8389. tileSource = new $.TileSource({
  8390. url: tileSource,
  8391. crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ?
  8392. imgOptions.crossOriginPolicy : viewer.crossOriginPolicy,
  8393. ajaxWithCredentials: viewer.ajaxWithCredentials,
  8394. ajaxHeaders: viewer.ajaxHeaders,
  8395. useCanvas: viewer.useCanvas,
  8396. success: function( event ) {
  8397. successCallback( event.tileSource );
  8398. }
  8399. });
  8400. tileSource.addHandler( 'open-failed', function( event ) {
  8401. failCallback( event );
  8402. } );
  8403. } else if ($.isPlainObject(tileSource) || tileSource.nodeType) {
  8404. if (tileSource.crossOriginPolicy === undefined &&
  8405. (imgOptions.crossOriginPolicy !== undefined || viewer.crossOriginPolicy !== undefined)) {
  8406. tileSource.crossOriginPolicy = imgOptions.crossOriginPolicy !== undefined ?
  8407. imgOptions.crossOriginPolicy : viewer.crossOriginPolicy;
  8408. }
  8409. if (tileSource.ajaxWithCredentials === undefined) {
  8410. tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials;
  8411. }
  8412. if (tileSource.useCanvas === undefined) {
  8413. tileSource.useCanvas = viewer.useCanvas;
  8414. }
  8415. if ( $.isFunction( tileSource.getTileUrl ) ) {
  8416. //Custom tile source
  8417. var customTileSource = new $.TileSource( tileSource );
  8418. customTileSource.getTileUrl = tileSource.getTileUrl;
  8419. successCallback( customTileSource );
  8420. } else {
  8421. //inline configuration
  8422. var $TileSource = $.TileSource.determineType( _this, tileSource );
  8423. if ( !$TileSource ) {
  8424. failCallback( {
  8425. message: "Unable to load TileSource",
  8426. source: tileSource
  8427. });
  8428. return;
  8429. }
  8430. var options = $TileSource.prototype.configure.apply( _this, [ tileSource ] );
  8431. waitUntilReady(new $TileSource(options), tileSource);
  8432. }
  8433. } else {
  8434. //can assume it's already a tile source implementation
  8435. waitUntilReady(tileSource, tileSource);
  8436. }
  8437. });
  8438. }
  8439. function getOverlayObject( viewer, overlay ) {
  8440. if ( overlay instanceof $.Overlay ) {
  8441. return overlay;
  8442. }
  8443. var element = null;
  8444. if ( overlay.element ) {
  8445. element = $.getElement( overlay.element );
  8446. } else {
  8447. var id = overlay.id ?
  8448. overlay.id :
  8449. "openseadragon-overlay-" + Math.floor( Math.random() * 10000000 );
  8450. element = $.getElement( overlay.id );
  8451. if ( !element ) {
  8452. element = document.createElement( "a" );
  8453. element.href = "#/overlay/" + id;
  8454. }
  8455. element.id = id;
  8456. $.addClass( element, overlay.className ?
  8457. overlay.className :
  8458. "openseadragon-overlay"
  8459. );
  8460. }
  8461. var location = overlay.location;
  8462. var width = overlay.width;
  8463. var height = overlay.height;
  8464. if (!location) {
  8465. var x = overlay.x;
  8466. var y = overlay.y;
  8467. if (overlay.px !== undefined) {
  8468. var rect = viewer.viewport.imageToViewportRectangle(new $.Rect(
  8469. overlay.px,
  8470. overlay.py,
  8471. width || 0,
  8472. height || 0));
  8473. x = rect.x;
  8474. y = rect.y;
  8475. width = width !== undefined ? rect.width : undefined;
  8476. height = height !== undefined ? rect.height : undefined;
  8477. }
  8478. location = new $.Point(x, y);
  8479. }
  8480. var placement = overlay.placement;
  8481. if (placement && $.type(placement) === "string") {
  8482. placement = $.Placement[overlay.placement.toUpperCase()];
  8483. }
  8484. return new $.Overlay({
  8485. element: element,
  8486. location: location,
  8487. placement: placement,
  8488. onDraw: overlay.onDraw,
  8489. checkResize: overlay.checkResize,
  8490. width: width,
  8491. height: height,
  8492. rotationMode: overlay.rotationMode
  8493. });
  8494. }
  8495. /**
  8496. * @private
  8497. * @inner
  8498. * Determines the index of the given overlay in the given overlays array.
  8499. */
  8500. function getOverlayIndex( overlays, element ) {
  8501. var i;
  8502. for ( i = overlays.length - 1; i >= 0; i-- ) {
  8503. if ( overlays[ i ].element === element ) {
  8504. return i;
  8505. }
  8506. }
  8507. return -1;
  8508. }
  8509. ///////////////////////////////////////////////////////////////////////////////
  8510. // Schedulers provide the general engine for animation
  8511. ///////////////////////////////////////////////////////////////////////////////
  8512. function scheduleUpdate( viewer, updateFunc ){
  8513. return $.requestAnimationFrame( function(){
  8514. updateFunc( viewer );
  8515. } );
  8516. }
  8517. //provides a sequence in the fade animation
  8518. function scheduleControlsFade( viewer ) {
  8519. $.requestAnimationFrame( function(){
  8520. updateControlsFade( viewer );
  8521. });
  8522. }
  8523. //initiates an animation to hide the controls
  8524. function beginControlsAutoHide( viewer ) {
  8525. if ( !viewer.autoHideControls ) {
  8526. return;
  8527. }
  8528. viewer.controlsShouldFade = true;
  8529. viewer.controlsFadeBeginTime =
  8530. $.now() +
  8531. viewer.controlsFadeDelay;
  8532. window.setTimeout( function(){
  8533. scheduleControlsFade( viewer );
  8534. }, viewer.controlsFadeDelay );
  8535. }
  8536. //determines if fade animation is done or continues the animation
  8537. function updateControlsFade( viewer ) {
  8538. var currentTime,
  8539. deltaTime,
  8540. opacity,
  8541. i;
  8542. if ( viewer.controlsShouldFade ) {
  8543. currentTime = $.now();
  8544. deltaTime = currentTime - viewer.controlsFadeBeginTime;
  8545. opacity = 1.0 - deltaTime / viewer.controlsFadeLength;
  8546. opacity = Math.min( 1.0, opacity );
  8547. opacity = Math.max( 0.0, opacity );
  8548. for ( i = viewer.controls.length - 1; i >= 0; i--) {
  8549. if (viewer.controls[ i ].autoFade) {
  8550. viewer.controls[ i ].setOpacity( opacity );
  8551. }
  8552. }
  8553. if ( opacity > 0 ) {
  8554. // fade again
  8555. scheduleControlsFade( viewer );
  8556. }
  8557. }
  8558. }
  8559. //stop the fade animation on the controls and show them
  8560. function abortControlsAutoHide( viewer ) {
  8561. var i;
  8562. viewer.controlsShouldFade = false;
  8563. for ( i = viewer.controls.length - 1; i >= 0; i-- ) {
  8564. viewer.controls[ i ].setOpacity( 1.0 );
  8565. }
  8566. }
  8567. ///////////////////////////////////////////////////////////////////////////////
  8568. // Default view event handlers.
  8569. ///////////////////////////////////////////////////////////////////////////////
  8570. function onFocus(){
  8571. abortControlsAutoHide( this );
  8572. }
  8573. function onBlur(){
  8574. beginControlsAutoHide( this );
  8575. }
  8576. function onCanvasKeyDown( event ) {
  8577. var canvasKeyDownEventArgs = {
  8578. originalEvent: event.originalEvent,
  8579. preventDefaultAction: event.preventDefaultAction,
  8580. preventVerticalPan: event.preventVerticalPan,
  8581. preventHorizontalPan: event.preventHorizontalPan
  8582. };
  8583. /**
  8584. * Raised when a keyboard key is pressed and the focus is on the {@link OpenSeadragon.Viewer#canvas} element.
  8585. *
  8586. * @event canvas-key
  8587. * @memberof OpenSeadragon.Viewer
  8588. * @type {object}
  8589. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  8590. * @property {Object} originalEvent - The original DOM event.
  8591. * @property {Boolean} preventDefaultAction - Set to true to prevent default keyboard behaviour. Default: false.
  8592. * @property {Boolean} preventVerticalPan - Set to true to prevent keyboard vertical panning. Default: false.
  8593. * @property {Boolean} preventHorizontalPan - Set to true to prevent keyboard horizontal panning. Default: false.
  8594. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8595. */
  8596. this.raiseEvent('canvas-key', canvasKeyDownEventArgs);
  8597. if ( !canvasKeyDownEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
  8598. switch( event.keyCode ){
  8599. case 38://up arrow
  8600. if (!canvasKeyDownEventArgs.preventVerticalPan) {
  8601. if ( event.shift ) {
  8602. this.viewport.zoomBy(1.1);
  8603. } else {
  8604. this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -this.pixelsPerArrowPress)));
  8605. }
  8606. this.viewport.applyConstraints();
  8607. }
  8608. return false;
  8609. case 40://down arrow
  8610. if (!canvasKeyDownEventArgs.preventVerticalPan) {
  8611. if ( event.shift ) {
  8612. this.viewport.zoomBy(0.9);
  8613. } else {
  8614. this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, this.pixelsPerArrowPress)));
  8615. }
  8616. this.viewport.applyConstraints();
  8617. }
  8618. return false;
  8619. case 37://left arrow
  8620. if (!canvasKeyDownEventArgs.preventHorizontalPan) {
  8621. this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-this.pixelsPerArrowPress, 0)));
  8622. this.viewport.applyConstraints();
  8623. }
  8624. return false;
  8625. case 39://right arrow
  8626. if (!canvasKeyDownEventArgs.preventHorizontalPan) {
  8627. this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(this.pixelsPerArrowPress, 0)));
  8628. this.viewport.applyConstraints();
  8629. }
  8630. return false;
  8631. default:
  8632. //console.log( 'navigator keycode %s', event.keyCode );
  8633. return true;
  8634. }
  8635. } else {
  8636. return true;
  8637. }
  8638. }
  8639. function onCanvasKeyPress( event ) {
  8640. var canvasKeyPressEventArgs = {
  8641. originalEvent: event.originalEvent,
  8642. preventDefaultAction: event.preventDefaultAction,
  8643. preventVerticalPan: event.preventVerticalPan,
  8644. preventHorizontalPan: event.preventHorizontalPan
  8645. };
  8646. // This event is documented in onCanvasKeyDown
  8647. this.raiseEvent('canvas-key', canvasKeyPressEventArgs);
  8648. if ( !canvasKeyPressEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
  8649. switch( event.keyCode ){
  8650. case 43://=|+
  8651. case 61://=|+
  8652. this.viewport.zoomBy(1.1);
  8653. this.viewport.applyConstraints();
  8654. return false;
  8655. case 45://-|_
  8656. this.viewport.zoomBy(0.9);
  8657. this.viewport.applyConstraints();
  8658. return false;
  8659. case 48://0|)
  8660. this.viewport.goHome();
  8661. this.viewport.applyConstraints();
  8662. return false;
  8663. case 119://w
  8664. case 87://W
  8665. if (!canvasKeyPressEventArgs.preventVerticalPan) {
  8666. if ( event.shift ) {
  8667. this.viewport.zoomBy(1.1);
  8668. } else {
  8669. this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
  8670. }
  8671. this.viewport.applyConstraints();
  8672. }
  8673. return false;
  8674. case 115://s
  8675. case 83://S
  8676. if (!canvasKeyPressEventArgs.preventVerticalPan) {
  8677. if ( event.shift ) {
  8678. this.viewport.zoomBy(0.9);
  8679. } else {
  8680. this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
  8681. }
  8682. this.viewport.applyConstraints();
  8683. }
  8684. return false;
  8685. case 97://a
  8686. if (!canvasKeyPressEventArgs.preventHorizontalPan) {
  8687. this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0)));
  8688. this.viewport.applyConstraints();
  8689. }
  8690. return false;
  8691. case 100://d
  8692. if (!canvasKeyPressEventArgs.preventHorizontalPan) {
  8693. this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
  8694. this.viewport.applyConstraints();
  8695. }
  8696. return false;
  8697. case 114: //r - clockwise rotation
  8698. if(this.viewport.flipped){
  8699. this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360));
  8700. } else{
  8701. this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360));
  8702. }
  8703. this.viewport.applyConstraints();
  8704. return false;
  8705. case 82: //R - counterclockwise rotation
  8706. if(this.viewport.flipped){
  8707. this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360));
  8708. } else{
  8709. this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360));
  8710. }
  8711. this.viewport.applyConstraints();
  8712. return false;
  8713. case 102: //f
  8714. this.viewport.toggleFlip();
  8715. return false;
  8716. default:
  8717. // console.log( 'navigator keycode %s', event.keyCode );
  8718. return true;
  8719. }
  8720. } else {
  8721. return true;
  8722. }
  8723. }
  8724. function onCanvasClick( event ) {
  8725. var gestureSettings;
  8726. var haveKeyboardFocus = document.activeElement == this.canvas;
  8727. // If we don't have keyboard focus, request it.
  8728. if ( !haveKeyboardFocus ) {
  8729. this.canvas.focus();
  8730. }
  8731. if(this.viewport.flipped){
  8732. event.position.x = this.viewport.getContainerSize().x - event.position.x;
  8733. }
  8734. var canvasClickEventArgs = {
  8735. tracker: event.eventSource,
  8736. position: event.position,
  8737. quick: event.quick,
  8738. shift: event.shift,
  8739. originalEvent: event.originalEvent,
  8740. preventDefaultAction: event.preventDefaultAction
  8741. };
  8742. /**
  8743. * Raised when a mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element.
  8744. *
  8745. * @event canvas-click
  8746. * @memberof OpenSeadragon.Viewer
  8747. * @type {object}
  8748. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  8749. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  8750. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  8751. * @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags.
  8752. * @property {Boolean} shift - True if the shift key was pressed during this event.
  8753. * @property {Object} originalEvent - The original DOM event.
  8754. * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
  8755. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8756. */
  8757. this.raiseEvent( 'canvas-click', canvasClickEventArgs);
  8758. if ( !canvasClickEventArgs.preventDefaultAction && this.viewport && event.quick ) {
  8759. gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
  8760. if ( gestureSettings.clickToZoom ) {
  8761. this.viewport.zoomBy(
  8762. event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
  8763. gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null
  8764. );
  8765. this.viewport.applyConstraints();
  8766. }
  8767. }
  8768. }
  8769. function onCanvasDblClick( event ) {
  8770. var gestureSettings;
  8771. var canvasDblClickEventArgs = {
  8772. tracker: event.eventSource,
  8773. position: event.position,
  8774. shift: event.shift,
  8775. originalEvent: event.originalEvent,
  8776. preventDefaultAction: event.preventDefaultAction
  8777. };
  8778. /**
  8779. * Raised when a double mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element.
  8780. *
  8781. * @event canvas-double-click
  8782. * @memberof OpenSeadragon.Viewer
  8783. * @type {object}
  8784. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  8785. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  8786. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  8787. * @property {Boolean} shift - True if the shift key was pressed during this event.
  8788. * @property {Object} originalEvent - The original DOM event.
  8789. * @property {Boolean} preventDefaultAction - Set to true to prevent default double tap to zoom behaviour. Default: false.
  8790. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8791. */
  8792. this.raiseEvent( 'canvas-double-click', canvasDblClickEventArgs);
  8793. if ( !canvasDblClickEventArgs.preventDefaultAction && this.viewport ) {
  8794. gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
  8795. if ( gestureSettings.dblClickToZoom ) {
  8796. this.viewport.zoomBy(
  8797. event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
  8798. gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null
  8799. );
  8800. this.viewport.applyConstraints();
  8801. }
  8802. }
  8803. }
  8804. function onCanvasDrag( event ) {
  8805. var gestureSettings;
  8806. var canvasDragEventArgs = {
  8807. tracker: event.eventSource,
  8808. position: event.position,
  8809. delta: event.delta,
  8810. speed: event.speed,
  8811. direction: event.direction,
  8812. shift: event.shift,
  8813. originalEvent: event.originalEvent,
  8814. preventDefaultAction: event.preventDefaultAction
  8815. };
  8816. /**
  8817. * Raised when a mouse or touch drag operation occurs on the {@link OpenSeadragon.Viewer#canvas} element.
  8818. *
  8819. * @event canvas-drag
  8820. * @memberof OpenSeadragon.Viewer
  8821. * @type {object}
  8822. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  8823. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  8824. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  8825. * @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag.
  8826. * @property {Number} speed - Current computed speed, in pixels per second.
  8827. * @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
  8828. * @property {Boolean} shift - True if the shift key was pressed during this event.
  8829. * @property {Object} originalEvent - The original DOM event.
  8830. * @property {Boolean} preventDefaultAction - Set to true to prevent default drag behaviour. Default: false.
  8831. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8832. */
  8833. this.raiseEvent( 'canvas-drag', canvasDragEventArgs);
  8834. if ( !canvasDragEventArgs.preventDefaultAction && this.viewport ) {
  8835. gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
  8836. if( !this.panHorizontal ){
  8837. event.delta.x = 0;
  8838. }
  8839. if( !this.panVertical ){
  8840. event.delta.y = 0;
  8841. }
  8842. if(this.viewport.flipped){
  8843. event.delta.x = -event.delta.x;
  8844. }
  8845. if( this.constrainDuringPan ){
  8846. var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() );
  8847. this.viewport.centerSpringX.target.value += delta.x;
  8848. this.viewport.centerSpringY.target.value += delta.y;
  8849. var bounds = this.viewport.getBounds();
  8850. var constrainedBounds = this.viewport.getConstrainedBounds();
  8851. this.viewport.centerSpringX.target.value -= delta.x;
  8852. this.viewport.centerSpringY.target.value -= delta.y;
  8853. if (bounds.x != constrainedBounds.x) {
  8854. event.delta.x = 0;
  8855. }
  8856. if (bounds.y != constrainedBounds.y) {
  8857. event.delta.y = 0;
  8858. }
  8859. }
  8860. this.viewport.panBy( this.viewport.deltaPointsFromPixels( event.delta.negate() ), gestureSettings.flickEnabled && !this.constrainDuringPan);
  8861. }
  8862. }
  8863. function onCanvasDragEnd( event ) {
  8864. if (!event.preventDefaultAction && this.viewport) {
  8865. var gestureSettings = this.gestureSettingsByDeviceType(event.pointerType);
  8866. if (gestureSettings.flickEnabled &&
  8867. event.speed >= gestureSettings.flickMinSpeed) {
  8868. var amplitudeX = 0;
  8869. if (this.panHorizontal) {
  8870. amplitudeX = gestureSettings.flickMomentum * event.speed *
  8871. Math.cos(event.direction);
  8872. }
  8873. var amplitudeY = 0;
  8874. if (this.panVertical) {
  8875. amplitudeY = gestureSettings.flickMomentum * event.speed *
  8876. Math.sin(event.direction);
  8877. }
  8878. var center = this.viewport.pixelFromPoint(
  8879. this.viewport.getCenter(true));
  8880. var target = this.viewport.pointFromPixel(
  8881. new $.Point(center.x - amplitudeX, center.y - amplitudeY));
  8882. this.viewport.panTo(target, false);
  8883. }
  8884. this.viewport.applyConstraints();
  8885. }
  8886. /**
  8887. * Raised when a mouse or touch drag operation ends on the {@link OpenSeadragon.Viewer#canvas} element.
  8888. *
  8889. * @event canvas-drag-end
  8890. * @memberof OpenSeadragon.Viewer
  8891. * @type {object}
  8892. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  8893. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  8894. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  8895. * @property {Number} speed - Speed at the end of a drag gesture, in pixels per second.
  8896. * @property {Number} direction - Direction at the end of a drag gesture, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
  8897. * @property {Boolean} shift - True if the shift key was pressed during this event.
  8898. * @property {Object} originalEvent - The original DOM event.
  8899. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8900. */
  8901. this.raiseEvent('canvas-drag-end', {
  8902. tracker: event.eventSource,
  8903. position: event.position,
  8904. speed: event.speed,
  8905. direction: event.direction,
  8906. shift: event.shift,
  8907. originalEvent: event.originalEvent
  8908. });
  8909. }
  8910. function onCanvasEnter( event ) {
  8911. /**
  8912. * Raised when a pointer enters the {@link OpenSeadragon.Viewer#canvas} element.
  8913. *
  8914. * @event canvas-enter
  8915. * @memberof OpenSeadragon.Viewer
  8916. * @type {object}
  8917. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  8918. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  8919. * @property {String} pointerType - "mouse", "touch", "pen", etc.
  8920. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  8921. * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  8922. * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
  8923. * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
  8924. * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
  8925. * @property {Object} originalEvent - The original DOM event.
  8926. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8927. */
  8928. this.raiseEvent( 'canvas-enter', {
  8929. tracker: event.eventSource,
  8930. pointerType: event.pointerType,
  8931. position: event.position,
  8932. buttons: event.buttons,
  8933. pointers: event.pointers,
  8934. insideElementPressed: event.insideElementPressed,
  8935. buttonDownAny: event.buttonDownAny,
  8936. originalEvent: event.originalEvent
  8937. });
  8938. }
  8939. function onCanvasExit( event ) {
  8940. if (window.location != window.parent.location){
  8941. $.MouseTracker.resetAllMouseTrackers();
  8942. }
  8943. /**
  8944. * Raised when a pointer leaves the {@link OpenSeadragon.Viewer#canvas} element.
  8945. *
  8946. * @event canvas-exit
  8947. * @memberof OpenSeadragon.Viewer
  8948. * @type {object}
  8949. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  8950. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  8951. * @property {String} pointerType - "mouse", "touch", "pen", etc.
  8952. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  8953. * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  8954. * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
  8955. * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
  8956. * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
  8957. * @property {Object} originalEvent - The original DOM event.
  8958. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8959. */
  8960. this.raiseEvent( 'canvas-exit', {
  8961. tracker: event.eventSource,
  8962. pointerType: event.pointerType,
  8963. position: event.position,
  8964. buttons: event.buttons,
  8965. pointers: event.pointers,
  8966. insideElementPressed: event.insideElementPressed,
  8967. buttonDownAny: event.buttonDownAny,
  8968. originalEvent: event.originalEvent
  8969. });
  8970. }
  8971. function onCanvasPress( event ) {
  8972. /**
  8973. * Raised when the primary mouse button is pressed or touch starts on the {@link OpenSeadragon.Viewer#canvas} element.
  8974. *
  8975. * @event canvas-press
  8976. * @memberof OpenSeadragon.Viewer
  8977. * @type {object}
  8978. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  8979. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  8980. * @property {String} pointerType - "mouse", "touch", "pen", etc.
  8981. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  8982. * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
  8983. * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
  8984. * @property {Object} originalEvent - The original DOM event.
  8985. * @property {?Object} userData - Arbitrary subscriber-defined object.
  8986. */
  8987. this.raiseEvent( 'canvas-press', {
  8988. tracker: event.eventSource,
  8989. pointerType: event.pointerType,
  8990. position: event.position,
  8991. insideElementPressed: event.insideElementPressed,
  8992. insideElementReleased: event.insideElementReleased,
  8993. originalEvent: event.originalEvent
  8994. });
  8995. }
  8996. function onCanvasRelease( event ) {
  8997. /**
  8998. * Raised when the primary mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element.
  8999. *
  9000. * @event canvas-release
  9001. * @memberof OpenSeadragon.Viewer
  9002. * @type {object}
  9003. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9004. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  9005. * @property {String} pointerType - "mouse", "touch", "pen", etc.
  9006. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  9007. * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
  9008. * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
  9009. * @property {Object} originalEvent - The original DOM event.
  9010. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9011. */
  9012. this.raiseEvent( 'canvas-release', {
  9013. tracker: event.eventSource,
  9014. pointerType: event.pointerType,
  9015. position: event.position,
  9016. insideElementPressed: event.insideElementPressed,
  9017. insideElementReleased: event.insideElementReleased,
  9018. originalEvent: event.originalEvent
  9019. });
  9020. }
  9021. function onCanvasNonPrimaryPress( event ) {
  9022. /**
  9023. * Raised when any non-primary pointer button is pressed on the {@link OpenSeadragon.Viewer#canvas} element.
  9024. *
  9025. * @event canvas-nonprimary-press
  9026. * @memberof OpenSeadragon.Viewer
  9027. * @type {object}
  9028. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9029. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  9030. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  9031. * @property {String} pointerType - "mouse", "touch", "pen", etc.
  9032. * @property {Number} button - Button which caused the event.
  9033. * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
  9034. * @property {Number} buttons - Current buttons pressed.
  9035. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  9036. * @property {Object} originalEvent - The original DOM event.
  9037. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9038. */
  9039. this.raiseEvent( 'canvas-nonprimary-press', {
  9040. tracker: event.eventSource,
  9041. position: event.position,
  9042. pointerType: event.pointerType,
  9043. button: event.button,
  9044. buttons: event.buttons,
  9045. originalEvent: event.originalEvent
  9046. });
  9047. }
  9048. function onCanvasNonPrimaryRelease( event ) {
  9049. /**
  9050. * Raised when any non-primary pointer button is released on the {@link OpenSeadragon.Viewer#canvas} element.
  9051. *
  9052. * @event canvas-nonprimary-release
  9053. * @memberof OpenSeadragon.Viewer
  9054. * @type {object}
  9055. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9056. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  9057. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  9058. * @property {String} pointerType - "mouse", "touch", "pen", etc.
  9059. * @property {Number} button - Button which caused the event.
  9060. * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
  9061. * @property {Number} buttons - Current buttons pressed.
  9062. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  9063. * @property {Object} originalEvent - The original DOM event.
  9064. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9065. */
  9066. this.raiseEvent( 'canvas-nonprimary-release', {
  9067. tracker: event.eventSource,
  9068. position: event.position,
  9069. pointerType: event.pointerType,
  9070. button: event.button,
  9071. buttons: event.buttons,
  9072. originalEvent: event.originalEvent
  9073. });
  9074. }
  9075. function onCanvasPinch( event ) {
  9076. var gestureSettings,
  9077. centerPt,
  9078. lastCenterPt,
  9079. panByPt;
  9080. if ( !event.preventDefaultAction && this.viewport ) {
  9081. gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
  9082. if ( gestureSettings.pinchToZoom ) {
  9083. centerPt = this.viewport.pointFromPixel( event.center, true );
  9084. lastCenterPt = this.viewport.pointFromPixel( event.lastCenter, true );
  9085. panByPt = lastCenterPt.minus( centerPt );
  9086. if( !this.panHorizontal ) {
  9087. panByPt.x = 0;
  9088. }
  9089. if( !this.panVertical ) {
  9090. panByPt.y = 0;
  9091. }
  9092. this.viewport.zoomBy( event.distance / event.lastDistance, centerPt, true );
  9093. if ( gestureSettings.zoomToRefPoint ) {
  9094. this.viewport.panBy(panByPt, true);
  9095. }
  9096. this.viewport.applyConstraints();
  9097. }
  9098. if ( gestureSettings.pinchRotate ) {
  9099. // Pinch rotate
  9100. var angle1 = Math.atan2(event.gesturePoints[0].currentPos.y - event.gesturePoints[1].currentPos.y,
  9101. event.gesturePoints[0].currentPos.x - event.gesturePoints[1].currentPos.x);
  9102. var angle2 = Math.atan2(event.gesturePoints[0].lastPos.y - event.gesturePoints[1].lastPos.y,
  9103. event.gesturePoints[0].lastPos.x - event.gesturePoints[1].lastPos.x);
  9104. this.viewport.setRotation(this.viewport.getRotation() + ((angle1 - angle2) * (180 / Math.PI)));
  9105. }
  9106. }
  9107. /**
  9108. * Raised when a pinch event occurs on the {@link OpenSeadragon.Viewer#canvas} element.
  9109. *
  9110. * @event canvas-pinch
  9111. * @memberof OpenSeadragon.Viewer
  9112. * @type {object}
  9113. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9114. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  9115. * @property {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gesturePoints - Gesture points associated with the gesture. Velocity data can be found here.
  9116. * @property {OpenSeadragon.Point} lastCenter - The previous center point of the two pinch contact points relative to the tracked element.
  9117. * @property {OpenSeadragon.Point} center - The center point of the two pinch contact points relative to the tracked element.
  9118. * @property {Number} lastDistance - The previous distance between the two pinch contact points in CSS pixels.
  9119. * @property {Number} distance - The distance between the two pinch contact points in CSS pixels.
  9120. * @property {Boolean} shift - True if the shift key was pressed during this event.
  9121. * @property {Object} originalEvent - The original DOM event.
  9122. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9123. */
  9124. this.raiseEvent('canvas-pinch', {
  9125. tracker: event.eventSource,
  9126. gesturePoints: event.gesturePoints,
  9127. lastCenter: event.lastCenter,
  9128. center: event.center,
  9129. lastDistance: event.lastDistance,
  9130. distance: event.distance,
  9131. shift: event.shift,
  9132. originalEvent: event.originalEvent
  9133. });
  9134. //cancels event
  9135. return false;
  9136. }
  9137. function onCanvasScroll( event ) {
  9138. var gestureSettings,
  9139. factor,
  9140. thisScrollTime,
  9141. deltaScrollTime;
  9142. /* Certain scroll devices fire the scroll event way too fast so we are injecting a simple adjustment to keep things
  9143. * partially normalized. If we have already fired an event within the last 'minScrollDelta' milliseconds we skip
  9144. * this one and wait for the next event. */
  9145. thisScrollTime = $.now();
  9146. deltaScrollTime = thisScrollTime - this._lastScrollTime;
  9147. if (deltaScrollTime > this.minScrollDeltaTime) {
  9148. this._lastScrollTime = thisScrollTime;
  9149. if(this.viewport.flipped){
  9150. event.position.x = this.viewport.getContainerSize().x - event.position.x;
  9151. }
  9152. if ( !event.preventDefaultAction && this.viewport ) {
  9153. gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
  9154. if ( gestureSettings.scrollToZoom ) {
  9155. factor = Math.pow( this.zoomPerScroll, event.scroll );
  9156. this.viewport.zoomBy(
  9157. factor,
  9158. gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null
  9159. );
  9160. this.viewport.applyConstraints();
  9161. }
  9162. }
  9163. /**
  9164. * Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#canvas} element (mouse wheel).
  9165. *
  9166. * @event canvas-scroll
  9167. * @memberof OpenSeadragon.Viewer
  9168. * @type {object}
  9169. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9170. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  9171. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  9172. * @property {Number} scroll - The scroll delta for the event.
  9173. * @property {Boolean} shift - True if the shift key was pressed during this event.
  9174. * @property {Object} originalEvent - The original DOM event.
  9175. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9176. */
  9177. this.raiseEvent( 'canvas-scroll', {
  9178. tracker: event.eventSource,
  9179. position: event.position,
  9180. scroll: event.scroll,
  9181. shift: event.shift,
  9182. originalEvent: event.originalEvent
  9183. });
  9184. if (gestureSettings && gestureSettings.scrollToZoom) {
  9185. //cancels event
  9186. return false;
  9187. }
  9188. }
  9189. else {
  9190. gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
  9191. if (gestureSettings && gestureSettings.scrollToZoom) {
  9192. return false; // We are swallowing this event
  9193. }
  9194. }
  9195. }
  9196. function onContainerEnter( event ) {
  9197. THIS[ this.hash ].mouseInside = true;
  9198. abortControlsAutoHide( this );
  9199. /**
  9200. * Raised when the cursor enters the {@link OpenSeadragon.Viewer#container} element.
  9201. *
  9202. * @event container-enter
  9203. * @memberof OpenSeadragon.Viewer
  9204. * @type {object}
  9205. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9206. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  9207. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  9208. * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  9209. * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
  9210. * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
  9211. * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
  9212. * @property {Object} originalEvent - The original DOM event.
  9213. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9214. */
  9215. this.raiseEvent( 'container-enter', {
  9216. tracker: event.eventSource,
  9217. position: event.position,
  9218. buttons: event.buttons,
  9219. pointers: event.pointers,
  9220. insideElementPressed: event.insideElementPressed,
  9221. buttonDownAny: event.buttonDownAny,
  9222. originalEvent: event.originalEvent
  9223. });
  9224. }
  9225. function onContainerExit( event ) {
  9226. if ( event.pointers < 1 ) {
  9227. THIS[ this.hash ].mouseInside = false;
  9228. if ( !THIS[ this.hash ].animating ) {
  9229. beginControlsAutoHide( this );
  9230. }
  9231. }
  9232. /**
  9233. * Raised when the cursor leaves the {@link OpenSeadragon.Viewer#container} element.
  9234. *
  9235. * @event container-exit
  9236. * @memberof OpenSeadragon.Viewer
  9237. * @type {object}
  9238. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9239. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  9240. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  9241. * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
  9242. * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
  9243. * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
  9244. * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
  9245. * @property {Object} originalEvent - The original DOM event.
  9246. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9247. */
  9248. this.raiseEvent( 'container-exit', {
  9249. tracker: event.eventSource,
  9250. position: event.position,
  9251. buttons: event.buttons,
  9252. pointers: event.pointers,
  9253. insideElementPressed: event.insideElementPressed,
  9254. buttonDownAny: event.buttonDownAny,
  9255. originalEvent: event.originalEvent
  9256. });
  9257. }
  9258. ///////////////////////////////////////////////////////////////////////////////
  9259. // Page update routines ( aka Views - for future reference )
  9260. ///////////////////////////////////////////////////////////////////////////////
  9261. function updateMulti( viewer ) {
  9262. updateOnce( viewer );
  9263. // Request the next frame, unless we've been closed
  9264. if ( viewer.isOpen() ) {
  9265. viewer._updateRequestId = scheduleUpdate( viewer, updateMulti );
  9266. } else {
  9267. viewer._updateRequestId = false;
  9268. }
  9269. }
  9270. function updateOnce( viewer ) {
  9271. //viewer.profiler.beginUpdate();
  9272. if (viewer._opening) {
  9273. return;
  9274. }
  9275. if (viewer.autoResize) {
  9276. var containerSize = _getSafeElemSize(viewer.container);
  9277. var prevContainerSize = THIS[viewer.hash].prevContainerSize;
  9278. if (!containerSize.equals(prevContainerSize)) {
  9279. var viewport = viewer.viewport;
  9280. if (viewer.preserveImageSizeOnResize) {
  9281. var resizeRatio = prevContainerSize.x / containerSize.x;
  9282. var zoom = viewport.getZoom() * resizeRatio;
  9283. var center = viewport.getCenter();
  9284. viewport.resize(containerSize, false);
  9285. viewport.zoomTo(zoom, null, true);
  9286. viewport.panTo(center, true);
  9287. } else {
  9288. // maintain image position
  9289. var oldBounds = viewport.getBounds();
  9290. viewport.resize(containerSize, true);
  9291. viewport.fitBoundsWithConstraints(oldBounds, true);
  9292. }
  9293. THIS[viewer.hash].prevContainerSize = containerSize;
  9294. THIS[viewer.hash].forceRedraw = true;
  9295. }
  9296. }
  9297. var viewportChange = viewer.viewport.update();
  9298. var animated = viewer.world.update() || viewportChange;
  9299. if (viewportChange) {
  9300. /**
  9301. * Raised when any spring animation update occurs (zoom, pan, etc.),
  9302. * before the viewer has drawn the new location.
  9303. *
  9304. * @event viewport-change
  9305. * @memberof OpenSeadragon.Viewer
  9306. * @type {object}
  9307. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9308. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9309. */
  9310. viewer.raiseEvent('viewport-change');
  9311. }
  9312. if( viewer.referenceStrip ){
  9313. animated = viewer.referenceStrip.update( viewer.viewport ) || animated;
  9314. }
  9315. if ( !THIS[ viewer.hash ].animating && animated ) {
  9316. /**
  9317. * Raised when any spring animation starts (zoom, pan, etc.).
  9318. *
  9319. * @event animation-start
  9320. * @memberof OpenSeadragon.Viewer
  9321. * @type {object}
  9322. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9323. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9324. */
  9325. viewer.raiseEvent( "animation-start" );
  9326. abortControlsAutoHide( viewer );
  9327. }
  9328. if ( animated || THIS[ viewer.hash ].forceRedraw || viewer.world.needsDraw() ) {
  9329. drawWorld( viewer );
  9330. viewer._drawOverlays();
  9331. if( viewer.navigator ){
  9332. viewer.navigator.update( viewer.viewport );
  9333. }
  9334. THIS[ viewer.hash ].forceRedraw = false;
  9335. if (animated) {
  9336. /**
  9337. * Raised when any spring animation update occurs (zoom, pan, etc.),
  9338. * after the viewer has drawn the new location.
  9339. *
  9340. * @event animation
  9341. * @memberof OpenSeadragon.Viewer
  9342. * @type {object}
  9343. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9344. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9345. */
  9346. viewer.raiseEvent( "animation" );
  9347. }
  9348. }
  9349. if ( THIS[ viewer.hash ].animating && !animated ) {
  9350. /**
  9351. * Raised when any spring animation ends (zoom, pan, etc.).
  9352. *
  9353. * @event animation-finish
  9354. * @memberof OpenSeadragon.Viewer
  9355. * @type {object}
  9356. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9357. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9358. */
  9359. viewer.raiseEvent( "animation-finish" );
  9360. if ( !THIS[ viewer.hash ].mouseInside ) {
  9361. beginControlsAutoHide( viewer );
  9362. }
  9363. }
  9364. THIS[ viewer.hash ].animating = animated;
  9365. //viewer.profiler.endUpdate();
  9366. }
  9367. function drawWorld( viewer ) {
  9368. viewer.imageLoader.clear();
  9369. viewer.drawer.clear();
  9370. viewer.world.draw();
  9371. /**
  9372. * <em>- Needs documentation -</em>
  9373. *
  9374. * @event update-viewport
  9375. * @memberof OpenSeadragon.Viewer
  9376. * @type {object}
  9377. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  9378. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9379. */
  9380. viewer.raiseEvent( 'update-viewport', {} );
  9381. }
  9382. ///////////////////////////////////////////////////////////////////////////////
  9383. // Navigation Controls
  9384. ///////////////////////////////////////////////////////////////////////////////
  9385. function resolveUrl( prefix, url ) {
  9386. return prefix ? prefix + url : url;
  9387. }
  9388. function beginZoomingIn() {
  9389. THIS[ this.hash ].lastZoomTime = $.now();
  9390. THIS[ this.hash ].zoomFactor = this.zoomPerSecond;
  9391. THIS[ this.hash ].zooming = true;
  9392. scheduleZoom( this );
  9393. }
  9394. function beginZoomingOut() {
  9395. THIS[ this.hash ].lastZoomTime = $.now();
  9396. THIS[ this.hash ].zoomFactor = 1.0 / this.zoomPerSecond;
  9397. THIS[ this.hash ].zooming = true;
  9398. scheduleZoom( this );
  9399. }
  9400. function endZooming() {
  9401. THIS[ this.hash ].zooming = false;
  9402. }
  9403. function scheduleZoom( viewer ) {
  9404. $.requestAnimationFrame( $.delegate( viewer, doZoom ) );
  9405. }
  9406. function doZoom() {
  9407. var currentTime,
  9408. deltaTime,
  9409. adjustedFactor;
  9410. if ( THIS[ this.hash ].zooming && this.viewport) {
  9411. currentTime = $.now();
  9412. deltaTime = currentTime - THIS[ this.hash ].lastZoomTime;
  9413. adjustedFactor = Math.pow( THIS[ this.hash ].zoomFactor, deltaTime / 1000 );
  9414. this.viewport.zoomBy( adjustedFactor );
  9415. this.viewport.applyConstraints();
  9416. THIS[ this.hash ].lastZoomTime = currentTime;
  9417. scheduleZoom( this );
  9418. }
  9419. }
  9420. function doSingleZoomIn() {
  9421. if ( this.viewport ) {
  9422. THIS[ this.hash ].zooming = false;
  9423. this.viewport.zoomBy(
  9424. this.zoomPerClick / 1.0
  9425. );
  9426. this.viewport.applyConstraints();
  9427. }
  9428. }
  9429. function doSingleZoomOut() {
  9430. if ( this.viewport ) {
  9431. THIS[ this.hash ].zooming = false;
  9432. this.viewport.zoomBy(
  9433. 1.0 / this.zoomPerClick
  9434. );
  9435. this.viewport.applyConstraints();
  9436. }
  9437. }
  9438. function lightUp() {
  9439. this.buttons.emulateEnter();
  9440. this.buttons.emulateExit();
  9441. }
  9442. function onHome() {
  9443. if ( this.viewport ) {
  9444. this.viewport.goHome();
  9445. }
  9446. }
  9447. function onFullScreen() {
  9448. if ( this.isFullPage() && !$.isFullScreen() ) {
  9449. // Is fullPage but not fullScreen
  9450. this.setFullPage( false );
  9451. } else {
  9452. this.setFullScreen( !this.isFullPage() );
  9453. }
  9454. // correct for no mouseout event on change
  9455. if ( this.buttons ) {
  9456. this.buttons.emulateExit();
  9457. }
  9458. this.fullPageButton.element.focus();
  9459. if ( this.viewport ) {
  9460. this.viewport.applyConstraints();
  9461. }
  9462. }
  9463. function onRotateLeft() {
  9464. if ( this.viewport ) {
  9465. var currRotation = this.viewport.getRotation();
  9466. if ( this.viewport.flipped ){
  9467. currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360);
  9468. } else {
  9469. currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360);
  9470. }
  9471. this.viewport.setRotation(currRotation);
  9472. }
  9473. }
  9474. function onRotateRight() {
  9475. if ( this.viewport ) {
  9476. var currRotation = this.viewport.getRotation();
  9477. if ( this.viewport.flipped ){
  9478. currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360);
  9479. } else {
  9480. currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360);
  9481. }
  9482. this.viewport.setRotation(currRotation);
  9483. }
  9484. }
  9485. /**
  9486. * Note: When pressed flip control button
  9487. */
  9488. function onFlip() {
  9489. this.viewport.toggleFlip();
  9490. }
  9491. function onPrevious(){
  9492. var previous = this._sequenceIndex - 1;
  9493. if(this.navPrevNextWrap && previous < 0){
  9494. previous += this.tileSources.length;
  9495. }
  9496. this.goToPage( previous );
  9497. }
  9498. function onNext(){
  9499. var next = this._sequenceIndex + 1;
  9500. if(this.navPrevNextWrap && next >= this.tileSources.length){
  9501. next = 0;
  9502. }
  9503. this.goToPage( next );
  9504. }
  9505. }( OpenSeadragon ));
  9506. /*
  9507. * OpenSeadragon - Navigator
  9508. *
  9509. * Copyright (C) 2009 CodePlex Foundation
  9510. * Copyright (C) 2010-2013 OpenSeadragon contributors
  9511. *
  9512. * Redistribution and use in source and binary forms, with or without
  9513. * modification, are permitted provided that the following conditions are
  9514. * met:
  9515. *
  9516. * - Redistributions of source code must retain the above copyright notice,
  9517. * this list of conditions and the following disclaimer.
  9518. *
  9519. * - Redistributions in binary form must reproduce the above copyright
  9520. * notice, this list of conditions and the following disclaimer in the
  9521. * documentation and/or other materials provided with the distribution.
  9522. *
  9523. * - Neither the name of CodePlex Foundation nor the names of its
  9524. * contributors may be used to endorse or promote products derived from
  9525. * this software without specific prior written permission.
  9526. *
  9527. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  9528. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  9529. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  9530. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9531. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  9532. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  9533. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  9534. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  9535. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  9536. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  9537. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  9538. */
  9539. (function( $ ){
  9540. /**
  9541. * @class Navigator
  9542. * @classdesc The Navigator provides a small view of the current image as fixed
  9543. * while representing the viewport as a moving box serving as a frame
  9544. * of reference in the larger viewport as to which portion of the image
  9545. * is currently being examined. The navigator's viewport can be interacted
  9546. * with using the keyboard or the mouse.
  9547. *
  9548. * @memberof OpenSeadragon
  9549. * @extends OpenSeadragon.Viewer
  9550. * @extends OpenSeadragon.EventSource
  9551. * @param {Object} options
  9552. */
  9553. $.Navigator = function( options ){
  9554. var viewer = options.viewer,
  9555. _this = this,
  9556. viewerSize,
  9557. navigatorSize;
  9558. //We may need to create a new element and id if they did not
  9559. //provide the id for the existing element
  9560. if( !options.id ){
  9561. options.id = 'navigator-' + $.now();
  9562. this.element = $.makeNeutralElement( "div" );
  9563. options.controlOptions = {
  9564. anchor: $.ControlAnchor.TOP_RIGHT,
  9565. attachToViewer: true,
  9566. autoFade: options.autoFade
  9567. };
  9568. if( options.position ){
  9569. if( 'BOTTOM_RIGHT' == options.position ){
  9570. options.controlOptions.anchor = $.ControlAnchor.BOTTOM_RIGHT;
  9571. } else if( 'BOTTOM_LEFT' == options.position ){
  9572. options.controlOptions.anchor = $.ControlAnchor.BOTTOM_LEFT;
  9573. } else if( 'TOP_RIGHT' == options.position ){
  9574. options.controlOptions.anchor = $.ControlAnchor.TOP_RIGHT;
  9575. } else if( 'TOP_LEFT' == options.position ){
  9576. options.controlOptions.anchor = $.ControlAnchor.TOP_LEFT;
  9577. } else if( 'ABSOLUTE' == options.position ){
  9578. options.controlOptions.anchor = $.ControlAnchor.ABSOLUTE;
  9579. options.controlOptions.top = options.top;
  9580. options.controlOptions.left = options.left;
  9581. options.controlOptions.height = options.height;
  9582. options.controlOptions.width = options.width;
  9583. }
  9584. }
  9585. } else {
  9586. this.element = document.getElementById( options.id );
  9587. options.controlOptions = {
  9588. anchor: $.ControlAnchor.NONE,
  9589. attachToViewer: false,
  9590. autoFade: false
  9591. };
  9592. }
  9593. this.element.id = options.id;
  9594. this.element.className += ' navigator';
  9595. options = $.extend( true, {
  9596. sizeRatio: $.DEFAULT_SETTINGS.navigatorSizeRatio
  9597. }, options, {
  9598. element: this.element,
  9599. tabIndex: -1, // No keyboard navigation, omit from tab order
  9600. //These need to be overridden to prevent recursion since
  9601. //the navigator is a viewer and a viewer has a navigator
  9602. showNavigator: false,
  9603. mouseNavEnabled: false,
  9604. showNavigationControl: false,
  9605. showSequenceControl: false,
  9606. immediateRender: true,
  9607. blendTime: 0,
  9608. animationTime: 0,
  9609. autoResize: options.autoResize,
  9610. // prevent resizing the navigator from adding unwanted space around the image
  9611. minZoomImageRatio: 1.0,
  9612. background: options.background,
  9613. opacity: options.opacity,
  9614. borderColor: options.borderColor,
  9615. displayRegionColor: options.displayRegionColor
  9616. });
  9617. options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio;
  9618. $.setElementTouchActionNone( this.element );
  9619. this.borderWidth = 2;
  9620. //At some browser magnification levels the display regions lines up correctly, but at some there appears to
  9621. //be a one pixel gap.
  9622. this.fudge = new $.Point(1, 1);
  9623. this.totalBorderWidths = new $.Point(this.borderWidth * 2, this.borderWidth * 2).minus(this.fudge);
  9624. if ( options.controlOptions.anchor != $.ControlAnchor.NONE ) {
  9625. (function( style, borderWidth ){
  9626. style.margin = '0px';
  9627. style.border = borderWidth + 'px solid ' + options.borderColor;
  9628. style.padding = '0px';
  9629. style.background = options.background;
  9630. style.opacity = options.opacity;
  9631. style.overflow = 'hidden';
  9632. }( this.element.style, this.borderWidth));
  9633. }
  9634. this.displayRegion = $.makeNeutralElement( "div" );
  9635. this.displayRegion.id = this.element.id + '-displayregion';
  9636. this.displayRegion.className = 'displayregion';
  9637. (function( style, borderWidth ){
  9638. style.position = 'relative';
  9639. style.top = '0px';
  9640. style.left = '0px';
  9641. style.fontSize = '0px';
  9642. style.overflow = 'hidden';
  9643. style.border = borderWidth + 'px solid ' + options.displayRegionColor;
  9644. style.margin = '0px';
  9645. style.padding = '0px';
  9646. //TODO: IE doesn't like this property being set
  9647. //try{ style.outline = '2px auto #909'; }catch(e){/*ignore*/}
  9648. style.background = 'transparent';
  9649. // We use square bracket notation on the statement below, because float is a keyword.
  9650. // This is important for the Google Closure compiler, if nothing else.
  9651. /*jshint sub:true */
  9652. style['float'] = 'left'; //Webkit
  9653. style.cssFloat = 'left'; //Firefox
  9654. style.styleFloat = 'left'; //IE
  9655. style.zIndex = 999999999;
  9656. style.cursor = 'default';
  9657. }( this.displayRegion.style, this.borderWidth ));
  9658. this.displayRegionContainer = $.makeNeutralElement("div");
  9659. this.displayRegionContainer.id = this.element.id + '-displayregioncontainer';
  9660. this.displayRegionContainer.className = "displayregioncontainer";
  9661. this.displayRegionContainer.style.width = "100%";
  9662. this.displayRegionContainer.style.height = "100%";
  9663. viewer.addControl(
  9664. this.element,
  9665. options.controlOptions
  9666. );
  9667. this._resizeWithViewer = options.controlOptions.anchor != $.ControlAnchor.ABSOLUTE &&
  9668. options.controlOptions.anchor != $.ControlAnchor.NONE;
  9669. if ( this._resizeWithViewer ) {
  9670. if ( options.width && options.height ) {
  9671. this.element.style.height = typeof (options.height) == "number" ? (options.height + 'px') : options.height;
  9672. this.element.style.width = typeof (options.width) == "number" ? (options.width + 'px') : options.width;
  9673. } else {
  9674. viewerSize = $.getElementSize( viewer.element );
  9675. this.element.style.height = Math.round( viewerSize.y * options.sizeRatio ) + 'px';
  9676. this.element.style.width = Math.round( viewerSize.x * options.sizeRatio ) + 'px';
  9677. this.oldViewerSize = viewerSize;
  9678. }
  9679. navigatorSize = $.getElementSize( this.element );
  9680. this.elementArea = navigatorSize.x * navigatorSize.y;
  9681. }
  9682. this.oldContainerSize = new $.Point( 0, 0 );
  9683. $.Viewer.apply( this, [ options ] );
  9684. this.displayRegionContainer.appendChild(this.displayRegion);
  9685. this.element.getElementsByTagName('div')[0].appendChild(this.displayRegionContainer);
  9686. function rotate(degrees) {
  9687. _setTransformRotate(_this.displayRegionContainer, degrees);
  9688. _setTransformRotate(_this.displayRegion, -degrees);
  9689. _this.viewport.setRotation(degrees);
  9690. }
  9691. if (options.navigatorRotate) {
  9692. var degrees = options.viewer.viewport ?
  9693. options.viewer.viewport.getRotation() :
  9694. options.viewer.degrees || 0;
  9695. rotate(degrees);
  9696. options.viewer.addHandler("rotate", function (args) {
  9697. rotate(args.degrees);
  9698. });
  9699. }
  9700. // Remove the base class' (Viewer's) innerTracker and replace it with our own
  9701. this.innerTracker.destroy();
  9702. this.innerTracker = new $.MouseTracker({
  9703. element: this.element,
  9704. dragHandler: $.delegate( this, onCanvasDrag ),
  9705. clickHandler: $.delegate( this, onCanvasClick ),
  9706. releaseHandler: $.delegate( this, onCanvasRelease ),
  9707. scrollHandler: $.delegate( this, onCanvasScroll )
  9708. });
  9709. this.addHandler("reset-size", function() {
  9710. if (_this.viewport) {
  9711. _this.viewport.goHome(true);
  9712. }
  9713. });
  9714. viewer.world.addHandler("item-index-change", function(event) {
  9715. window.setTimeout(function(){
  9716. var item = _this.world.getItemAt(event.previousIndex);
  9717. _this.world.setItemIndex(item, event.newIndex);
  9718. }, 1);
  9719. });
  9720. viewer.world.addHandler("remove-item", function(event) {
  9721. var theirItem = event.item;
  9722. var myItem = _this._getMatchingItem(theirItem);
  9723. if (myItem) {
  9724. _this.world.removeItem(myItem);
  9725. }
  9726. });
  9727. this.update(viewer.viewport);
  9728. };
  9729. $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.Navigator.prototype */{
  9730. /**
  9731. * Used to notify the navigator when its size has changed.
  9732. * Especially useful when {@link OpenSeadragon.Options}.navigatorAutoResize is set to false and the navigator is resizable.
  9733. * @function
  9734. */
  9735. updateSize: function () {
  9736. if ( this.viewport ) {
  9737. var containerSize = new $.Point(
  9738. (this.container.clientWidth === 0 ? 1 : this.container.clientWidth),
  9739. (this.container.clientHeight === 0 ? 1 : this.container.clientHeight)
  9740. );
  9741. if ( !containerSize.equals( this.oldContainerSize ) ) {
  9742. this.viewport.resize( containerSize, true );
  9743. this.viewport.goHome(true);
  9744. this.oldContainerSize = containerSize;
  9745. this.drawer.clear();
  9746. this.world.draw();
  9747. }
  9748. }
  9749. },
  9750. /**
  9751. /* Flip navigator element
  9752. * @param {Boolean} state - Flip state to set.
  9753. */
  9754. setFlip: function(state) {
  9755. this.viewport.setFlip(state);
  9756. this.setDisplayTransform(this.viewer.viewport.getFlip() ? "scale(-1,1)" : "scale(1,1)");
  9757. return this;
  9758. },
  9759. setDisplayTransform: function(rule) {
  9760. setElementTransform(this.displayRegion, rule);
  9761. setElementTransform(this.canvas, rule);
  9762. setElementTransform(this.element, rule);
  9763. },
  9764. /**
  9765. * Used to update the navigator minimap's viewport rectangle when a change in the viewer's viewport occurs.
  9766. * @function
  9767. * @param {OpenSeadragon.Viewport} The viewport this navigator is tracking.
  9768. */
  9769. update: function( viewport ) {
  9770. var viewerSize,
  9771. newWidth,
  9772. newHeight,
  9773. bounds,
  9774. topleft,
  9775. bottomright;
  9776. viewerSize = $.getElementSize( this.viewer.element );
  9777. if ( this._resizeWithViewer && viewerSize.x && viewerSize.y && !viewerSize.equals( this.oldViewerSize ) ) {
  9778. this.oldViewerSize = viewerSize;
  9779. if ( this.maintainSizeRatio || !this.elementArea) {
  9780. newWidth = viewerSize.x * this.sizeRatio;
  9781. newHeight = viewerSize.y * this.sizeRatio;
  9782. } else {
  9783. newWidth = Math.sqrt(this.elementArea * (viewerSize.x / viewerSize.y));
  9784. newHeight = this.elementArea / newWidth;
  9785. }
  9786. this.element.style.width = Math.round( newWidth ) + 'px';
  9787. this.element.style.height = Math.round( newHeight ) + 'px';
  9788. if (!this.elementArea) {
  9789. this.elementArea = newWidth * newHeight;
  9790. }
  9791. this.updateSize();
  9792. }
  9793. if (viewport && this.viewport) {
  9794. bounds = viewport.getBoundsNoRotate(true);
  9795. topleft = this.viewport.pixelFromPointNoRotate(bounds.getTopLeft(), false);
  9796. bottomright = this.viewport.pixelFromPointNoRotate(bounds.getBottomRight(), false)
  9797. .minus( this.totalBorderWidths );
  9798. //update style for navigator-box
  9799. var style = this.displayRegion.style;
  9800. style.display = this.world.getItemCount() ? 'block' : 'none';
  9801. style.top = Math.round( topleft.y ) + 'px';
  9802. style.left = Math.round( topleft.x ) + 'px';
  9803. var width = Math.abs( topleft.x - bottomright.x );
  9804. var height = Math.abs( topleft.y - bottomright.y );
  9805. // make sure width and height are non-negative so IE doesn't throw
  9806. style.width = Math.round( Math.max( width, 0 ) ) + 'px';
  9807. style.height = Math.round( Math.max( height, 0 ) ) + 'px';
  9808. }
  9809. },
  9810. // overrides Viewer.addTiledImage
  9811. addTiledImage: function(options) {
  9812. var _this = this;
  9813. var original = options.originalTiledImage;
  9814. delete options.original;
  9815. var optionsClone = $.extend({}, options, {
  9816. success: function(event) {
  9817. var myItem = event.item;
  9818. myItem._originalForNavigator = original;
  9819. _this._matchBounds(myItem, original, true);
  9820. function matchBounds() {
  9821. _this._matchBounds(myItem, original);
  9822. }
  9823. function matchOpacity() {
  9824. _this._matchOpacity(myItem, original);
  9825. }
  9826. function matchCompositeOperation() {
  9827. _this._matchCompositeOperation(myItem, original);
  9828. }
  9829. original.addHandler('bounds-change', matchBounds);
  9830. original.addHandler('clip-change', matchBounds);
  9831. original.addHandler('opacity-change', matchOpacity);
  9832. original.addHandler('composite-operation-change', matchCompositeOperation);
  9833. }
  9834. });
  9835. return $.Viewer.prototype.addTiledImage.apply(this, [optionsClone]);
  9836. },
  9837. // private
  9838. _getMatchingItem: function(theirItem) {
  9839. var count = this.world.getItemCount();
  9840. var item;
  9841. for (var i = 0; i < count; i++) {
  9842. item = this.world.getItemAt(i);
  9843. if (item._originalForNavigator === theirItem) {
  9844. return item;
  9845. }
  9846. }
  9847. return null;
  9848. },
  9849. // private
  9850. _matchBounds: function(myItem, theirItem, immediately) {
  9851. var bounds = theirItem.getBoundsNoRotate();
  9852. myItem.setPosition(bounds.getTopLeft(), immediately);
  9853. myItem.setWidth(bounds.width, immediately);
  9854. myItem.setRotation(theirItem.getRotation(), immediately);
  9855. myItem.setClip(theirItem.getClip());
  9856. },
  9857. // private
  9858. _matchOpacity: function(myItem, theirItem) {
  9859. myItem.setOpacity(theirItem.opacity);
  9860. },
  9861. // private
  9862. _matchCompositeOperation: function(myItem, theirItem) {
  9863. myItem.setCompositeOperation(theirItem.compositeOperation);
  9864. }
  9865. });
  9866. /**
  9867. * @private
  9868. * @inner
  9869. * @function
  9870. */
  9871. function onCanvasClick( event ) {
  9872. var canvasClickEventArgs = {
  9873. tracker: event.eventSource,
  9874. position: event.position,
  9875. quick: event.quick,
  9876. shift: event.shift,
  9877. originalEvent: event.originalEvent,
  9878. preventDefaultAction: event.preventDefaultAction
  9879. };
  9880. /**
  9881. * Raised when a click event occurs on the {@link OpenSeadragon.Viewer#navigator} element.
  9882. *
  9883. * @event navigator-click
  9884. * @memberof OpenSeadragon.Viewer
  9885. * @type {object}
  9886. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9887. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  9888. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  9889. * @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags.
  9890. * @property {Boolean} shift - True if the shift key was pressed during this event.
  9891. * @property {Object} originalEvent - The original DOM event.
  9892. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9893. * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
  9894. */
  9895. this.viewer.raiseEvent('navigator-click', canvasClickEventArgs);
  9896. if ( !canvasClickEventArgs.preventDefaultAction && event.quick && this.viewer.viewport && (this.panVertical || this.panHorizontal)) {
  9897. if(this.viewer.viewport.flipped) {
  9898. event.position.x = this.viewport.getContainerSize().x - event.position.x;
  9899. }
  9900. var target = this.viewport.pointFromPixel(event.position);
  9901. if (!this.panVertical) {
  9902. // perform only horizonal pan
  9903. target.y = this.viewer.viewport.getCenter(true).y;
  9904. } else if (!this.panHorizontal) {
  9905. // perform only vertical pan
  9906. target.x = this.viewer.viewport.getCenter(true).x;
  9907. }
  9908. this.viewer.viewport.panTo(target);
  9909. this.viewer.viewport.applyConstraints();
  9910. }
  9911. }
  9912. /**
  9913. * @private
  9914. * @inner
  9915. * @function
  9916. */
  9917. function onCanvasDrag( event ) {
  9918. var canvasDragEventArgs = {
  9919. tracker: event.eventSource,
  9920. position: event.position,
  9921. delta: event.delta,
  9922. speed: event.speed,
  9923. direction: event.direction,
  9924. shift: event.shift,
  9925. originalEvent: event.originalEvent,
  9926. preventDefaultAction: event.preventDefaultAction
  9927. };
  9928. /**
  9929. * Raised when a drag event occurs on the {@link OpenSeadragon.Viewer#navigator} element.
  9930. *
  9931. * @event navigator-drag
  9932. * @memberof OpenSeadragon.Viewer
  9933. * @type {object}
  9934. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9935. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  9936. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  9937. * @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag.
  9938. * @property {Number} speed - Current computed speed, in pixels per second.
  9939. * @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
  9940. * @property {Boolean} shift - True if the shift key was pressed during this event.
  9941. * @property {Object} originalEvent - The original DOM event.
  9942. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9943. * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
  9944. */
  9945. this.viewer.raiseEvent('navigator-drag', canvasDragEventArgs);
  9946. if ( !canvasDragEventArgs.preventDefaultAction && this.viewer.viewport ) {
  9947. if( !this.panHorizontal ){
  9948. event.delta.x = 0;
  9949. }
  9950. if( !this.panVertical ){
  9951. event.delta.y = 0;
  9952. }
  9953. if(this.viewer.viewport.flipped){
  9954. event.delta.x = -event.delta.x;
  9955. }
  9956. this.viewer.viewport.panBy(
  9957. this.viewport.deltaPointsFromPixels(
  9958. event.delta
  9959. )
  9960. );
  9961. if( this.viewer.constrainDuringPan ){
  9962. this.viewer.viewport.applyConstraints();
  9963. }
  9964. }
  9965. }
  9966. /**
  9967. * @private
  9968. * @inner
  9969. * @function
  9970. */
  9971. function onCanvasRelease( event ) {
  9972. if ( event.insideElementPressed && this.viewer.viewport ) {
  9973. this.viewer.viewport.applyConstraints();
  9974. }
  9975. }
  9976. /**
  9977. * @private
  9978. * @inner
  9979. * @function
  9980. */
  9981. function onCanvasScroll( event ) {
  9982. /**
  9983. * Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#navigator} element (mouse wheel, touch pinch, etc.).
  9984. *
  9985. * @event navigator-scroll
  9986. * @memberof OpenSeadragon.Viewer
  9987. * @type {object}
  9988. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  9989. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
  9990. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
  9991. * @property {Number} scroll - The scroll delta for the event.
  9992. * @property {Boolean} shift - True if the shift key was pressed during this event.
  9993. * @property {Object} originalEvent - The original DOM event.
  9994. * @property {?Object} userData - Arbitrary subscriber-defined object.
  9995. */
  9996. this.viewer.raiseEvent( 'navigator-scroll', {
  9997. tracker: event.eventSource,
  9998. position: event.position,
  9999. scroll: event.scroll,
  10000. shift: event.shift,
  10001. originalEvent: event.originalEvent
  10002. });
  10003. //don't scroll the page up and down if the user is scrolling
  10004. //in the navigator
  10005. return false;
  10006. }
  10007. /**
  10008. * @function
  10009. * @private
  10010. * @param {Object} element
  10011. * @param {Number} degrees
  10012. */
  10013. function _setTransformRotate( element, degrees ) {
  10014. setElementTransform(element, "rotate(" + degrees + "deg)");
  10015. }
  10016. function setElementTransform( element, rule ) {
  10017. element.style.webkitTransform = rule;
  10018. element.style.mozTransform = rule;
  10019. element.style.msTransform = rule;
  10020. element.style.oTransform = rule;
  10021. element.style.transform = rule;
  10022. }
  10023. }( OpenSeadragon ));
  10024. /*
  10025. * OpenSeadragon - getString/setString
  10026. *
  10027. * Copyright (C) 2009 CodePlex Foundation
  10028. * Copyright (C) 2010-2013 OpenSeadragon contributors
  10029. *
  10030. * Redistribution and use in source and binary forms, with or without
  10031. * modification, are permitted provided that the following conditions are
  10032. * met:
  10033. *
  10034. * - Redistributions of source code must retain the above copyright notice,
  10035. * this list of conditions and the following disclaimer.
  10036. *
  10037. * - Redistributions in binary form must reproduce the above copyright
  10038. * notice, this list of conditions and the following disclaimer in the
  10039. * documentation and/or other materials provided with the distribution.
  10040. *
  10041. * - Neither the name of CodePlex Foundation nor the names of its
  10042. * contributors may be used to endorse or promote products derived from
  10043. * this software without specific prior written permission.
  10044. *
  10045. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  10046. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  10047. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  10048. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  10049. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10050. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  10051. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  10052. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  10053. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  10054. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  10055. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  10056. */
  10057. (function( $ ){
  10058. //TODO: I guess this is where the i18n needs to be reimplemented. I'll look
  10059. // into existing patterns for i18n in javascript but i think that mimicking
  10060. // pythons gettext might be a reasonable approach.
  10061. var I18N = {
  10062. Errors: {
  10063. Dzc: "Sorry, we don't support Deep Zoom Collections!",
  10064. Dzi: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
  10065. Xml: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
  10066. ImageFormat: "Sorry, we don't support {0}-based Deep Zoom Images.",
  10067. Security: "It looks like a security restriction stopped us from " +
  10068. "loading this Deep Zoom Image.",
  10069. Status: "This space unintentionally left blank ({0} {1}).",
  10070. OpenFailed: "Unable to open {0}: {1}"
  10071. },
  10072. Tooltips: {
  10073. FullPage: "Toggle full page",
  10074. Home: "Go home",
  10075. ZoomIn: "Zoom in",
  10076. ZoomOut: "Zoom out",
  10077. NextPage: "Next page",
  10078. PreviousPage: "Previous page",
  10079. RotateLeft: "Rotate left",
  10080. RotateRight: "Rotate right",
  10081. Flip: "Flip Horizontally"
  10082. }
  10083. };
  10084. $.extend( $, /** @lends OpenSeadragon */{
  10085. /**
  10086. * @function
  10087. * @param {String} property
  10088. */
  10089. getString: function( prop ) {
  10090. var props = prop.split('.'),
  10091. string = null,
  10092. args = arguments,
  10093. container = I18N,
  10094. i;
  10095. for (i = 0; i < props.length - 1; i++) {
  10096. // in case not a subproperty
  10097. container = container[ props[ i ] ] || {};
  10098. }
  10099. string = container[ props[ i ] ];
  10100. if ( typeof ( string ) != "string" ) {
  10101. $.console.log( "Untranslated source string:", prop );
  10102. string = ""; // FIXME: this breaks gettext()-style convention, which would return source
  10103. }
  10104. return string.replace(/\{\d+\}/g, function(capture) {
  10105. var i = parseInt( capture.match( /\d+/ ), 10 ) + 1;
  10106. return i < args.length ?
  10107. args[ i ] :
  10108. "";
  10109. });
  10110. },
  10111. /**
  10112. * @function
  10113. * @param {String} property
  10114. * @param {*} value
  10115. */
  10116. setString: function( prop, value ) {
  10117. var props = prop.split('.'),
  10118. container = I18N,
  10119. i;
  10120. for ( i = 0; i < props.length - 1; i++ ) {
  10121. if ( !container[ props[ i ] ] ) {
  10122. container[ props[ i ] ] = {};
  10123. }
  10124. container = container[ props[ i ] ];
  10125. }
  10126. container[ props[ i ] ] = value;
  10127. }
  10128. });
  10129. }( OpenSeadragon ));
  10130. /*
  10131. * OpenSeadragon - Point
  10132. *
  10133. * Copyright (C) 2009 CodePlex Foundation
  10134. * Copyright (C) 2010-2013 OpenSeadragon contributors
  10135. *
  10136. * Redistribution and use in source and binary forms, with or without
  10137. * modification, are permitted provided that the following conditions are
  10138. * met:
  10139. *
  10140. * - Redistributions of source code must retain the above copyright notice,
  10141. * this list of conditions and the following disclaimer.
  10142. *
  10143. * - Redistributions in binary form must reproduce the above copyright
  10144. * notice, this list of conditions and the following disclaimer in the
  10145. * documentation and/or other materials provided with the distribution.
  10146. *
  10147. * - Neither the name of CodePlex Foundation nor the names of its
  10148. * contributors may be used to endorse or promote products derived from
  10149. * this software without specific prior written permission.
  10150. *
  10151. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  10152. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  10153. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  10154. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  10155. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10156. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  10157. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  10158. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  10159. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  10160. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  10161. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  10162. */
  10163. (function( $ ){
  10164. /**
  10165. * @class Point
  10166. * @classdesc A Point is really used as a 2-dimensional vector, equally useful for
  10167. * representing a point on a plane, or the height and width of a plane
  10168. * not requiring any other frame of reference.
  10169. *
  10170. * @memberof OpenSeadragon
  10171. * @param {Number} [x] The vector component 'x'. Defaults to the origin at 0.
  10172. * @param {Number} [y] The vector component 'y'. Defaults to the origin at 0.
  10173. */
  10174. $.Point = function( x, y ) {
  10175. /**
  10176. * The vector component 'x'.
  10177. * @member {Number} x
  10178. * @memberof OpenSeadragon.Point#
  10179. */
  10180. this.x = typeof ( x ) == "number" ? x : 0;
  10181. /**
  10182. * The vector component 'y'.
  10183. * @member {Number} y
  10184. * @memberof OpenSeadragon.Point#
  10185. */
  10186. this.y = typeof ( y ) == "number" ? y : 0;
  10187. };
  10188. /** @lends OpenSeadragon.Point.prototype */
  10189. $.Point.prototype = {
  10190. /**
  10191. * @function
  10192. * @returns {OpenSeadragon.Point} a duplicate of this Point
  10193. */
  10194. clone: function() {
  10195. return new $.Point(this.x, this.y);
  10196. },
  10197. /**
  10198. * Add another Point to this point and return a new Point.
  10199. * @function
  10200. * @param {OpenSeadragon.Point} point The point to add vector components.
  10201. * @returns {OpenSeadragon.Point} A new point representing the sum of the
  10202. * vector components
  10203. */
  10204. plus: function( point ) {
  10205. return new $.Point(
  10206. this.x + point.x,
  10207. this.y + point.y
  10208. );
  10209. },
  10210. /**
  10211. * Subtract another Point to this point and return a new Point.
  10212. * @function
  10213. * @param {OpenSeadragon.Point} point The point to subtract vector components.
  10214. * @returns {OpenSeadragon.Point} A new point representing the subtraction of the
  10215. * vector components
  10216. */
  10217. minus: function( point ) {
  10218. return new $.Point(
  10219. this.x - point.x,
  10220. this.y - point.y
  10221. );
  10222. },
  10223. /**
  10224. * Multiply this point by a factor and return a new Point.
  10225. * @function
  10226. * @param {Number} factor The factor to multiply vector components.
  10227. * @returns {OpenSeadragon.Point} A new point representing the multiplication
  10228. * of the vector components by the factor
  10229. */
  10230. times: function( factor ) {
  10231. return new $.Point(
  10232. this.x * factor,
  10233. this.y * factor
  10234. );
  10235. },
  10236. /**
  10237. * Divide this point by a factor and return a new Point.
  10238. * @function
  10239. * @param {Number} factor The factor to divide vector components.
  10240. * @returns {OpenSeadragon.Point} A new point representing the division of the
  10241. * vector components by the factor
  10242. */
  10243. divide: function( factor ) {
  10244. return new $.Point(
  10245. this.x / factor,
  10246. this.y / factor
  10247. );
  10248. },
  10249. /**
  10250. * Compute the opposite of this point and return a new Point.
  10251. * @function
  10252. * @returns {OpenSeadragon.Point} A new point representing the opposite of the
  10253. * vector components
  10254. */
  10255. negate: function() {
  10256. return new $.Point( -this.x, -this.y );
  10257. },
  10258. /**
  10259. * Compute the distance between this point and another point.
  10260. * @function
  10261. * @param {OpenSeadragon.Point} point The point to compute the distance with.
  10262. * @returns {Number} The distance between the 2 points
  10263. */
  10264. distanceTo: function( point ) {
  10265. return Math.sqrt(
  10266. Math.pow( this.x - point.x, 2 ) +
  10267. Math.pow( this.y - point.y, 2 )
  10268. );
  10269. },
  10270. /**
  10271. * Compute the squared distance between this point and another point.
  10272. * Useful for optimizing things like comparing distances.
  10273. * @function
  10274. * @param {OpenSeadragon.Point} point The point to compute the squared distance with.
  10275. * @returns {Number} The squared distance between the 2 points
  10276. */
  10277. squaredDistanceTo: function( point ) {
  10278. return Math.pow( this.x - point.x, 2 ) +
  10279. Math.pow( this.y - point.y, 2 );
  10280. },
  10281. /**
  10282. * Apply a function to each coordinate of this point and return a new point.
  10283. * @function
  10284. * @param {function} func The function to apply to each coordinate.
  10285. * @returns {OpenSeadragon.Point} A new point with the coordinates computed
  10286. * by the specified function
  10287. */
  10288. apply: function( func ) {
  10289. return new $.Point( func( this.x ), func( this.y ) );
  10290. },
  10291. /**
  10292. * Check if this point is equal to another one.
  10293. * @function
  10294. * @param {OpenSeadragon.Point} point The point to compare this point with.
  10295. * @returns {Boolean} true if they are equal, false otherwise.
  10296. */
  10297. equals: function( point ) {
  10298. return (
  10299. point instanceof $.Point
  10300. ) && (
  10301. this.x === point.x
  10302. ) && (
  10303. this.y === point.y
  10304. );
  10305. },
  10306. /**
  10307. * Rotates the point around the specified pivot
  10308. * From http://stackoverflow.com/questions/4465931/rotate-rectangle-around-a-point
  10309. * @function
  10310. * @param {Number} degress to rotate around the pivot.
  10311. * @param {OpenSeadragon.Point} [pivot=(0,0)] Point around which to rotate.
  10312. * Defaults to the origin.
  10313. * @returns {OpenSeadragon.Point}. A new point representing the point rotated around the specified pivot
  10314. */
  10315. rotate: function (degrees, pivot) {
  10316. pivot = pivot || new $.Point(0, 0);
  10317. var cos;
  10318. var sin;
  10319. // Avoid float computations when possible
  10320. if (degrees % 90 === 0) {
  10321. var d = $.positiveModulo(degrees, 360);
  10322. switch (d) {
  10323. case 0:
  10324. cos = 1;
  10325. sin = 0;
  10326. break;
  10327. case 90:
  10328. cos = 0;
  10329. sin = 1;
  10330. break;
  10331. case 180:
  10332. cos = -1;
  10333. sin = 0;
  10334. break;
  10335. case 270:
  10336. cos = 0;
  10337. sin = -1;
  10338. break;
  10339. }
  10340. } else {
  10341. var angle = degrees * Math.PI / 180.0;
  10342. cos = Math.cos(angle);
  10343. sin = Math.sin(angle);
  10344. }
  10345. var x = cos * (this.x - pivot.x) - sin * (this.y - pivot.y) + pivot.x;
  10346. var y = sin * (this.x - pivot.x) + cos * (this.y - pivot.y) + pivot.y;
  10347. return new $.Point(x, y);
  10348. },
  10349. /**
  10350. * Convert this point to a string in the format (x,y) where x and y are
  10351. * rounded to the nearest integer.
  10352. * @function
  10353. * @returns {String} A string representation of this point.
  10354. */
  10355. toString: function() {
  10356. return "(" + (Math.round(this.x * 100) / 100) + "," + (Math.round(this.y * 100) / 100) + ")";
  10357. }
  10358. };
  10359. }( OpenSeadragon ));
  10360. /*
  10361. * OpenSeadragon - TileSource
  10362. *
  10363. * Copyright (C) 2009 CodePlex Foundation
  10364. * Copyright (C) 2010-2013 OpenSeadragon contributors
  10365. *
  10366. * Redistribution and use in source and binary forms, with or without
  10367. * modification, are permitted provided that the following conditions are
  10368. * met:
  10369. *
  10370. * - Redistributions of source code must retain the above copyright notice,
  10371. * this list of conditions and the following disclaimer.
  10372. *
  10373. * - Redistributions in binary form must reproduce the above copyright
  10374. * notice, this list of conditions and the following disclaimer in the
  10375. * documentation and/or other materials provided with the distribution.
  10376. *
  10377. * - Neither the name of CodePlex Foundation nor the names of its
  10378. * contributors may be used to endorse or promote products derived from
  10379. * this software without specific prior written permission.
  10380. *
  10381. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  10382. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  10383. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  10384. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  10385. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10386. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  10387. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  10388. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  10389. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  10390. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  10391. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  10392. */
  10393. (function( $ ){
  10394. /**
  10395. * @class TileSource
  10396. * @classdesc The TileSource contains the most basic implementation required to create a
  10397. * smooth transition between layers in an image pyramid. It has only a single key
  10398. * interface that must be implemented to complete its key functionality:
  10399. * 'getTileUrl'. It also has several optional interfaces that can be
  10400. * implemented if a new TileSource wishes to support configuration via a simple
  10401. * object or array ('configure') and if the tile source supports or requires
  10402. * configuration via retrieval of a document on the network ala AJAX or JSONP,
  10403. * ('getImageInfo').
  10404. * <br/>
  10405. * By default the image pyramid is split into N layers where the image's longest
  10406. * side in M (in pixels), where N is the smallest integer which satisfies
  10407. * <strong>2^(N+1) >= M</strong>.
  10408. *
  10409. * @memberof OpenSeadragon
  10410. * @extends OpenSeadragon.EventSource
  10411. * @param {Object} options
  10412. * You can either specify a URL, or literally define the TileSource (by specifying
  10413. * width, height, tileSize, tileOverlap, minLevel, and maxLevel). For the former,
  10414. * the extending class is expected to implement 'getImageInfo' and 'configure'.
  10415. * For the latter, the construction is assumed to occur through
  10416. * the extending classes implementation of 'configure'.
  10417. * @param {String} [options.url]
  10418. * The URL for the data necessary for this TileSource.
  10419. * @param {String} [options.referenceStripThumbnailUrl]
  10420. * The URL for a thumbnail image to be used by the reference strip
  10421. * @param {Function} [options.success]
  10422. * A function to be called upon successful creation.
  10423. * @param {Boolean} [options.ajaxWithCredentials]
  10424. * If this TileSource needs to make an AJAX call, this specifies whether to set
  10425. * the XHR's withCredentials (for accessing secure data).
  10426. * @param {Object} [options.ajaxHeaders]
  10427. * A set of headers to include in AJAX requests.
  10428. * @param {Number} [options.width]
  10429. * Width of the source image at max resolution in pixels.
  10430. * @param {Number} [options.height]
  10431. * Height of the source image at max resolution in pixels.
  10432. * @param {Number} [options.tileSize]
  10433. * The size of the tiles to assumed to make up each pyramid layer in pixels.
  10434. * Tile size determines the point at which the image pyramid must be
  10435. * divided into a matrix of smaller images.
  10436. * Use options.tileWidth and options.tileHeight to support non-square tiles.
  10437. * @param {Number} [options.tileWidth]
  10438. * The width of the tiles to assumed to make up each pyramid layer in pixels.
  10439. * @param {Number} [options.tileHeight]
  10440. * The height of the tiles to assumed to make up each pyramid layer in pixels.
  10441. * @param {Number} [options.tileOverlap]
  10442. * The number of pixels each tile is expected to overlap touching tiles.
  10443. * @param {Number} [options.minLevel]
  10444. * The minimum level to attempt to load.
  10445. * @param {Number} [options.maxLevel]
  10446. * The maximum level to attempt to load.
  10447. */
  10448. $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLevel ) {
  10449. var _this = this;
  10450. var args = arguments,
  10451. options,
  10452. i;
  10453. if( $.isPlainObject( width ) ){
  10454. options = width;
  10455. }else{
  10456. options = {
  10457. width: args[0],
  10458. height: args[1],
  10459. tileSize: args[2],
  10460. tileOverlap: args[3],
  10461. minLevel: args[4],
  10462. maxLevel: args[5]
  10463. };
  10464. }
  10465. //Tile sources supply some events, namely 'ready' when they must be configured
  10466. //by asynchronously fetching their configuration data.
  10467. $.EventSource.call( this );
  10468. //we allow options to override anything we don't treat as
  10469. //required via idiomatic options or which is functionally
  10470. //set depending on the state of the readiness of this tile
  10471. //source
  10472. $.extend( true, this, options );
  10473. if (!this.success) {
  10474. //Any functions that are passed as arguments are bound to the ready callback
  10475. for ( i = 0; i < arguments.length; i++ ) {
  10476. if ( $.isFunction( arguments[ i ] ) ) {
  10477. this.success = arguments[ i ];
  10478. //only one callback per constructor
  10479. break;
  10480. }
  10481. }
  10482. }
  10483. if (this.success) {
  10484. this.addHandler( 'ready', function ( event ) {
  10485. _this.success( event );
  10486. } );
  10487. }
  10488. /**
  10489. * Ratio of width to height
  10490. * @member {Number} aspectRatio
  10491. * @memberof OpenSeadragon.TileSource#
  10492. */
  10493. /**
  10494. * Vector storing x and y dimensions ( width and height respectively ).
  10495. * @member {OpenSeadragon.Point} dimensions
  10496. * @memberof OpenSeadragon.TileSource#
  10497. */
  10498. /**
  10499. * The overlap in pixels each tile shares with its adjacent neighbors.
  10500. * @member {Number} tileOverlap
  10501. * @memberof OpenSeadragon.TileSource#
  10502. */
  10503. /**
  10504. * The minimum pyramid level this tile source supports or should attempt to load.
  10505. * @member {Number} minLevel
  10506. * @memberof OpenSeadragon.TileSource#
  10507. */
  10508. /**
  10509. * The maximum pyramid level this tile source supports or should attempt to load.
  10510. * @member {Number} maxLevel
  10511. * @memberof OpenSeadragon.TileSource#
  10512. */
  10513. /**
  10514. *
  10515. * @member {Boolean} ready
  10516. * @memberof OpenSeadragon.TileSource#
  10517. */
  10518. if( 'string' == $.type( arguments[ 0 ] ) ){
  10519. this.url = arguments[0];
  10520. }
  10521. if (this.url) {
  10522. //in case the getImageInfo method is overridden and/or implies an
  10523. //async mechanism set some safe defaults first
  10524. this.aspectRatio = 1;
  10525. this.dimensions = new $.Point( 10, 10 );
  10526. this._tileWidth = 0;
  10527. this._tileHeight = 0;
  10528. this.tileOverlap = 0;
  10529. this.minLevel = 0;
  10530. this.maxLevel = 0;
  10531. this.ready = false;
  10532. //configuration via url implies the extending class
  10533. //implements and 'configure'
  10534. this.getImageInfo( this.url );
  10535. } else {
  10536. //explicit configuration via positional args in constructor
  10537. //or the more idiomatic 'options' object
  10538. this.ready = true;
  10539. this.aspectRatio = (options.width && options.height) ?
  10540. (options.width / options.height) : 1;
  10541. this.dimensions = new $.Point( options.width, options.height );
  10542. if ( this.tileSize ){
  10543. this._tileWidth = this._tileHeight = this.tileSize;
  10544. delete this.tileSize;
  10545. } else {
  10546. if( this.tileWidth ){
  10547. // We were passed tileWidth in options, but we want to rename it
  10548. // with a leading underscore to make clear that it is not safe to directly modify it
  10549. this._tileWidth = this.tileWidth;
  10550. delete this.tileWidth;
  10551. } else {
  10552. this._tileWidth = 0;
  10553. }
  10554. if( this.tileHeight ){
  10555. // See note above about renaming this.tileWidth
  10556. this._tileHeight = this.tileHeight;
  10557. delete this.tileHeight;
  10558. } else {
  10559. this._tileHeight = 0;
  10560. }
  10561. }
  10562. this.tileOverlap = options.tileOverlap ? options.tileOverlap : 0;
  10563. this.minLevel = options.minLevel ? options.minLevel : 0;
  10564. this.maxLevel = ( undefined !== options.maxLevel && null !== options.maxLevel ) ?
  10565. options.maxLevel : (
  10566. ( options.width && options.height ) ? Math.ceil(
  10567. Math.log( Math.max( options.width, options.height ) ) /
  10568. Math.log( 2 )
  10569. ) : 0
  10570. );
  10571. if( this.success && $.isFunction( this.success ) ){
  10572. this.success( this );
  10573. }
  10574. }
  10575. };
  10576. /** @lends OpenSeadragon.TileSource.prototype */
  10577. $.TileSource.prototype = {
  10578. getTileSize: function( level ) {
  10579. $.console.error(
  10580. "[TileSource.getTileSize] is deprecated. " +
  10581. "Use TileSource.getTileWidth() and TileSource.getTileHeight() instead"
  10582. );
  10583. return this._tileWidth;
  10584. },
  10585. /**
  10586. * Return the tileWidth for a given level.
  10587. * Subclasses should override this if tileWidth can be different at different levels
  10588. * such as in IIIFTileSource. Code should use this function rather than reading
  10589. * from ._tileWidth directly.
  10590. * @function
  10591. * @param {Number} level
  10592. */
  10593. getTileWidth: function( level ) {
  10594. if (!this._tileWidth) {
  10595. return this.getTileSize(level);
  10596. }
  10597. return this._tileWidth;
  10598. },
  10599. /**
  10600. * Return the tileHeight for a given level.
  10601. * Subclasses should override this if tileHeight can be different at different levels
  10602. * such as in IIIFTileSource. Code should use this function rather than reading
  10603. * from ._tileHeight directly.
  10604. * @function
  10605. * @param {Number} level
  10606. */
  10607. getTileHeight: function( level ) {
  10608. if (!this._tileHeight) {
  10609. return this.getTileSize(level);
  10610. }
  10611. return this._tileHeight;
  10612. },
  10613. /**
  10614. * @function
  10615. * @param {Number} level
  10616. */
  10617. getLevelScale: function( level ) {
  10618. // see https://github.com/openseadragon/openseadragon/issues/22
  10619. // we use the tilesources implementation of getLevelScale to generate
  10620. // a memoized re-implementation
  10621. var levelScaleCache = {},
  10622. i;
  10623. for( i = 0; i <= this.maxLevel; i++ ){
  10624. levelScaleCache[ i ] = 1 / Math.pow(2, this.maxLevel - i);
  10625. }
  10626. this.getLevelScale = function( _level ){
  10627. return levelScaleCache[ _level ];
  10628. };
  10629. return this.getLevelScale( level );
  10630. },
  10631. /**
  10632. * @function
  10633. * @param {Number} level
  10634. */
  10635. getNumTiles: function( level ) {
  10636. var scale = this.getLevelScale( level ),
  10637. x = Math.ceil( scale * this.dimensions.x / this.getTileWidth(level) ),
  10638. y = Math.ceil( scale * this.dimensions.y / this.getTileHeight(level) );
  10639. return new $.Point( x, y );
  10640. },
  10641. /**
  10642. * @function
  10643. * @param {Number} level
  10644. */
  10645. getPixelRatio: function( level ) {
  10646. var imageSizeScaled = this.dimensions.times( this.getLevelScale( level ) ),
  10647. rx = 1.0 / imageSizeScaled.x,
  10648. ry = 1.0 / imageSizeScaled.y;
  10649. return new $.Point(rx, ry);
  10650. },
  10651. /**
  10652. * @function
  10653. * @returns {Number} The highest level in this tile source that can be contained in a single tile.
  10654. */
  10655. getClosestLevel: function() {
  10656. var i,
  10657. tiles;
  10658. for (i = this.minLevel + 1; i <= this.maxLevel; i++){
  10659. tiles = this.getNumTiles(i);
  10660. if (tiles.x > 1 || tiles.y > 1) {
  10661. break;
  10662. }
  10663. }
  10664. return i - 1;
  10665. },
  10666. /**
  10667. * @function
  10668. * @param {Number} level
  10669. * @param {OpenSeadragon.Point} point
  10670. */
  10671. getTileAtPoint: function(level, point) {
  10672. var validPoint = point.x >= 0 && point.x <= 1 &&
  10673. point.y >= 0 && point.y <= 1 / this.aspectRatio;
  10674. $.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point.");
  10675. var widthScaled = this.dimensions.x * this.getLevelScale(level);
  10676. var pixelX = point.x * widthScaled;
  10677. var pixelY = point.y * widthScaled;
  10678. var x = Math.floor(pixelX / this.getTileWidth(level));
  10679. var y = Math.floor(pixelY / this.getTileHeight(level));
  10680. // When point.x == 1 or point.y == 1 / this.aspectRatio we want to
  10681. // return the last tile of the row/column
  10682. if (point.x >= 1) {
  10683. x = this.getNumTiles(level).x - 1;
  10684. }
  10685. var EPSILON = 1e-15;
  10686. if (point.y >= 1 / this.aspectRatio - EPSILON) {
  10687. y = this.getNumTiles(level).y - 1;
  10688. }
  10689. return new $.Point(x, y);
  10690. },
  10691. /**
  10692. * @function
  10693. * @param {Number} level
  10694. * @param {Number} x
  10695. * @param {Number} y
  10696. * @param {Boolean} [isSource=false] Whether to return the source bounds of the tile.
  10697. * @returns {OpenSeadragon.Rect} Either where this tile fits (in normalized coordinates) or the
  10698. * portion of the tile to use as the source of the drawing operation (in pixels), depending on
  10699. * the isSource parameter.
  10700. */
  10701. getTileBounds: function( level, x, y, isSource ) {
  10702. var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
  10703. tileWidth = this.getTileWidth(level),
  10704. tileHeight = this.getTileHeight(level),
  10705. px = ( x === 0 ) ? 0 : tileWidth * x - this.tileOverlap,
  10706. py = ( y === 0 ) ? 0 : tileHeight * y - this.tileOverlap,
  10707. sx = tileWidth + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
  10708. sy = tileHeight + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
  10709. scale = 1.0 / dimensionsScaled.x;
  10710. sx = Math.min( sx, dimensionsScaled.x - px );
  10711. sy = Math.min( sy, dimensionsScaled.y - py );
  10712. if (isSource) {
  10713. return new $.Rect(0, 0, sx, sy);
  10714. }
  10715. return new $.Rect( px * scale, py * scale, sx * scale, sy * scale );
  10716. },
  10717. /**
  10718. * Responsible for retrieving, and caching the
  10719. * image metadata pertinent to this TileSources implementation.
  10720. * @function
  10721. * @param {String} url
  10722. * @throws {Error}
  10723. */
  10724. getImageInfo: function( url ) {
  10725. var _this = this,
  10726. callbackName,
  10727. callback,
  10728. readySource,
  10729. options,
  10730. urlParts,
  10731. filename,
  10732. lastDot;
  10733. if( url ) {
  10734. urlParts = url.split( '/' );
  10735. filename = urlParts[ urlParts.length - 1 ];
  10736. lastDot = filename.lastIndexOf( '.' );
  10737. if ( lastDot > -1 ) {
  10738. urlParts[ urlParts.length - 1 ] = filename.slice( 0, lastDot );
  10739. }
  10740. }
  10741. callback = function( data ){
  10742. if( typeof (data) === "string" ) {
  10743. data = $.parseXml( data );
  10744. }
  10745. var $TileSource = $.TileSource.determineType( _this, data, url );
  10746. if ( !$TileSource ) {
  10747. /**
  10748. * Raised when an error occurs loading a TileSource.
  10749. *
  10750. * @event open-failed
  10751. * @memberof OpenSeadragon.TileSource
  10752. * @type {object}
  10753. * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event.
  10754. * @property {String} message
  10755. * @property {String} source
  10756. * @property {?Object} userData - Arbitrary subscriber-defined object.
  10757. */
  10758. _this.raiseEvent( 'open-failed', { message: "Unable to load TileSource", source: url } );
  10759. return;
  10760. }
  10761. options = $TileSource.prototype.configure.apply( _this, [ data, url ]);
  10762. if (options.ajaxWithCredentials === undefined) {
  10763. options.ajaxWithCredentials = _this.ajaxWithCredentials;
  10764. }
  10765. readySource = new $TileSource( options );
  10766. _this.ready = true;
  10767. /**
  10768. * Raised when a TileSource is opened and initialized.
  10769. *
  10770. * @event ready
  10771. * @memberof OpenSeadragon.TileSource
  10772. * @type {object}
  10773. * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event.
  10774. * @property {Object} tileSource
  10775. * @property {?Object} userData - Arbitrary subscriber-defined object.
  10776. */
  10777. _this.raiseEvent( 'ready', { tileSource: readySource } );
  10778. };
  10779. if( url.match(/\.js$/) ){
  10780. //TODO: Its not very flexible to require tile sources to end jsonp
  10781. // request for info with a url that ends with '.js' but for
  10782. // now it's the only way I see to distinguish uniformly.
  10783. callbackName = url.split('/').pop().replace('.js', '');
  10784. $.jsonp({
  10785. url: url,
  10786. async: false,
  10787. callbackName: callbackName,
  10788. callback: callback
  10789. });
  10790. } else {
  10791. // request info via xhr asynchronously.
  10792. $.makeAjaxRequest( {
  10793. url: url,
  10794. withCredentials: this.ajaxWithCredentials,
  10795. headers: this.ajaxHeaders,
  10796. success: function( xhr ) {
  10797. var data = processResponse( xhr );
  10798. callback( data );
  10799. },
  10800. error: function ( xhr, exc ) {
  10801. var msg;
  10802. /*
  10803. IE < 10 will block XHR requests to different origins. Any property access on the request
  10804. object will raise an exception which we'll attempt to handle by formatting the original
  10805. exception rather than the second one raised when we try to access xhr.status
  10806. */
  10807. try {
  10808. msg = "HTTP " + xhr.status + " attempting to load TileSource";
  10809. } catch ( e ) {
  10810. var formattedExc;
  10811. if ( typeof ( exc ) == "undefined" || !exc.toString ) {
  10812. formattedExc = "Unknown error";
  10813. } else {
  10814. formattedExc = exc.toString();
  10815. }
  10816. msg = formattedExc + " attempting to load TileSource";
  10817. }
  10818. /***
  10819. * Raised when an error occurs loading a TileSource.
  10820. *
  10821. * @event open-failed
  10822. * @memberof OpenSeadragon.TileSource
  10823. * @type {object}
  10824. * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event.
  10825. * @property {String} message
  10826. * @property {String} source
  10827. * @property {?Object} userData - Arbitrary subscriber-defined object.
  10828. */
  10829. _this.raiseEvent( 'open-failed', {
  10830. message: msg,
  10831. source: url
  10832. });
  10833. }
  10834. });
  10835. }
  10836. },
  10837. /**
  10838. * Responsible determining if a the particular TileSource supports the
  10839. * data format ( and allowed to apply logic against the url the data was
  10840. * loaded from, if any ). Overriding implementations are expected to do
  10841. * something smart with data and / or url to determine support. Also
  10842. * understand that iteration order of TileSources is not guarunteed so
  10843. * please make sure your data or url is expressive enough to ensure a simple
  10844. * and sufficient mechanisim for clear determination.
  10845. * @function
  10846. * @param {String|Object|Array|Document} data
  10847. * @param {String} url - the url the data was loaded
  10848. * from if any.
  10849. * @return {Boolean}
  10850. */
  10851. supports: function( data, url ) {
  10852. return false;
  10853. },
  10854. /**
  10855. * Responsible for parsing and configuring the
  10856. * image metadata pertinent to this TileSources implementation.
  10857. * This method is not implemented by this class other than to throw an Error
  10858. * announcing you have to implement it. Because of the variety of tile
  10859. * server technologies, and various specifications for building image
  10860. * pyramids, this method is here to allow easy integration.
  10861. * @function
  10862. * @param {String|Object|Array|Document} data
  10863. * @param {String} url - the url the data was loaded
  10864. * from if any.
  10865. * @return {Object} options - A dictionary of keyword arguments sufficient
  10866. * to configure this tile sources constructor.
  10867. * @throws {Error}
  10868. */
  10869. configure: function( data, url ) {
  10870. throw new Error( "Method not implemented." );
  10871. },
  10872. /**
  10873. * Responsible for retrieving the url which will return an image for the
  10874. * region specified by the given x, y, and level components.
  10875. * This method is not implemented by this class other than to throw an Error
  10876. * announcing you have to implement it. Because of the variety of tile
  10877. * server technologies, and various specifications for building image
  10878. * pyramids, this method is here to allow easy integration.
  10879. * @function
  10880. * @param {Number} level
  10881. * @param {Number} x
  10882. * @param {Number} y
  10883. * @throws {Error}
  10884. */
  10885. getTileUrl: function( level, x, y ) {
  10886. throw new Error( "Method not implemented." );
  10887. },
  10888. /**
  10889. * Responsible for retrieving the headers which will be attached to the image request for the
  10890. * region specified by the given x, y, and level components.
  10891. * This option is only relevant if {@link OpenSeadragon.Options}.loadTilesWithAjax is set to true.
  10892. * The headers returned here will override headers specified at the Viewer or TiledImage level.
  10893. * Specifying a falsy value for a header will clear its existing value set at the Viewer or
  10894. * TiledImage level (if any).
  10895. * @function
  10896. * @param {Number} level
  10897. * @param {Number} x
  10898. * @param {Number} y
  10899. * @returns {Object}
  10900. */
  10901. getTileAjaxHeaders: function( level, x, y ) {
  10902. return {};
  10903. },
  10904. /**
  10905. * @function
  10906. * @param {Number} level
  10907. * @param {Number} x
  10908. * @param {Number} y
  10909. */
  10910. tileExists: function( level, x, y ) {
  10911. var numTiles = this.getNumTiles( level );
  10912. return level >= this.minLevel &&
  10913. level <= this.maxLevel &&
  10914. x >= 0 &&
  10915. y >= 0 &&
  10916. x < numTiles.x &&
  10917. y < numTiles.y;
  10918. }
  10919. };
  10920. $.extend( true, $.TileSource.prototype, $.EventSource.prototype );
  10921. /**
  10922. * Decides whether to try to process the response as xml, json, or hand back
  10923. * the text
  10924. * @private
  10925. * @inner
  10926. * @function
  10927. * @param {XMLHttpRequest} xhr - the completed network request
  10928. */
  10929. function processResponse( xhr ){
  10930. var responseText = xhr.responseText,
  10931. status = xhr.status,
  10932. statusText,
  10933. data;
  10934. if ( !xhr ) {
  10935. throw new Error( $.getString( "Errors.Security" ) );
  10936. } else if ( xhr.status !== 200 && xhr.status !== 0 ) {
  10937. status = xhr.status;
  10938. statusText = ( status == 404 ) ?
  10939. "Not Found" :
  10940. xhr.statusText;
  10941. throw new Error( $.getString( "Errors.Status", status, statusText ) );
  10942. }
  10943. if( responseText.match(/\s*<.*/) ){
  10944. try{
  10945. data = ( xhr.responseXML && xhr.responseXML.documentElement ) ?
  10946. xhr.responseXML :
  10947. $.parseXml( responseText );
  10948. } catch (e){
  10949. data = xhr.responseText;
  10950. }
  10951. }else if( responseText.match(/\s*[\{\[].*/) ){
  10952. try{
  10953. data = $.parseJSON(responseText);
  10954. } catch(e){
  10955. data = responseText;
  10956. }
  10957. }else{
  10958. data = responseText;
  10959. }
  10960. return data;
  10961. }
  10962. /**
  10963. * Determines the TileSource Implementation by introspection of OpenSeadragon
  10964. * namespace, calling each TileSource implementation of 'isType'
  10965. * @private
  10966. * @inner
  10967. * @function
  10968. * @param {Object|Array|Document} data - the tile source configuration object
  10969. * @param {String} url - the url where the tile source configuration object was
  10970. * loaded from, if any.
  10971. */
  10972. $.TileSource.determineType = function( tileSource, data, url ){
  10973. var property;
  10974. for( property in OpenSeadragon ){
  10975. if( property.match(/.+TileSource$/) &&
  10976. $.isFunction( OpenSeadragon[ property ] ) &&
  10977. $.isFunction( OpenSeadragon[ property ].prototype.supports ) &&
  10978. OpenSeadragon[ property ].prototype.supports.call( tileSource, data, url )
  10979. ){
  10980. return OpenSeadragon[ property ];
  10981. }
  10982. }
  10983. $.console.error( "No TileSource was able to open %s %s", url, data );
  10984. };
  10985. }( OpenSeadragon ));
  10986. /*
  10987. * OpenSeadragon - DziTileSource
  10988. *
  10989. * Copyright (C) 2009 CodePlex Foundation
  10990. * Copyright (C) 2010-2013 OpenSeadragon contributors
  10991. *
  10992. * Redistribution and use in source and binary forms, with or without
  10993. * modification, are permitted provided that the following conditions are
  10994. * met:
  10995. *
  10996. * - Redistributions of source code must retain the above copyright notice,
  10997. * this list of conditions and the following disclaimer.
  10998. *
  10999. * - Redistributions in binary form must reproduce the above copyright
  11000. * notice, this list of conditions and the following disclaimer in the
  11001. * documentation and/or other materials provided with the distribution.
  11002. *
  11003. * - Neither the name of CodePlex Foundation nor the names of its
  11004. * contributors may be used to endorse or promote products derived from
  11005. * this software without specific prior written permission.
  11006. *
  11007. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  11008. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  11009. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  11010. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  11011. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  11012. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  11013. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  11014. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  11015. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  11016. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  11017. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  11018. */
  11019. (function( $ ){
  11020. /**
  11021. * @class DziTileSource
  11022. * @memberof OpenSeadragon
  11023. * @extends OpenSeadragon.TileSource
  11024. * @param {Number|Object} width - the pixel width of the image or the idiomatic
  11025. * options object which is used instead of positional arguments.
  11026. * @param {Number} height
  11027. * @param {Number} tileSize
  11028. * @param {Number} tileOverlap
  11029. * @param {String} tilesUrl
  11030. * @param {String} fileFormat
  11031. * @param {OpenSeadragon.DisplayRect[]} displayRects
  11032. * @property {String} tilesUrl
  11033. * @property {String} fileFormat
  11034. * @property {OpenSeadragon.DisplayRect[]} displayRects
  11035. */
  11036. $.DziTileSource = function( width, height, tileSize, tileOverlap, tilesUrl, fileFormat, displayRects, minLevel, maxLevel ) {
  11037. var i,
  11038. rect,
  11039. level,
  11040. options;
  11041. if( $.isPlainObject( width ) ){
  11042. options = width;
  11043. }else{
  11044. options = {
  11045. width: arguments[ 0 ],
  11046. height: arguments[ 1 ],
  11047. tileSize: arguments[ 2 ],
  11048. tileOverlap: arguments[ 3 ],
  11049. tilesUrl: arguments[ 4 ],
  11050. fileFormat: arguments[ 5 ],
  11051. displayRects: arguments[ 6 ],
  11052. minLevel: arguments[ 7 ],
  11053. maxLevel: arguments[ 8 ]
  11054. };
  11055. }
  11056. this._levelRects = {};
  11057. this.tilesUrl = options.tilesUrl;
  11058. this.fileFormat = options.fileFormat;
  11059. this.displayRects = options.displayRects;
  11060. if ( this.displayRects ) {
  11061. for ( i = this.displayRects.length - 1; i >= 0; i-- ) {
  11062. rect = this.displayRects[ i ];
  11063. for ( level = rect.minLevel; level <= rect.maxLevel; level++ ) {
  11064. if ( !this._levelRects[ level ] ) {
  11065. this._levelRects[ level ] = [];
  11066. }
  11067. this._levelRects[ level ].push( rect );
  11068. }
  11069. }
  11070. }
  11071. $.TileSource.apply( this, [ options ] );
  11072. };
  11073. $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.DziTileSource.prototype */{
  11074. /**
  11075. * Determine if the data and/or url imply the image service is supported by
  11076. * this tile source.
  11077. * @function
  11078. * @param {Object|Array} data
  11079. * @param {String} optional - url
  11080. */
  11081. supports: function( data, url ){
  11082. var ns;
  11083. if ( data.Image ) {
  11084. ns = data.Image.xmlns;
  11085. } else if ( data.documentElement) {
  11086. if ("Image" == data.documentElement.localName || "Image" == data.documentElement.tagName) {
  11087. ns = data.documentElement.namespaceURI;
  11088. }
  11089. }
  11090. ns = (ns || '').toLowerCase();
  11091. return (ns.indexOf('schemas.microsoft.com/deepzoom/2008') !== -1 ||
  11092. ns.indexOf('schemas.microsoft.com/deepzoom/2009') !== -1);
  11093. },
  11094. /**
  11095. *
  11096. * @function
  11097. * @param {Object|XMLDocument} data - the raw configuration
  11098. * @param {String} url - the url the data was retrieved from if any.
  11099. * @return {Object} options - A dictionary of keyword arguments sufficient
  11100. * to configure this tile sources constructor.
  11101. */
  11102. configure: function( data, url ){
  11103. var options;
  11104. if( !$.isPlainObject(data) ){
  11105. options = configureFromXML( this, data );
  11106. }else{
  11107. options = configureFromObject( this, data );
  11108. }
  11109. if (url && !options.tilesUrl) {
  11110. options.tilesUrl = url.replace(
  11111. /([^\/]+?)(\.(dzi|xml|js)?(\?[^\/]*)?)?\/?$/, '$1_files/');
  11112. if (url.search(/\.(dzi|xml|js)\?/) != -1) {
  11113. options.queryParams = url.match(/\?.*/);
  11114. }else{
  11115. options.queryParams = '';
  11116. }
  11117. }
  11118. return options;
  11119. },
  11120. /**
  11121. * @function
  11122. * @param {Number} level
  11123. * @param {Number} x
  11124. * @param {Number} y
  11125. */
  11126. getTileUrl: function( level, x, y ) {
  11127. return [ this.tilesUrl, level, '/', x, '_', y, '.', this.fileFormat, this.queryParams ].join( '' );
  11128. },
  11129. /**
  11130. * @function
  11131. * @param {Number} level
  11132. * @param {Number} x
  11133. * @param {Number} y
  11134. */
  11135. tileExists: function( level, x, y ) {
  11136. var rects = this._levelRects[ level ],
  11137. rect,
  11138. scale,
  11139. xMin,
  11140. yMin,
  11141. xMax,
  11142. yMax,
  11143. i;
  11144. if ((this.minLevel && level < this.minLevel) || (this.maxLevel && level > this.maxLevel)) {
  11145. return false;
  11146. }
  11147. if ( !rects || !rects.length ) {
  11148. return true;
  11149. }
  11150. for ( i = rects.length - 1; i >= 0; i-- ) {
  11151. rect = rects[ i ];
  11152. if ( level < rect.minLevel || level > rect.maxLevel ) {
  11153. continue;
  11154. }
  11155. scale = this.getLevelScale( level );
  11156. xMin = rect.x * scale;
  11157. yMin = rect.y * scale;
  11158. xMax = xMin + rect.width * scale;
  11159. yMax = yMin + rect.height * scale;
  11160. xMin = Math.floor( xMin / this._tileWidth );
  11161. yMin = Math.floor( yMin / this._tileWidth ); // DZI tiles are square, so we just use _tileWidth
  11162. xMax = Math.ceil( xMax / this._tileWidth );
  11163. yMax = Math.ceil( yMax / this._tileWidth );
  11164. if ( xMin <= x && x < xMax && yMin <= y && y < yMax ) {
  11165. return true;
  11166. }
  11167. }
  11168. return false;
  11169. }
  11170. });
  11171. /**
  11172. * @private
  11173. * @inner
  11174. * @function
  11175. */
  11176. function configureFromXML( tileSource, xmlDoc ){
  11177. if ( !xmlDoc || !xmlDoc.documentElement ) {
  11178. throw new Error( $.getString( "Errors.Xml" ) );
  11179. }
  11180. var root = xmlDoc.documentElement,
  11181. rootName = root.localName || root.tagName,
  11182. ns = xmlDoc.documentElement.namespaceURI,
  11183. configuration = null,
  11184. displayRects = [],
  11185. dispRectNodes,
  11186. dispRectNode,
  11187. rectNode,
  11188. sizeNode,
  11189. i;
  11190. if ( rootName == "Image" ) {
  11191. try {
  11192. sizeNode = root.getElementsByTagName("Size" )[ 0 ];
  11193. if (sizeNode === undefined) {
  11194. sizeNode = root.getElementsByTagNameNS(ns, "Size" )[ 0 ];
  11195. }
  11196. configuration = {
  11197. Image: {
  11198. xmlns: "http://schemas.microsoft.com/deepzoom/2008",
  11199. Url: root.getAttribute( "Url" ),
  11200. Format: root.getAttribute( "Format" ),
  11201. DisplayRect: null,
  11202. Overlap: parseInt( root.getAttribute( "Overlap" ), 10 ),
  11203. TileSize: parseInt( root.getAttribute( "TileSize" ), 10 ),
  11204. Size: {
  11205. Height: parseInt( sizeNode.getAttribute( "Height" ), 10 ),
  11206. Width: parseInt( sizeNode.getAttribute( "Width" ), 10 )
  11207. }
  11208. }
  11209. };
  11210. if ( !$.imageFormatSupported( configuration.Image.Format ) ) {
  11211. throw new Error(
  11212. $.getString( "Errors.ImageFormat", configuration.Image.Format.toUpperCase() )
  11213. );
  11214. }
  11215. dispRectNodes = root.getElementsByTagName("DisplayRect" );
  11216. if (dispRectNodes === undefined) {
  11217. dispRectNodes = root.getElementsByTagNameNS(ns, "DisplayRect" )[ 0 ];
  11218. }
  11219. for ( i = 0; i < dispRectNodes.length; i++ ) {
  11220. dispRectNode = dispRectNodes[ i ];
  11221. rectNode = dispRectNode.getElementsByTagName("Rect" )[ 0 ];
  11222. if (rectNode === undefined) {
  11223. rectNode = dispRectNode.getElementsByTagNameNS(ns, "Rect" )[ 0 ];
  11224. }
  11225. displayRects.push({
  11226. Rect: {
  11227. X: parseInt( rectNode.getAttribute( "X" ), 10 ),
  11228. Y: parseInt( rectNode.getAttribute( "Y" ), 10 ),
  11229. Width: parseInt( rectNode.getAttribute( "Width" ), 10 ),
  11230. Height: parseInt( rectNode.getAttribute( "Height" ), 10 ),
  11231. MinLevel: parseInt( dispRectNode.getAttribute( "MinLevel" ), 10 ),
  11232. MaxLevel: parseInt( dispRectNode.getAttribute( "MaxLevel" ), 10 )
  11233. }
  11234. });
  11235. }
  11236. if( displayRects.length ){
  11237. configuration.Image.DisplayRect = displayRects;
  11238. }
  11239. return configureFromObject( tileSource, configuration );
  11240. } catch ( e ) {
  11241. throw (e instanceof Error) ?
  11242. e :
  11243. new Error( $.getString("Errors.Dzi") );
  11244. }
  11245. } else if ( rootName == "Collection" ) {
  11246. throw new Error( $.getString( "Errors.Dzc" ) );
  11247. } else if ( rootName == "Error" ) {
  11248. var messageNode = root.getElementsByTagName("Message")[0];
  11249. var message = messageNode.firstChild.nodeValue;
  11250. throw new Error(message);
  11251. }
  11252. throw new Error( $.getString( "Errors.Dzi" ) );
  11253. }
  11254. /**
  11255. * @private
  11256. * @inner
  11257. * @function
  11258. */
  11259. function configureFromObject( tileSource, configuration ){
  11260. var imageData = configuration.Image,
  11261. tilesUrl = imageData.Url,
  11262. fileFormat = imageData.Format,
  11263. sizeData = imageData.Size,
  11264. dispRectData = imageData.DisplayRect || [],
  11265. width = parseInt( sizeData.Width, 10 ),
  11266. height = parseInt( sizeData.Height, 10 ),
  11267. tileSize = parseInt( imageData.TileSize, 10 ),
  11268. tileOverlap = parseInt( imageData.Overlap, 10 ),
  11269. displayRects = [],
  11270. rectData,
  11271. i;
  11272. //TODO: need to figure out out to better handle image format compatibility
  11273. // which actually includes additional file formats like xml and pdf
  11274. // and plain text for various tilesource implementations to avoid low
  11275. // level errors.
  11276. //
  11277. // For now, just don't perform the check.
  11278. //
  11279. /*if ( !imageFormatSupported( fileFormat ) ) {
  11280. throw new Error(
  11281. $.getString( "Errors.ImageFormat", fileFormat.toUpperCase() )
  11282. );
  11283. }*/
  11284. for ( i = 0; i < dispRectData.length; i++ ) {
  11285. rectData = dispRectData[ i ].Rect;
  11286. displayRects.push( new $.DisplayRect(
  11287. parseInt( rectData.X, 10 ),
  11288. parseInt( rectData.Y, 10 ),
  11289. parseInt( rectData.Width, 10 ),
  11290. parseInt( rectData.Height, 10 ),
  11291. parseInt( rectData.MinLevel, 10 ),
  11292. parseInt( rectData.MaxLevel, 10 )
  11293. ));
  11294. }
  11295. return $.extend(true, {
  11296. width: width, /* width *required */
  11297. height: height, /* height *required */
  11298. tileSize: tileSize, /* tileSize *required */
  11299. tileOverlap: tileOverlap, /* tileOverlap *required */
  11300. minLevel: null, /* minLevel */
  11301. maxLevel: null, /* maxLevel */
  11302. tilesUrl: tilesUrl, /* tilesUrl */
  11303. fileFormat: fileFormat, /* fileFormat */
  11304. displayRects: displayRects /* displayRects */
  11305. }, configuration );
  11306. }
  11307. }( OpenSeadragon ));
  11308. /*
  11309. * OpenSeadragon - IIIFTileSource
  11310. *
  11311. * Copyright (C) 2009 CodePlex Foundation
  11312. * Copyright (C) 2010-2013 OpenSeadragon contributors
  11313. *
  11314. * Redistribution and use in source and binary forms, with or without
  11315. * modification, are permitted provided that the following conditions are
  11316. * met:
  11317. *
  11318. * - Redistributions of source code must retain the above copyright notice,
  11319. * this list of conditions and the following disclaimer.
  11320. *
  11321. * - Redistributions in binary form must reproduce the above copyright
  11322. * notice, this list of conditions and the following disclaimer in the
  11323. * documentation and/or other materials provided with the distribution.
  11324. *
  11325. * - Neither the name of CodePlex Foundation nor the names of its
  11326. * contributors may be used to endorse or promote products derived from
  11327. * this software without specific prior written permission.
  11328. *
  11329. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  11330. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  11331. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  11332. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  11333. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  11334. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  11335. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  11336. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  11337. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  11338. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  11339. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  11340. */
  11341. (function( $ ){
  11342. /**
  11343. * @class IIIFTileSource
  11344. * @classdesc A client implementation of the International Image Interoperability Framework
  11345. * Format: Image API 1.0 - 2.1
  11346. *
  11347. * @memberof OpenSeadragon
  11348. * @extends OpenSeadragon.TileSource
  11349. * @see http://iiif.io/api/image/
  11350. * @param {String} [options.tileFormat='jpg']
  11351. * The extension that will be used when requiring tiles.
  11352. */
  11353. $.IIIFTileSource = function( options ){
  11354. /* eslint-disable camelcase */
  11355. $.extend( true, this, options );
  11356. if ( !( this.height && this.width && this['@id'] ) ) {
  11357. throw new Error( 'IIIF required parameters not provided.' );
  11358. }
  11359. options.tileSizePerScaleFactor = {};
  11360. this.tileFormat = this.tileFormat || 'jpg';
  11361. // N.B. 2.0 renamed scale_factors to scaleFactors
  11362. if ( this.tile_width && this.tile_height ) {
  11363. options.tileWidth = this.tile_width;
  11364. options.tileHeight = this.tile_height;
  11365. } else if ( this.tile_width ) {
  11366. options.tileSize = this.tile_width;
  11367. } else if ( this.tile_height ) {
  11368. options.tileSize = this.tile_height;
  11369. } else if ( this.tiles ) {
  11370. // Version 2.0 forwards
  11371. if ( this.tiles.length == 1 ) {
  11372. options.tileWidth = this.tiles[0].width;
  11373. // Use height if provided, otherwise assume square tiles and use width.
  11374. options.tileHeight = this.tiles[0].height || this.tiles[0].width;
  11375. this.scale_factors = this.tiles[0].scaleFactors;
  11376. } else {
  11377. // Multiple tile sizes at different levels
  11378. this.scale_factors = [];
  11379. for (var t = 0; t < this.tiles.length; t++ ) {
  11380. for (var sf = 0; sf < this.tiles[t].scaleFactors.length; sf++) {
  11381. var scaleFactor = this.tiles[t].scaleFactors[sf];
  11382. this.scale_factors.push(scaleFactor);
  11383. options.tileSizePerScaleFactor[scaleFactor] = {
  11384. width: this.tiles[t].width,
  11385. height: this.tiles[t].height || this.tiles[t].width
  11386. };
  11387. }
  11388. }
  11389. }
  11390. } else if ( canBeTiled(options.profile) ) {
  11391. // use the largest of tileOptions that is smaller than the short dimension
  11392. var shortDim = Math.min( this.height, this.width ),
  11393. tileOptions = [256, 512, 1024],
  11394. smallerTiles = [];
  11395. for ( var c = 0; c < tileOptions.length; c++ ) {
  11396. if ( tileOptions[c] <= shortDim ) {
  11397. smallerTiles.push( tileOptions[c] );
  11398. }
  11399. }
  11400. if ( smallerTiles.length > 0 ) {
  11401. options.tileSize = Math.max.apply( null, smallerTiles );
  11402. } else {
  11403. // If we're smaller than 256, just use the short side.
  11404. options.tileSize = shortDim;
  11405. }
  11406. } else if (this.sizes && this.sizes.length > 0) {
  11407. // This info.json can't be tiled, but we can still construct a legacy pyramid from the sizes array.
  11408. // In this mode, IIIFTileSource will call functions from the abstract baseTileSource or the
  11409. // LegacyTileSource instead of performing IIIF tiling.
  11410. this.emulateLegacyImagePyramid = true;
  11411. options.levels = constructLevels( this );
  11412. // use the largest available size to define tiles
  11413. $.extend( true, options, {
  11414. width: options.levels[ options.levels.length - 1 ].width,
  11415. height: options.levels[ options.levels.length - 1 ].height,
  11416. tileSize: Math.max( options.height, options.width ),
  11417. tileOverlap: 0,
  11418. minLevel: 0,
  11419. maxLevel: options.levels.length - 1
  11420. });
  11421. this.levels = options.levels;
  11422. } else {
  11423. $.console.error("Nothing in the info.json to construct image pyramids from");
  11424. }
  11425. if (!options.maxLevel && !this.emulateLegacyImagePyramid) {
  11426. if (!this.scale_factors) {
  11427. options.maxLevel = Number(Math.ceil(Math.log(Math.max(this.width, this.height), 2)));
  11428. } else {
  11429. var maxScaleFactor = Math.max.apply(null, this.scale_factors);
  11430. options.maxLevel = Math.round(Math.log(maxScaleFactor) * Math.LOG2E);
  11431. }
  11432. }
  11433. $.TileSource.apply( this, [ options ] );
  11434. };
  11435. $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.IIIFTileSource.prototype */{
  11436. /**
  11437. * Determine if the data and/or url imply the image service is supported by
  11438. * this tile source.
  11439. * @function
  11440. * @param {Object|Array} data
  11441. * @param {String} optional - url
  11442. */
  11443. supports: function( data, url ) {
  11444. // Version 2.0 and forwards
  11445. if (data.protocol && data.protocol == 'http://iiif.io/api/image') {
  11446. return true;
  11447. // Version 1.1
  11448. } else if ( data['@context'] && (
  11449. data['@context'] == "http://library.stanford.edu/iiif/image-api/1.1/context.json" ||
  11450. data['@context'] == "http://iiif.io/api/image/1/context.json") ) {
  11451. // N.B. the iiif.io context is wrong, but where the representation lives so likely to be used
  11452. return true;
  11453. // Version 1.0
  11454. } else if ( data.profile &&
  11455. data.profile.indexOf("http://library.stanford.edu/iiif/image-api/compliance.html") === 0) {
  11456. return true;
  11457. } else if ( data.identifier && data.width && data.height ) {
  11458. return true;
  11459. } else if ( data.documentElement &&
  11460. "info" == data.documentElement.tagName &&
  11461. "http://library.stanford.edu/iiif/image-api/ns/" ==
  11462. data.documentElement.namespaceURI) {
  11463. return true;
  11464. // Not IIIF
  11465. } else {
  11466. return false;
  11467. }
  11468. },
  11469. /**
  11470. *
  11471. * @function
  11472. * @param {Object} data - the raw configuration
  11473. * @example <caption>IIIF 1.1 Info Looks like this</caption>
  11474. * {
  11475. * "@context" : "http://library.stanford.edu/iiif/image-api/1.1/context.json",
  11476. * "@id" : "http://iiif.example.com/prefix/1E34750D-38DB-4825-A38A-B60A345E591C",
  11477. * "width" : 6000,
  11478. * "height" : 4000,
  11479. * "scale_factors" : [ 1, 2, 4 ],
  11480. * "tile_width" : 1024,
  11481. * "tile_height" : 1024,
  11482. * "formats" : [ "jpg", "png" ],
  11483. * "qualities" : [ "native", "grey" ],
  11484. * "profile" : "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0"
  11485. * }
  11486. */
  11487. configure: function( data, url ){
  11488. // Try to deduce our version and fake it upwards if needed
  11489. if ( !$.isPlainObject(data) ) {
  11490. var options = configureFromXml10( data );
  11491. options['@context'] = "http://iiif.io/api/image/1.0/context.json";
  11492. options['@id'] = url.replace('/info.xml', '');
  11493. return options;
  11494. } else {
  11495. if ( !data['@context'] ) {
  11496. data['@context'] = 'http://iiif.io/api/image/1.0/context.json';
  11497. data['@id'] = url.replace('/info.json', '');
  11498. }
  11499. if(data.preferredFormats) {
  11500. for (var f = 0; f < data.preferredFormats.length; f++ ) {
  11501. if ( OpenSeadragon.imageFormatSupported(data.preferredFormats[f]) ) {
  11502. data.tileFormat = data.preferredFormats[f];
  11503. break;
  11504. }
  11505. }
  11506. }
  11507. return data;
  11508. }
  11509. },
  11510. /**
  11511. * Return the tileWidth for the given level.
  11512. * @function
  11513. * @param {Number} level
  11514. */
  11515. getTileWidth: function( level ) {
  11516. if(this.emulateLegacyImagePyramid) {
  11517. return $.TileSource.prototype.getTileWidth.call(this, level);
  11518. }
  11519. var scaleFactor = Math.pow(2, this.maxLevel - level);
  11520. if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) {
  11521. return this.tileSizePerScaleFactor[scaleFactor].width;
  11522. }
  11523. return this._tileWidth;
  11524. },
  11525. /**
  11526. * Return the tileHeight for the given level.
  11527. * @function
  11528. * @param {Number} level
  11529. */
  11530. getTileHeight: function( level ) {
  11531. if(this.emulateLegacyImagePyramid) {
  11532. return $.TileSource.prototype.getTileHeight.call(this, level);
  11533. }
  11534. var scaleFactor = Math.pow(2, this.maxLevel - level);
  11535. if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) {
  11536. return this.tileSizePerScaleFactor[scaleFactor].height;
  11537. }
  11538. return this._tileHeight;
  11539. },
  11540. /**
  11541. * @function
  11542. * @param {Number} level
  11543. */
  11544. getLevelScale: function ( level ) {
  11545. if(this.emulateLegacyImagePyramid) {
  11546. var levelScale = NaN;
  11547. if (this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel) {
  11548. levelScale =
  11549. this.levels[level].width /
  11550. this.levels[this.maxLevel].width;
  11551. }
  11552. return levelScale;
  11553. }
  11554. return $.TileSource.prototype.getLevelScale.call(this, level);
  11555. },
  11556. /**
  11557. * @function
  11558. * @param {Number} level
  11559. */
  11560. getNumTiles: function( level ) {
  11561. if(this.emulateLegacyImagePyramid) {
  11562. var scale = this.getLevelScale(level);
  11563. if (scale) {
  11564. return new $.Point(1, 1);
  11565. } else {
  11566. return new $.Point(0, 0);
  11567. }
  11568. }
  11569. return $.TileSource.prototype.getNumTiles.call(this, level);
  11570. },
  11571. /**
  11572. * @function
  11573. * @param {Number} level
  11574. * @param {OpenSeadragon.Point} point
  11575. */
  11576. getTileAtPoint: function( level, point ) {
  11577. if(this.emulateLegacyImagePyramid) {
  11578. return new $.Point(0, 0);
  11579. }
  11580. return $.TileSource.prototype.getTileAtPoint.call(this, level, point);
  11581. },
  11582. /**
  11583. * Responsible for retrieving the url which will return an image for the
  11584. * region specified by the given x, y, and level components.
  11585. * @function
  11586. * @param {Number} level - z index
  11587. * @param {Number} x
  11588. * @param {Number} y
  11589. * @throws {Error}
  11590. */
  11591. getTileUrl: function( level, x, y ){
  11592. if(this.emulateLegacyImagePyramid) {
  11593. var url = null;
  11594. if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) {
  11595. url = this.levels[ level ].url;
  11596. }
  11597. return url;
  11598. }
  11599. //# constants
  11600. var IIIF_ROTATION = '0',
  11601. //## get the scale (level as a decimal)
  11602. scale = Math.pow( 0.5, this.maxLevel - level ),
  11603. //# image dimensions at this level
  11604. levelWidth = Math.ceil( this.width * scale ),
  11605. levelHeight = Math.ceil( this.height * scale ),
  11606. //## iiif region
  11607. tileWidth,
  11608. tileHeight,
  11609. iiifTileSizeWidth,
  11610. iiifTileSizeHeight,
  11611. iiifRegion,
  11612. iiifTileX,
  11613. iiifTileY,
  11614. iiifTileW,
  11615. iiifTileH,
  11616. iiifSize,
  11617. iiifSizeW,
  11618. iiifQuality,
  11619. uri,
  11620. isv1;
  11621. tileWidth = this.getTileWidth(level);
  11622. tileHeight = this.getTileHeight(level);
  11623. iiifTileSizeWidth = Math.ceil( tileWidth / scale );
  11624. iiifTileSizeHeight = Math.ceil( tileHeight / scale );
  11625. isv1 = ( this['@context'].indexOf('/1.0/context.json') > -1 ||
  11626. this['@context'].indexOf('/1.1/context.json') > -1 ||
  11627. this['@context'].indexOf('/1/context.json') > -1 );
  11628. if (isv1) {
  11629. iiifQuality = "native." + this.tileFormat;
  11630. } else {
  11631. iiifQuality = "default." + this.tileFormat;
  11632. }
  11633. if ( levelWidth < tileWidth && levelHeight < tileHeight ){
  11634. if ( isv1 || levelWidth !== this.width ) {
  11635. iiifSize = levelWidth + ",";
  11636. } else {
  11637. iiifSize = "max";
  11638. }
  11639. iiifRegion = 'full';
  11640. } else {
  11641. iiifTileX = x * iiifTileSizeWidth;
  11642. iiifTileY = y * iiifTileSizeHeight;
  11643. iiifTileW = Math.min( iiifTileSizeWidth, this.width - iiifTileX );
  11644. iiifTileH = Math.min( iiifTileSizeHeight, this.height - iiifTileY );
  11645. if ( x === 0 && y === 0 && iiifTileW === this.width && iiifTileH === this.height ) {
  11646. iiifRegion = "full";
  11647. } else {
  11648. iiifRegion = [ iiifTileX, iiifTileY, iiifTileW, iiifTileH ].join( ',' );
  11649. }
  11650. iiifSizeW = Math.ceil( iiifTileW * scale );
  11651. if ( (!isv1) && iiifSizeW === this.width ) {
  11652. iiifSize = "max";
  11653. } else {
  11654. iiifSize = iiifSizeW + ",";
  11655. }
  11656. }
  11657. uri = [ this['@id'], iiifRegion, iiifSize, IIIF_ROTATION, iiifQuality ].join( '/' );
  11658. return uri;
  11659. }
  11660. });
  11661. /**
  11662. * Determine whether arbitrary tile requests can be made against a service with the given profile
  11663. * @function
  11664. * @param {array} profile - IIIF profile array
  11665. * @throws {Error}
  11666. */
  11667. function canBeTiled ( profile ) {
  11668. var level0Profiles = [
  11669. "http://library.stanford.edu/iiif/image-api/compliance.html#level0",
  11670. "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0",
  11671. "http://iiif.io/api/image/2/level0.json"
  11672. ];
  11673. var isLevel0 = (level0Profiles.indexOf(profile[0]) !== -1);
  11674. var hasSizeByW = false;
  11675. if ( profile.length > 1 && profile[1].supports ) {
  11676. hasSizeByW = profile[1].supports.indexOf( "sizeByW" ) !== -1;
  11677. }
  11678. return !isLevel0 || hasSizeByW;
  11679. }
  11680. /**
  11681. * Build the legacy pyramid URLs (one tile per level)
  11682. * @function
  11683. * @param {object} options - infoJson
  11684. * @throws {Error}
  11685. */
  11686. function constructLevels(options) {
  11687. var levels = [];
  11688. for(var i = 0; i < options.sizes.length; i++) {
  11689. levels.push({
  11690. url: options['@id'] + '/full/' + options.sizes[i].width + ',/0/default.' + options.tileFormat,
  11691. width: options.sizes[i].width,
  11692. height: options.sizes[i].height
  11693. });
  11694. }
  11695. return levels.sort(function(a, b) {
  11696. return a.width - b.width;
  11697. });
  11698. }
  11699. function configureFromXml10(xmlDoc) {
  11700. //parse the xml
  11701. if ( !xmlDoc || !xmlDoc.documentElement ) {
  11702. throw new Error( $.getString( "Errors.Xml" ) );
  11703. }
  11704. var root = xmlDoc.documentElement,
  11705. rootName = root.tagName,
  11706. configuration = null;
  11707. if ( rootName == "info" ) {
  11708. try {
  11709. configuration = {};
  11710. parseXML10( root, configuration );
  11711. return configuration;
  11712. } catch ( e ) {
  11713. throw (e instanceof Error) ?
  11714. e :
  11715. new Error( $.getString("Errors.IIIF") );
  11716. }
  11717. }
  11718. throw new Error( $.getString( "Errors.IIIF" ) );
  11719. }
  11720. function parseXML10( node, configuration, property ) {
  11721. var i,
  11722. value;
  11723. if ( node.nodeType == 3 && property ) {//text node
  11724. value = node.nodeValue.trim();
  11725. if( value.match(/^\d*$/)){
  11726. value = Number( value );
  11727. }
  11728. if( !configuration[ property ] ){
  11729. configuration[ property ] = value;
  11730. }else{
  11731. if( !$.isArray( configuration[ property ] ) ){
  11732. configuration[ property ] = [ configuration[ property ] ];
  11733. }
  11734. configuration[ property ].push( value );
  11735. }
  11736. } else if( node.nodeType == 1 ){
  11737. for( i = 0; i < node.childNodes.length; i++ ){
  11738. parseXML10( node.childNodes[ i ], configuration, node.nodeName );
  11739. }
  11740. }
  11741. }
  11742. }( OpenSeadragon ));
  11743. /*
  11744. * OpenSeadragon - OsmTileSource
  11745. *
  11746. * Copyright (C) 2009 CodePlex Foundation
  11747. * Copyright (C) 2010-2013 OpenSeadragon contributors
  11748. *
  11749. * Redistribution and use in source and binary forms, with or without
  11750. * modification, are permitted provided that the following conditions are
  11751. * met:
  11752. *
  11753. * - Redistributions of source code must retain the above copyright notice,
  11754. * this list of conditions and the following disclaimer.
  11755. *
  11756. * - Redistributions in binary form must reproduce the above copyright
  11757. * notice, this list of conditions and the following disclaimer in the
  11758. * documentation and/or other materials provided with the distribution.
  11759. *
  11760. * - Neither the name of CodePlex Foundation nor the names of its
  11761. * contributors may be used to endorse or promote products derived from
  11762. * this software without specific prior written permission.
  11763. *
  11764. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  11765. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  11766. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  11767. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  11768. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  11769. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  11770. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  11771. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  11772. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  11773. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  11774. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  11775. */
  11776. /*
  11777. * Derived from the OSM tile source in Rainer Simon's seajax-utils project
  11778. * <http://github.com/rsimon/seajax-utils>. Rainer Simon has contributed
  11779. * the included code to the OpenSeadragon project under the New BSD license;
  11780. * see <https://github.com/openseadragon/openseadragon/issues/58>.
  11781. */
  11782. (function( $ ){
  11783. /**
  11784. * @class OsmTileSource
  11785. * @classdesc A tilesource implementation for OpenStreetMap.<br><br>
  11786. *
  11787. * Note 1. Zoomlevels. Deep Zoom and OSM define zoom levels differently. In Deep
  11788. * Zoom, level 0 equals an image of 1x1 pixels. In OSM, level 0 equals an image of
  11789. * 256x256 levels (see http://gasi.ch/blog/inside-deep-zoom-2). I.e. there is a
  11790. * difference of log2(256)=8 levels.<br><br>
  11791. *
  11792. * Note 2. Image dimension. According to the OSM Wiki
  11793. * (http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels)
  11794. * the highest Mapnik zoom level has 256.144x256.144 tiles, with a 256x256
  11795. * pixel size. I.e. the Deep Zoom image dimension is 65.572.864x65.572.864
  11796. * pixels.
  11797. *
  11798. * @memberof OpenSeadragon
  11799. * @extends OpenSeadragon.TileSource
  11800. * @param {Number|Object} width - the pixel width of the image or the idiomatic
  11801. * options object which is used instead of positional arguments.
  11802. * @param {Number} height
  11803. * @param {Number} tileSize
  11804. * @param {Number} tileOverlap
  11805. * @param {String} tilesUrl
  11806. */
  11807. $.OsmTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) {
  11808. var options;
  11809. if( $.isPlainObject( width ) ){
  11810. options = width;
  11811. }else{
  11812. options = {
  11813. width: arguments[0],
  11814. height: arguments[1],
  11815. tileSize: arguments[2],
  11816. tileOverlap: arguments[3],
  11817. tilesUrl: arguments[4]
  11818. };
  11819. }
  11820. //apply default setting for standard public OpenStreatMaps service
  11821. //but allow them to be specified so fliks can host there own instance
  11822. //or apply against other services supportting the same standard
  11823. if( !options.width || !options.height ){
  11824. options.width = 65572864;
  11825. options.height = 65572864;
  11826. }
  11827. if( !options.tileSize ){
  11828. options.tileSize = 256;
  11829. options.tileOverlap = 0;
  11830. }
  11831. if( !options.tilesUrl ){
  11832. options.tilesUrl = "http://tile.openstreetmap.org/";
  11833. }
  11834. options.minLevel = 8;
  11835. $.TileSource.apply( this, [ options ] );
  11836. };
  11837. $.extend( $.OsmTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.OsmTileSource.prototype */{
  11838. /**
  11839. * Determine if the data and/or url imply the image service is supported by
  11840. * this tile source.
  11841. * @function
  11842. * @param {Object|Array} data
  11843. * @param {String} optional - url
  11844. */
  11845. supports: function( data, url ){
  11846. return (
  11847. data.type &&
  11848. "openstreetmaps" == data.type
  11849. );
  11850. },
  11851. /**
  11852. *
  11853. * @function
  11854. * @param {Object} data - the raw configuration
  11855. * @param {String} url - the url the data was retrieved from if any.
  11856. * @return {Object} options - A dictionary of keyword arguments sufficient
  11857. * to configure this tile sources constructor.
  11858. */
  11859. configure: function( data, url ){
  11860. return data;
  11861. },
  11862. /**
  11863. * @function
  11864. * @param {Number} level
  11865. * @param {Number} x
  11866. * @param {Number} y
  11867. */
  11868. getTileUrl: function( level, x, y ) {
  11869. return this.tilesUrl + (level - 8) + "/" + x + "/" + y + ".png";
  11870. }
  11871. });
  11872. }( OpenSeadragon ));
  11873. /*
  11874. * OpenSeadragon - TmsTileSource
  11875. *
  11876. * Copyright (C) 2009 CodePlex Foundation
  11877. * Copyright (C) 2010-2013 OpenSeadragon contributors
  11878. *
  11879. * Redistribution and use in source and binary forms, with or without
  11880. * modification, are permitted provided that the following conditions are
  11881. * met:
  11882. *
  11883. * - Redistributions of source code must retain the above copyright notice,
  11884. * this list of conditions and the following disclaimer.
  11885. *
  11886. * - Redistributions in binary form must reproduce the above copyright
  11887. * notice, this list of conditions and the following disclaimer in the
  11888. * documentation and/or other materials provided with the distribution.
  11889. *
  11890. * - Neither the name of CodePlex Foundation nor the names of its
  11891. * contributors may be used to endorse or promote products derived from
  11892. * this software without specific prior written permission.
  11893. *
  11894. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  11895. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  11896. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  11897. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  11898. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  11899. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  11900. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  11901. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  11902. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  11903. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  11904. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  11905. */
  11906. /*
  11907. * Derived from the TMS tile source in Rainer Simon's seajax-utils project
  11908. * <http://github.com/rsimon/seajax-utils>. Rainer Simon has contributed
  11909. * the included code to the OpenSeadragon project under the New BSD license;
  11910. * see <https://github.com/openseadragon/openseadragon/issues/58>.
  11911. */
  11912. (function( $ ){
  11913. /**
  11914. * @class TmsTileSource
  11915. * @classdesc A tilesource implementation for Tiled Map Services (TMS).
  11916. * TMS tile scheme ( [ as supported by OpenLayers ] is described here
  11917. * ( http://openlayers.org/dev/examples/tms.html ).
  11918. *
  11919. * @memberof OpenSeadragon
  11920. * @extends OpenSeadragon.TileSource
  11921. * @param {Number|Object} width - the pixel width of the image or the idiomatic
  11922. * options object which is used instead of positional arguments.
  11923. * @param {Number} height
  11924. * @param {Number} tileSize
  11925. * @param {Number} tileOverlap
  11926. * @param {String} tilesUrl
  11927. */
  11928. $.TmsTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) {
  11929. var options;
  11930. if( $.isPlainObject( width ) ){
  11931. options = width;
  11932. }else{
  11933. options = {
  11934. width: arguments[0],
  11935. height: arguments[1],
  11936. tileSize: arguments[2],
  11937. tileOverlap: arguments[3],
  11938. tilesUrl: arguments[4]
  11939. };
  11940. }
  11941. // TMS has integer multiples of 256 for width/height and adds buffer
  11942. // if necessary -> account for this!
  11943. var bufferedWidth = Math.ceil(options.width / 256) * 256,
  11944. bufferedHeight = Math.ceil(options.height / 256) * 256,
  11945. max;
  11946. // Compute number of zoomlevels in this tileset
  11947. if (bufferedWidth > bufferedHeight) {
  11948. max = bufferedWidth / 256;
  11949. } else {
  11950. max = bufferedHeight / 256;
  11951. }
  11952. options.maxLevel = Math.ceil(Math.log(max) / Math.log(2)) - 1;
  11953. options.tileSize = 256;
  11954. options.width = bufferedWidth;
  11955. options.height = bufferedHeight;
  11956. $.TileSource.apply( this, [ options ] );
  11957. };
  11958. $.extend( $.TmsTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.TmsTileSource.prototype */{
  11959. /**
  11960. * Determine if the data and/or url imply the image service is supported by
  11961. * this tile source.
  11962. * @function
  11963. * @param {Object|Array} data
  11964. * @param {String} optional - url
  11965. */
  11966. supports: function( data, url ){
  11967. return ( data.type && "tiledmapservice" == data.type );
  11968. },
  11969. /**
  11970. *
  11971. * @function
  11972. * @param {Object} data - the raw configuration
  11973. * @param {String} url - the url the data was retrieved from if any.
  11974. * @return {Object} options - A dictionary of keyword arguments sufficient
  11975. * to configure this tile sources constructor.
  11976. */
  11977. configure: function( data, url ){
  11978. return data;
  11979. },
  11980. /**
  11981. * @function
  11982. * @param {Number} level
  11983. * @param {Number} x
  11984. * @param {Number} y
  11985. */
  11986. getTileUrl: function( level, x, y ) {
  11987. // Convert from Deep Zoom definition to TMS zoom definition
  11988. var yTiles = this.getNumTiles( level ).y - 1;
  11989. return this.tilesUrl + level + "/" + x + "/" + (yTiles - y) + ".png";
  11990. }
  11991. });
  11992. }( OpenSeadragon ));
  11993. (function($) {
  11994. /**
  11995. * @class ZoomifyTileSource
  11996. * @classdesc A tilesource implementation for the zoomify format.
  11997. *
  11998. * A description of the format can be found here:
  11999. * https://ecommons.cornell.edu/bitstream/handle/1813/5410/Introducing_Zoomify_Image.pdf
  12000. *
  12001. * There are two ways of creating a zoomify tilesource for openseadragon
  12002. *
  12003. * 1) Supplying all necessary information in the tilesource object. A minimal example object for this method looks like this:
  12004. *
  12005. * {
  12006. * type: "zoomifytileservice",
  12007. * width: 1000,
  12008. * height: 1000,
  12009. * tilesUrl: "/test/data/zoomify/"
  12010. * }
  12011. *
  12012. * The tileSize is currently hardcoded to 256 (the usual Zoomify default). The tileUrl must the path to the image _directory_.
  12013. *
  12014. * 2) Loading image metadata from xml file: (CURRENTLY NOT SUPPORTED)
  12015. *
  12016. * When creating zoomify formatted images one "xml" like file with name ImageProperties.xml
  12017. * will be created as well. Here is an example of such a file:
  12018. *
  12019. * <IMAGE_PROPERTIES WIDTH="1000" HEIGHT="1000" NUMTILES="21" NUMIMAGES="1" VERSION="1.8" TILESIZE="256" />
  12020. *
  12021. * To use this xml file as metadata source you must supply the path to the ImageProperties.xml file and leave out all other parameters:
  12022. * As stated above, this method of loading a zoomify tilesource is currently not supported
  12023. *
  12024. * {
  12025. * type: "zoomifytileservice",
  12026. * tilesUrl: "/test/data/zoomify/ImageProperties.xml"
  12027. * }
  12028. *
  12029. * @memberof OpenSeadragon
  12030. * @extends OpenSeadragon.TileSource
  12031. * @param {Number} width - the pixel width of the image.
  12032. * @param {Number} height
  12033. * @param {Number} tileSize
  12034. * @param {String} tilesUrl
  12035. */
  12036. $.ZoomifyTileSource = function(options) {
  12037. options.tileSize = 256;
  12038. var currentImageSize = {
  12039. x: options.width,
  12040. y: options.height
  12041. };
  12042. options.imageSizes = [{
  12043. x: options.width,
  12044. y: options.height
  12045. }];
  12046. options.gridSize = [this._getGridSize(options.width, options.height, options.tileSize)];
  12047. while (parseInt(currentImageSize.x, 10) > options.tileSize || parseInt(currentImageSize.y, 10) > options.tileSize) {
  12048. currentImageSize.x = Math.floor(currentImageSize.x / 2);
  12049. currentImageSize.y = Math.floor(currentImageSize.y / 2);
  12050. options.imageSizes.push({
  12051. x: currentImageSize.x,
  12052. y: currentImageSize.y
  12053. });
  12054. options.gridSize.push(this._getGridSize(currentImageSize.x, currentImageSize.y, options.tileSize));
  12055. }
  12056. options.imageSizes.reverse();
  12057. options.gridSize.reverse();
  12058. options.minLevel = 0;
  12059. options.maxLevel = options.gridSize.length - 1;
  12060. OpenSeadragon.TileSource.apply(this, [options]);
  12061. };
  12062. $.extend($.ZoomifyTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.ZoomifyTileSource.prototype */ {
  12063. //private
  12064. _getGridSize: function(width, height, tileSize) {
  12065. return {
  12066. x: Math.ceil(width / tileSize),
  12067. y: Math.ceil(height / tileSize)
  12068. };
  12069. },
  12070. //private
  12071. _calculateAbsoluteTileNumber: function(level, x, y) {
  12072. var num = 0;
  12073. var size = {};
  12074. //Sum up all tiles below the level we want the number of tiles
  12075. for (var z = 0; z < level; z++) {
  12076. size = this.gridSize[z];
  12077. num += size.x * size.y;
  12078. }
  12079. //Add the tiles of the level
  12080. size = this.gridSize[level];
  12081. num += size.x * y + x;
  12082. return num;
  12083. },
  12084. /**
  12085. * Determine if the data and/or url imply the image service is supported by
  12086. * this tile source.
  12087. * @function
  12088. * @param {Object|Array} data
  12089. * @param {String} optional - url
  12090. */
  12091. supports: function(data, url) {
  12092. return (data.type && "zoomifytileservice" == data.type);
  12093. },
  12094. /**
  12095. *
  12096. * @function
  12097. * @param {Object} data - the raw configuration
  12098. * @param {String} url - the url the data was retrieved from if any.
  12099. * @return {Object} options - A dictionary of keyword arguments sufficient
  12100. * to configure this tile sources constructor.
  12101. */
  12102. configure: function(data, url) {
  12103. return data;
  12104. },
  12105. /**
  12106. * @function
  12107. * @param {Number} level
  12108. * @param {Number} x
  12109. * @param {Number} y
  12110. */
  12111. getTileUrl: function(level, x, y) {
  12112. //console.log(level);
  12113. var result = 0;
  12114. var num = this._calculateAbsoluteTileNumber(level, x, y);
  12115. result = Math.floor(num / 256);
  12116. return this.tilesUrl + 'TileGroup' + result + '/' + level + '-' + x + '-' + y + '.jpg';
  12117. }
  12118. });
  12119. }(OpenSeadragon));
  12120. /*
  12121. * OpenSeadragon - LegacyTileSource
  12122. *
  12123. * Copyright (C) 2009 CodePlex Foundation
  12124. * Copyright (C) 2010-2013 OpenSeadragon contributors
  12125. *
  12126. * Redistribution and use in source and binary forms, with or without
  12127. * modification, are permitted provided that the following conditions are
  12128. * met:
  12129. *
  12130. * - Redistributions of source code must retain the above copyright notice,
  12131. * this list of conditions and the following disclaimer.
  12132. *
  12133. * - Redistributions in binary form must reproduce the above copyright
  12134. * notice, this list of conditions and the following disclaimer in the
  12135. * documentation and/or other materials provided with the distribution.
  12136. *
  12137. * - Neither the name of CodePlex Foundation nor the names of its
  12138. * contributors may be used to endorse or promote products derived from
  12139. * this software without specific prior written permission.
  12140. *
  12141. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  12142. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  12143. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  12144. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  12145. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  12146. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  12147. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  12148. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  12149. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  12150. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  12151. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  12152. */
  12153. (function( $ ){
  12154. /**
  12155. * @class LegacyTileSource
  12156. * @classdesc The LegacyTileSource allows simple, traditional image pyramids to be loaded
  12157. * into an OpenSeadragon Viewer. Basically, this translates to the historically
  12158. * common practice of starting with a 'master' image, maybe a tiff for example,
  12159. * and generating a set of 'service' images like one or more thumbnails, a medium
  12160. * resolution image and a high resolution image in standard web formats like
  12161. * png or jpg.
  12162. *
  12163. * @memberof OpenSeadragon
  12164. * @extends OpenSeadragon.TileSource
  12165. * @param {Array} levels An array of file descriptions, each is an object with
  12166. * a 'url', a 'width', and a 'height'. Overriding classes can expect more
  12167. * properties but these properties are sufficient for this implementation.
  12168. * Additionally, the levels are required to be listed in order from
  12169. * smallest to largest.
  12170. * @property {Number} aspectRatio
  12171. * @property {Number} dimensions
  12172. * @property {Number} tileSize
  12173. * @property {Number} tileOverlap
  12174. * @property {Number} minLevel
  12175. * @property {Number} maxLevel
  12176. * @property {Array} levels
  12177. */
  12178. $.LegacyTileSource = function( levels ) {
  12179. var options,
  12180. width,
  12181. height;
  12182. if( $.isArray( levels ) ){
  12183. options = {
  12184. type: 'legacy-image-pyramid',
  12185. levels: levels
  12186. };
  12187. }
  12188. //clean up the levels to make sure we support all formats
  12189. options.levels = filterFiles( options.levels );
  12190. if ( options.levels.length > 0 ) {
  12191. width = options.levels[ options.levels.length - 1 ].width;
  12192. height = options.levels[ options.levels.length - 1 ].height;
  12193. }
  12194. else {
  12195. width = 0;
  12196. height = 0;
  12197. $.console.error( "No supported image formats found" );
  12198. }
  12199. $.extend( true, options, {
  12200. width: width,
  12201. height: height,
  12202. tileSize: Math.max( height, width ),
  12203. tileOverlap: 0,
  12204. minLevel: 0,
  12205. maxLevel: options.levels.length > 0 ? options.levels.length - 1 : 0
  12206. } );
  12207. $.TileSource.apply( this, [ options ] );
  12208. this.levels = options.levels;
  12209. };
  12210. $.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.LegacyTileSource.prototype */{
  12211. /**
  12212. * Determine if the data and/or url imply the image service is supported by
  12213. * this tile source.
  12214. * @function
  12215. * @param {Object|Array} data
  12216. * @param {String} optional - url
  12217. */
  12218. supports: function( data, url ){
  12219. return (
  12220. data.type &&
  12221. "legacy-image-pyramid" == data.type
  12222. ) || (
  12223. data.documentElement &&
  12224. "legacy-image-pyramid" == data.documentElement.getAttribute('type')
  12225. );
  12226. },
  12227. /**
  12228. *
  12229. * @function
  12230. * @param {Object|XMLDocument} configuration - the raw configuration
  12231. * @param {String} dataUrl - the url the data was retrieved from if any.
  12232. * @return {Object} options - A dictionary of keyword arguments sufficient
  12233. * to configure this tile sources constructor.
  12234. */
  12235. configure: function( configuration, dataUrl ){
  12236. var options;
  12237. if( !$.isPlainObject(configuration) ){
  12238. options = configureFromXML( this, configuration );
  12239. }else{
  12240. options = configureFromObject( this, configuration );
  12241. }
  12242. return options;
  12243. },
  12244. /**
  12245. * @function
  12246. * @param {Number} level
  12247. */
  12248. getLevelScale: function ( level ) {
  12249. var levelScale = NaN;
  12250. if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) {
  12251. levelScale =
  12252. this.levels[ level ].width /
  12253. this.levels[ this.maxLevel ].width;
  12254. }
  12255. return levelScale;
  12256. },
  12257. /**
  12258. * @function
  12259. * @param {Number} level
  12260. */
  12261. getNumTiles: function( level ) {
  12262. var scale = this.getLevelScale( level );
  12263. if ( scale ){
  12264. return new $.Point( 1, 1 );
  12265. } else {
  12266. return new $.Point( 0, 0 );
  12267. }
  12268. },
  12269. /**
  12270. * This method is not implemented by this class other than to throw an Error
  12271. * announcing you have to implement it. Because of the variety of tile
  12272. * server technologies, and various specifications for building image
  12273. * pyramids, this method is here to allow easy integration.
  12274. * @function
  12275. * @param {Number} level
  12276. * @param {Number} x
  12277. * @param {Number} y
  12278. * @throws {Error}
  12279. */
  12280. getTileUrl: function ( level, x, y ) {
  12281. var url = null;
  12282. if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) {
  12283. url = this.levels[ level ].url;
  12284. }
  12285. return url;
  12286. }
  12287. } );
  12288. /**
  12289. * This method removes any files from the Array which don't conform to our
  12290. * basic requirements for a 'level' in the LegacyTileSource.
  12291. * @private
  12292. * @inner
  12293. * @function
  12294. */
  12295. function filterFiles( files ){
  12296. var filtered = [],
  12297. file,
  12298. i;
  12299. for( i = 0; i < files.length; i++ ){
  12300. file = files[ i ];
  12301. if( file.height &&
  12302. file.width &&
  12303. file.url ){
  12304. //This is sufficient to serve as a level
  12305. filtered.push({
  12306. url: file.url,
  12307. width: Number( file.width ),
  12308. height: Number( file.height )
  12309. });
  12310. }
  12311. else {
  12312. $.console.error( 'Unsupported image format: %s', file.url ? file.url : '<no URL>' );
  12313. }
  12314. }
  12315. return filtered.sort(function(a, b) {
  12316. return a.height - b.height;
  12317. });
  12318. }
  12319. /**
  12320. * @private
  12321. * @inner
  12322. * @function
  12323. */
  12324. function configureFromXML( tileSource, xmlDoc ){
  12325. if ( !xmlDoc || !xmlDoc.documentElement ) {
  12326. throw new Error( $.getString( "Errors.Xml" ) );
  12327. }
  12328. var root = xmlDoc.documentElement,
  12329. rootName = root.tagName,
  12330. conf = null,
  12331. levels = [],
  12332. level,
  12333. i;
  12334. if ( rootName == "image" ) {
  12335. try {
  12336. conf = {
  12337. type: root.getAttribute( "type" ),
  12338. levels: []
  12339. };
  12340. levels = root.getElementsByTagName( "level" );
  12341. for ( i = 0; i < levels.length; i++ ) {
  12342. level = levels[ i ];
  12343. conf.levels.push({
  12344. url: level.getAttribute( "url" ),
  12345. width: parseInt( level.getAttribute( "width" ), 10 ),
  12346. height: parseInt( level.getAttribute( "height" ), 10 )
  12347. });
  12348. }
  12349. return configureFromObject( tileSource, conf );
  12350. } catch ( e ) {
  12351. throw (e instanceof Error) ?
  12352. e :
  12353. new Error( 'Unknown error parsing Legacy Image Pyramid XML.' );
  12354. }
  12355. } else if ( rootName == "collection" ) {
  12356. throw new Error( 'Legacy Image Pyramid Collections not yet supported.' );
  12357. } else if ( rootName == "error" ) {
  12358. throw new Error( 'Error: ' + xmlDoc );
  12359. }
  12360. throw new Error( 'Unknown element ' + rootName );
  12361. }
  12362. /**
  12363. * @private
  12364. * @inner
  12365. * @function
  12366. */
  12367. function configureFromObject( tileSource, configuration ){
  12368. return configuration.levels;
  12369. }
  12370. }( OpenSeadragon ));
  12371. /*
  12372. * OpenSeadragon - ImageTileSource
  12373. *
  12374. * Copyright (C) 2009 CodePlex Foundation
  12375. * Copyright (C) 2010-2013 OpenSeadragon contributors
  12376. *
  12377. * Redistribution and use in source and binary forms, with or without
  12378. * modification, are permitted provided that the following conditions are
  12379. * met:
  12380. *
  12381. * - Redistributions of source code must retain the above copyright notice,
  12382. * this list of conditions and the following disclaimer.
  12383. *
  12384. * - Redistributions in binary form must reproduce the above copyright
  12385. * notice, this list of conditions and the following disclaimer in the
  12386. * documentation and/or other materials provided with the distribution.
  12387. *
  12388. * - Neither the name of CodePlex Foundation nor the names of its
  12389. * contributors may be used to endorse or promote products derived from
  12390. * this software without specific prior written permission.
  12391. *
  12392. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  12393. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  12394. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  12395. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  12396. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  12397. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  12398. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  12399. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  12400. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  12401. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  12402. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  12403. */
  12404. (function ($) {
  12405. /**
  12406. * @class ImageTileSource
  12407. * @classdesc The ImageTileSource allows a simple image to be loaded
  12408. * into an OpenSeadragon Viewer.
  12409. * There are 2 ways to open an ImageTileSource:
  12410. * 1. viewer.open({type: 'image', url: fooUrl});
  12411. * 2. viewer.open(new OpenSeadragon.ImageTileSource({url: fooUrl}));
  12412. *
  12413. * With the first syntax, the crossOriginPolicy, ajaxWithCredentials and
  12414. * useCanvas options are inherited from the viewer if they are not
  12415. * specified directly in the options object.
  12416. *
  12417. * @memberof OpenSeadragon
  12418. * @extends OpenSeadragon.TileSource
  12419. * @param {Object} options Options object.
  12420. * @param {String} options.url URL of the image
  12421. * @param {Boolean} [options.buildPyramid=true] If set to true (default), a
  12422. * pyramid will be built internally to provide a better downsampling.
  12423. * @param {String|Boolean} [options.crossOriginPolicy=false] Valid values are
  12424. * 'Anonymous', 'use-credentials', and false. If false, image requests will
  12425. * not use CORS preventing internal pyramid building for images from other
  12426. * domains.
  12427. * @param {String|Boolean} [options.ajaxWithCredentials=false] Whether to set
  12428. * the withCredentials XHR flag for AJAX requests (when loading tile sources).
  12429. * @param {Boolean} [options.useCanvas=true] Set to false to prevent any use
  12430. * of the canvas API.
  12431. */
  12432. $.ImageTileSource = function (options) {
  12433. options = $.extend({
  12434. buildPyramid: true,
  12435. crossOriginPolicy: false,
  12436. ajaxWithCredentials: false,
  12437. useCanvas: true
  12438. }, options);
  12439. $.TileSource.apply(this, [options]);
  12440. };
  12441. $.extend($.ImageTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.ImageTileSource.prototype */{
  12442. /**
  12443. * Determine if the data and/or url imply the image service is supported by
  12444. * this tile source.
  12445. * @function
  12446. * @param {Object|Array} data
  12447. * @param {String} optional - url
  12448. */
  12449. supports: function (data, url) {
  12450. return data.type && data.type === "image";
  12451. },
  12452. /**
  12453. *
  12454. * @function
  12455. * @param {Object} options - the options
  12456. * @param {String} dataUrl - the url the image was retrieved from, if any.
  12457. * @return {Object} options - A dictionary of keyword arguments sufficient
  12458. * to configure this tile sources constructor.
  12459. */
  12460. configure: function (options, dataUrl) {
  12461. return options;
  12462. },
  12463. /**
  12464. * Responsible for retrieving, and caching the
  12465. * image metadata pertinent to this TileSources implementation.
  12466. * @function
  12467. * @param {String} url
  12468. * @throws {Error}
  12469. */
  12470. getImageInfo: function (url) {
  12471. var image = this._image = new Image();
  12472. var _this = this;
  12473. if (this.crossOriginPolicy) {
  12474. image.crossOrigin = this.crossOriginPolicy;
  12475. }
  12476. if (this.ajaxWithCredentials) {
  12477. image.useCredentials = this.ajaxWithCredentials;
  12478. }
  12479. $.addEvent(image, 'load', function () {
  12480. /* IE8 fix since it has no naturalWidth and naturalHeight */
  12481. _this.width = Object.prototype.hasOwnProperty.call(image, 'naturalWidth') ? image.naturalWidth : image.width;
  12482. _this.height = Object.prototype.hasOwnProperty.call(image, 'naturalHeight') ? image.naturalHeight : image.height;
  12483. _this.aspectRatio = _this.width / _this.height;
  12484. _this.dimensions = new $.Point(_this.width, _this.height);
  12485. _this._tileWidth = _this.width;
  12486. _this._tileHeight = _this.height;
  12487. _this.tileOverlap = 0;
  12488. _this.minLevel = 0;
  12489. _this.levels = _this._buildLevels();
  12490. _this.maxLevel = _this.levels.length - 1;
  12491. _this.ready = true;
  12492. // Note: this event is documented elsewhere, in TileSource
  12493. _this.raiseEvent('ready', {tileSource: _this});
  12494. });
  12495. $.addEvent(image, 'error', function () {
  12496. // Note: this event is documented elsewhere, in TileSource
  12497. _this.raiseEvent('open-failed', {
  12498. message: "Error loading image at " + url,
  12499. source: url
  12500. });
  12501. });
  12502. image.src = url;
  12503. },
  12504. /**
  12505. * @function
  12506. * @param {Number} level
  12507. */
  12508. getLevelScale: function (level) {
  12509. var levelScale = NaN;
  12510. if (level >= this.minLevel && level <= this.maxLevel) {
  12511. levelScale =
  12512. this.levels[level].width /
  12513. this.levels[this.maxLevel].width;
  12514. }
  12515. return levelScale;
  12516. },
  12517. /**
  12518. * @function
  12519. * @param {Number} level
  12520. */
  12521. getNumTiles: function (level) {
  12522. var scale = this.getLevelScale(level);
  12523. if (scale) {
  12524. return new $.Point(1, 1);
  12525. } else {
  12526. return new $.Point(0, 0);
  12527. }
  12528. },
  12529. /**
  12530. * Retrieves a tile url
  12531. * @function
  12532. * @param {Number} level Level of the tile
  12533. * @param {Number} x x coordinate of the tile
  12534. * @param {Number} y y coordinate of the tile
  12535. */
  12536. getTileUrl: function (level, x, y) {
  12537. var url = null;
  12538. if (level >= this.minLevel && level <= this.maxLevel) {
  12539. url = this.levels[level].url;
  12540. }
  12541. return url;
  12542. },
  12543. /**
  12544. * Retrieves a tile context 2D
  12545. * @function
  12546. * @param {Number} level Level of the tile
  12547. * @param {Number} x x coordinate of the tile
  12548. * @param {Number} y y coordinate of the tile
  12549. */
  12550. getContext2D: function (level, x, y) {
  12551. var context = null;
  12552. if (level >= this.minLevel && level <= this.maxLevel) {
  12553. context = this.levels[level].context2D;
  12554. }
  12555. return context;
  12556. },
  12557. // private
  12558. //
  12559. // Builds the different levels of the pyramid if possible
  12560. // (i.e. if canvas API enabled and no canvas tainting issue).
  12561. _buildLevels: function () {
  12562. var levels = [{
  12563. url: this._image.src,
  12564. /* IE8 fix since it has no naturalWidth and naturalHeight */
  12565. width: Object.prototype.hasOwnProperty.call(this._image, 'naturalWidth') ? this._image.naturalWidth : this._image.width,
  12566. height: Object.prototype.hasOwnProperty.call(this._image, 'naturalHeight') ? this._image.naturalHeight : this._image.height
  12567. }];
  12568. if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) {
  12569. // We don't need the image anymore. Allows it to be GC.
  12570. delete this._image;
  12571. return levels;
  12572. }
  12573. /* IE8 fix since it has no naturalWidth and naturalHeight */
  12574. var currentWidth = Object.prototype.hasOwnProperty.call(this._image, 'naturalWidth') ? this._image.naturalWidth : this._image.width;
  12575. var currentHeight = Object.prototype.hasOwnProperty.call(this._image, 'naturalHeight') ? this._image.naturalHeight : this._image.height;
  12576. var bigCanvas = document.createElement("canvas");
  12577. var bigContext = bigCanvas.getContext("2d");
  12578. bigCanvas.width = currentWidth;
  12579. bigCanvas.height = currentHeight;
  12580. bigContext.drawImage(this._image, 0, 0, currentWidth, currentHeight);
  12581. // We cache the context of the highest level because the browser
  12582. // is a lot faster at downsampling something it already has
  12583. // downsampled before.
  12584. levels[0].context2D = bigContext;
  12585. // We don't need the image anymore. Allows it to be GC.
  12586. delete this._image;
  12587. if ($.isCanvasTainted(bigCanvas)) {
  12588. // If the canvas is tainted, we can't compute the pyramid.
  12589. return levels;
  12590. }
  12591. // We build smaller levels until either width or height becomes
  12592. // 1 pixel wide.
  12593. while (currentWidth >= 2 && currentHeight >= 2) {
  12594. currentWidth = Math.floor(currentWidth / 2);
  12595. currentHeight = Math.floor(currentHeight / 2);
  12596. var smallCanvas = document.createElement("canvas");
  12597. var smallContext = smallCanvas.getContext("2d");
  12598. smallCanvas.width = currentWidth;
  12599. smallCanvas.height = currentHeight;
  12600. smallContext.drawImage(bigCanvas, 0, 0, currentWidth, currentHeight);
  12601. levels.splice(0, 0, {
  12602. context2D: smallContext,
  12603. width: currentWidth,
  12604. height: currentHeight
  12605. });
  12606. bigCanvas = smallCanvas;
  12607. bigContext = smallContext;
  12608. }
  12609. return levels;
  12610. }
  12611. });
  12612. }(OpenSeadragon));
  12613. /*
  12614. * OpenSeadragon - TileSourceCollection
  12615. *
  12616. * Copyright (C) 2009 CodePlex Foundation
  12617. * Copyright (C) 2010-2013 OpenSeadragon contributors
  12618. *
  12619. * Redistribution and use in source and binary forms, with or without
  12620. * modification, are permitted provided that the following conditions are
  12621. * met:
  12622. *
  12623. * - Redistributions of source code must retain the above copyright notice,
  12624. * this list of conditions and the following disclaimer.
  12625. *
  12626. * - Redistributions in binary form must reproduce the above copyright
  12627. * notice, this list of conditions and the following disclaimer in the
  12628. * documentation and/or other materials provided with the distribution.
  12629. *
  12630. * - Neither the name of CodePlex Foundation nor the names of its
  12631. * contributors may be used to endorse or promote products derived from
  12632. * this software without specific prior written permission.
  12633. *
  12634. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  12635. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  12636. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  12637. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  12638. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  12639. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  12640. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  12641. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  12642. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  12643. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  12644. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  12645. */
  12646. (function($) {
  12647. // deprecated
  12648. $.TileSourceCollection = function(tileSize, tileSources, rows, layout) {
  12649. $.console.error('TileSourceCollection is deprecated; use World instead');
  12650. };
  12651. }(OpenSeadragon));
  12652. /*
  12653. * OpenSeadragon - Button
  12654. *
  12655. * Copyright (C) 2009 CodePlex Foundation
  12656. * Copyright (C) 2010-2013 OpenSeadragon contributors
  12657. *
  12658. * Redistribution and use in source and binary forms, with or without
  12659. * modification, are permitted provided that the following conditions are
  12660. * met:
  12661. *
  12662. * - Redistributions of source code must retain the above copyright notice,
  12663. * this list of conditions and the following disclaimer.
  12664. *
  12665. * - Redistributions in binary form must reproduce the above copyright
  12666. * notice, this list of conditions and the following disclaimer in the
  12667. * documentation and/or other materials provided with the distribution.
  12668. *
  12669. * - Neither the name of CodePlex Foundation nor the names of its
  12670. * contributors may be used to endorse or promote products derived from
  12671. * this software without specific prior written permission.
  12672. *
  12673. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  12674. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  12675. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  12676. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  12677. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  12678. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  12679. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  12680. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  12681. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  12682. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  12683. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  12684. */
  12685. (function( $ ){
  12686. /**
  12687. * An enumeration of button states
  12688. * @member ButtonState
  12689. * @memberof OpenSeadragon
  12690. * @static
  12691. * @type {Object}
  12692. * @property {Number} REST
  12693. * @property {Number} GROUP
  12694. * @property {Number} HOVER
  12695. * @property {Number} DOWN
  12696. */
  12697. $.ButtonState = {
  12698. REST: 0,
  12699. GROUP: 1,
  12700. HOVER: 2,
  12701. DOWN: 3
  12702. };
  12703. /**
  12704. * @class Button
  12705. * @classdesc Manages events, hover states for individual buttons, tool-tips, as well
  12706. * as fading the buttons out when the user has not interacted with them
  12707. * for a specified period.
  12708. *
  12709. * @memberof OpenSeadragon
  12710. * @extends OpenSeadragon.EventSource
  12711. * @param {Object} options
  12712. * @param {Element} [options.element=null] Element to use as the button. If not specified, an HTML &lt;div&gt; element is created.
  12713. * @param {String} [options.tooltip=null] Provides context help for the button when the
  12714. * user hovers over it.
  12715. * @param {String} [options.srcRest=null] URL of image to use in 'rest' state.
  12716. * @param {String} [options.srcGroup=null] URL of image to use in 'up' state.
  12717. * @param {String} [options.srcHover=null] URL of image to use in 'hover' state.
  12718. * @param {String} [options.srcDown=null] URL of image to use in 'down' state.
  12719. * @param {Number} [options.fadeDelay=0] How long to wait before fading.
  12720. * @param {Number} [options.fadeLength=2000] How long should it take to fade the button.
  12721. * @param {OpenSeadragon.EventHandler} [options.onPress=null] Event handler callback for {@link OpenSeadragon.Button.event:press}.
  12722. * @param {OpenSeadragon.EventHandler} [options.onRelease=null] Event handler callback for {@link OpenSeadragon.Button.event:release}.
  12723. * @param {OpenSeadragon.EventHandler} [options.onClick=null] Event handler callback for {@link OpenSeadragon.Button.event:click}.
  12724. * @param {OpenSeadragon.EventHandler} [options.onEnter=null] Event handler callback for {@link OpenSeadragon.Button.event:enter}.
  12725. * @param {OpenSeadragon.EventHandler} [options.onExit=null] Event handler callback for {@link OpenSeadragon.Button.event:exit}.
  12726. * @param {OpenSeadragon.EventHandler} [options.onFocus=null] Event handler callback for {@link OpenSeadragon.Button.event:focus}.
  12727. * @param {OpenSeadragon.EventHandler} [options.onBlur=null] Event handler callback for {@link OpenSeadragon.Button.event:blur}.
  12728. */
  12729. $.Button = function( options ) {
  12730. var _this = this;
  12731. $.EventSource.call( this );
  12732. $.extend( true, this, {
  12733. tooltip: null,
  12734. srcRest: null,
  12735. srcGroup: null,
  12736. srcHover: null,
  12737. srcDown: null,
  12738. clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold,
  12739. clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold,
  12740. /**
  12741. * How long to wait before fading.
  12742. * @member {Number} fadeDelay
  12743. * @memberof OpenSeadragon.Button#
  12744. */
  12745. fadeDelay: 0,
  12746. /**
  12747. * How long should it take to fade the button.
  12748. * @member {Number} fadeLength
  12749. * @memberof OpenSeadragon.Button#
  12750. */
  12751. fadeLength: 2000,
  12752. onPress: null,
  12753. onRelease: null,
  12754. onClick: null,
  12755. onEnter: null,
  12756. onExit: null,
  12757. onFocus: null,
  12758. onBlur: null
  12759. }, options );
  12760. /**
  12761. * The button element.
  12762. * @member {Element} element
  12763. * @memberof OpenSeadragon.Button#
  12764. */
  12765. this.element = options.element || $.makeNeutralElement("div");
  12766. //if the user has specified the element to bind the control to explicitly
  12767. //then do not add the default control images
  12768. if ( !options.element ) {
  12769. this.imgRest = $.makeTransparentImage( this.srcRest );
  12770. this.imgGroup = $.makeTransparentImage( this.srcGroup );
  12771. this.imgHover = $.makeTransparentImage( this.srcHover );
  12772. this.imgDown = $.makeTransparentImage( this.srcDown );
  12773. this.imgRest.alt =
  12774. this.imgGroup.alt =
  12775. this.imgHover.alt =
  12776. this.imgDown.alt =
  12777. this.tooltip;
  12778. this.element.style.position = "relative";
  12779. $.setElementTouchActionNone( this.element );
  12780. this.imgGroup.style.position =
  12781. this.imgHover.style.position =
  12782. this.imgDown.style.position =
  12783. "absolute";
  12784. this.imgGroup.style.top =
  12785. this.imgHover.style.top =
  12786. this.imgDown.style.top =
  12787. "0px";
  12788. this.imgGroup.style.left =
  12789. this.imgHover.style.left =
  12790. this.imgDown.style.left =
  12791. "0px";
  12792. this.imgHover.style.visibility =
  12793. this.imgDown.style.visibility =
  12794. "hidden";
  12795. if ($.Browser.vendor == $.BROWSERS.FIREFOX && $.Browser.version < 3) {
  12796. this.imgGroup.style.top =
  12797. this.imgHover.style.top =
  12798. this.imgDown.style.top =
  12799. "";
  12800. }
  12801. this.element.appendChild( this.imgRest );
  12802. this.element.appendChild( this.imgGroup );
  12803. this.element.appendChild( this.imgHover );
  12804. this.element.appendChild( this.imgDown );
  12805. }
  12806. this.addHandler("press", this.onPress);
  12807. this.addHandler("release", this.onRelease);
  12808. this.addHandler("click", this.onClick);
  12809. this.addHandler("enter", this.onEnter);
  12810. this.addHandler("exit", this.onExit);
  12811. this.addHandler("focus", this.onFocus);
  12812. this.addHandler("blur", this.onBlur);
  12813. /**
  12814. * The button's current state.
  12815. * @member {OpenSeadragon.ButtonState} currentState
  12816. * @memberof OpenSeadragon.Button#
  12817. */
  12818. this.currentState = $.ButtonState.GROUP;
  12819. // When the button last began to fade.
  12820. this.fadeBeginTime = null;
  12821. // Whether this button should fade after user stops interacting with the viewport.
  12822. this.shouldFade = false;
  12823. this.element.style.display = "inline-block";
  12824. this.element.style.position = "relative";
  12825. this.element.title = this.tooltip;
  12826. /**
  12827. * Tracks mouse/touch/key events on the button.
  12828. * @member {OpenSeadragon.MouseTracker} tracker
  12829. * @memberof OpenSeadragon.Button#
  12830. */
  12831. this.tracker = new $.MouseTracker({
  12832. element: this.element,
  12833. clickTimeThreshold: this.clickTimeThreshold,
  12834. clickDistThreshold: this.clickDistThreshold,
  12835. enterHandler: function( event ) {
  12836. if ( event.insideElementPressed ) {
  12837. inTo( _this, $.ButtonState.DOWN );
  12838. /**
  12839. * Raised when the cursor enters the Button element.
  12840. *
  12841. * @event enter
  12842. * @memberof OpenSeadragon.Button
  12843. * @type {object}
  12844. * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
  12845. * @property {Object} originalEvent - The original DOM event.
  12846. * @property {?Object} userData - Arbitrary subscriber-defined object.
  12847. */
  12848. _this.raiseEvent( "enter", { originalEvent: event.originalEvent } );
  12849. } else if ( !event.buttonDownAny ) {
  12850. inTo( _this, $.ButtonState.HOVER );
  12851. }
  12852. },
  12853. focusHandler: function ( event ) {
  12854. this.enterHandler( event );
  12855. /**
  12856. * Raised when the Button element receives focus.
  12857. *
  12858. * @event focus
  12859. * @memberof OpenSeadragon.Button
  12860. * @type {object}
  12861. * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
  12862. * @property {Object} originalEvent - The original DOM event.
  12863. * @property {?Object} userData - Arbitrary subscriber-defined object.
  12864. */
  12865. _this.raiseEvent( "focus", { originalEvent: event.originalEvent } );
  12866. },
  12867. exitHandler: function( event ) {
  12868. outTo( _this, $.ButtonState.GROUP );
  12869. if ( event.insideElementPressed ) {
  12870. /**
  12871. * Raised when the cursor leaves the Button element.
  12872. *
  12873. * @event exit
  12874. * @memberof OpenSeadragon.Button
  12875. * @type {object}
  12876. * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
  12877. * @property {Object} originalEvent - The original DOM event.
  12878. * @property {?Object} userData - Arbitrary subscriber-defined object.
  12879. */
  12880. _this.raiseEvent( "exit", { originalEvent: event.originalEvent } );
  12881. }
  12882. },
  12883. blurHandler: function ( event ) {
  12884. this.exitHandler( event );
  12885. /**
  12886. * Raised when the Button element loses focus.
  12887. *
  12888. * @event blur
  12889. * @memberof OpenSeadragon.Button
  12890. * @type {object}
  12891. * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
  12892. * @property {Object} originalEvent - The original DOM event.
  12893. * @property {?Object} userData - Arbitrary subscriber-defined object.
  12894. */
  12895. _this.raiseEvent( "blur", { originalEvent: event.originalEvent } );
  12896. },
  12897. pressHandler: function ( event ) {
  12898. inTo( _this, $.ButtonState.DOWN );
  12899. /**
  12900. * Raised when a mouse button is pressed or touch occurs in the Button element.
  12901. *
  12902. * @event press
  12903. * @memberof OpenSeadragon.Button
  12904. * @type {object}
  12905. * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
  12906. * @property {Object} originalEvent - The original DOM event.
  12907. * @property {?Object} userData - Arbitrary subscriber-defined object.
  12908. */
  12909. _this.raiseEvent( "press", { originalEvent: event.originalEvent } );
  12910. },
  12911. releaseHandler: function( event ) {
  12912. if ( event.insideElementPressed && event.insideElementReleased ) {
  12913. outTo( _this, $.ButtonState.HOVER );
  12914. /**
  12915. * Raised when the mouse button is released or touch ends in the Button element.
  12916. *
  12917. * @event release
  12918. * @memberof OpenSeadragon.Button
  12919. * @type {object}
  12920. * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
  12921. * @property {Object} originalEvent - The original DOM event.
  12922. * @property {?Object} userData - Arbitrary subscriber-defined object.
  12923. */
  12924. _this.raiseEvent( "release", { originalEvent: event.originalEvent } );
  12925. } else if ( event.insideElementPressed ) {
  12926. outTo( _this, $.ButtonState.GROUP );
  12927. } else {
  12928. inTo( _this, $.ButtonState.HOVER );
  12929. }
  12930. },
  12931. clickHandler: function( event ) {
  12932. if ( event.quick ) {
  12933. /**
  12934. * Raised when a mouse button is pressed and released or touch is initiated and ended in the Button element within the time and distance threshold.
  12935. *
  12936. * @event click
  12937. * @memberof OpenSeadragon.Button
  12938. * @type {object}
  12939. * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
  12940. * @property {Object} originalEvent - The original DOM event.
  12941. * @property {?Object} userData - Arbitrary subscriber-defined object.
  12942. */
  12943. _this.raiseEvent("click", { originalEvent: event.originalEvent });
  12944. }
  12945. },
  12946. keyHandler: function( event ){
  12947. //console.log( "%s : handling key %s!", _this.tooltip, event.keyCode);
  12948. if( 13 === event.keyCode ){
  12949. /***
  12950. * Raised when a mouse button is pressed and released or touch is initiated and ended in the Button element within the time and distance threshold.
  12951. *
  12952. * @event click
  12953. * @memberof OpenSeadragon.Button
  12954. * @type {object}
  12955. * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
  12956. * @property {Object} originalEvent - The original DOM event.
  12957. * @property {?Object} userData - Arbitrary subscriber-defined object.
  12958. */
  12959. _this.raiseEvent( "click", { originalEvent: event.originalEvent } );
  12960. /***
  12961. * Raised when the mouse button is released or touch ends in the Button element.
  12962. *
  12963. * @event release
  12964. * @memberof OpenSeadragon.Button
  12965. * @type {object}
  12966. * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
  12967. * @property {Object} originalEvent - The original DOM event.
  12968. * @property {?Object} userData - Arbitrary subscriber-defined object.
  12969. */
  12970. _this.raiseEvent( "release", { originalEvent: event.originalEvent } );
  12971. return false;
  12972. }
  12973. return true;
  12974. }
  12975. });
  12976. outTo( this, $.ButtonState.REST );
  12977. };
  12978. $.extend( $.Button.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.Button.prototype */{
  12979. /**
  12980. * TODO: Determine what this function is intended to do and if it's actually
  12981. * useful as an API point.
  12982. * @function
  12983. */
  12984. notifyGroupEnter: function() {
  12985. inTo( this, $.ButtonState.GROUP );
  12986. },
  12987. /**
  12988. * TODO: Determine what this function is intended to do and if it's actually
  12989. * useful as an API point.
  12990. * @function
  12991. */
  12992. notifyGroupExit: function() {
  12993. outTo( this, $.ButtonState.REST );
  12994. },
  12995. /**
  12996. * @function
  12997. */
  12998. disable: function(){
  12999. this.notifyGroupExit();
  13000. this.element.disabled = true;
  13001. $.setElementOpacity( this.element, 0.2, true );
  13002. },
  13003. /**
  13004. * @function
  13005. */
  13006. enable: function(){
  13007. this.element.disabled = false;
  13008. $.setElementOpacity( this.element, 1.0, true );
  13009. this.notifyGroupEnter();
  13010. }
  13011. });
  13012. function scheduleFade( button ) {
  13013. $.requestAnimationFrame(function(){
  13014. updateFade( button );
  13015. });
  13016. }
  13017. function updateFade( button ) {
  13018. var currentTime,
  13019. deltaTime,
  13020. opacity;
  13021. if ( button.shouldFade ) {
  13022. currentTime = $.now();
  13023. deltaTime = currentTime - button.fadeBeginTime;
  13024. opacity = 1.0 - deltaTime / button.fadeLength;
  13025. opacity = Math.min( 1.0, opacity );
  13026. opacity = Math.max( 0.0, opacity );
  13027. if( button.imgGroup ){
  13028. $.setElementOpacity( button.imgGroup, opacity, true );
  13029. }
  13030. if ( opacity > 0 ) {
  13031. // fade again
  13032. scheduleFade( button );
  13033. }
  13034. }
  13035. }
  13036. function beginFading( button ) {
  13037. button.shouldFade = true;
  13038. button.fadeBeginTime = $.now() + button.fadeDelay;
  13039. window.setTimeout( function(){
  13040. scheduleFade( button );
  13041. }, button.fadeDelay );
  13042. }
  13043. function stopFading( button ) {
  13044. button.shouldFade = false;
  13045. if( button.imgGroup ){
  13046. $.setElementOpacity( button.imgGroup, 1.0, true );
  13047. }
  13048. }
  13049. function inTo( button, newState ) {
  13050. if( button.element.disabled ){
  13051. return;
  13052. }
  13053. if ( newState >= $.ButtonState.GROUP &&
  13054. button.currentState == $.ButtonState.REST ) {
  13055. stopFading( button );
  13056. button.currentState = $.ButtonState.GROUP;
  13057. }
  13058. if ( newState >= $.ButtonState.HOVER &&
  13059. button.currentState == $.ButtonState.GROUP ) {
  13060. if( button.imgHover ){
  13061. button.imgHover.style.visibility = "";
  13062. }
  13063. button.currentState = $.ButtonState.HOVER;
  13064. }
  13065. if ( newState >= $.ButtonState.DOWN &&
  13066. button.currentState == $.ButtonState.HOVER ) {
  13067. if( button.imgDown ){
  13068. button.imgDown.style.visibility = "";
  13069. }
  13070. button.currentState = $.ButtonState.DOWN;
  13071. }
  13072. }
  13073. function outTo( button, newState ) {
  13074. if( button.element.disabled ){
  13075. return;
  13076. }
  13077. if ( newState <= $.ButtonState.HOVER &&
  13078. button.currentState == $.ButtonState.DOWN ) {
  13079. if( button.imgDown ){
  13080. button.imgDown.style.visibility = "hidden";
  13081. }
  13082. button.currentState = $.ButtonState.HOVER;
  13083. }
  13084. if ( newState <= $.ButtonState.GROUP &&
  13085. button.currentState == $.ButtonState.HOVER ) {
  13086. if( button.imgHover ){
  13087. button.imgHover.style.visibility = "hidden";
  13088. }
  13089. button.currentState = $.ButtonState.GROUP;
  13090. }
  13091. if ( newState <= $.ButtonState.REST &&
  13092. button.currentState == $.ButtonState.GROUP ) {
  13093. beginFading( button );
  13094. button.currentState = $.ButtonState.REST;
  13095. }
  13096. }
  13097. }( OpenSeadragon ));
  13098. /*
  13099. * OpenSeadragon - ButtonGroup
  13100. *
  13101. * Copyright (C) 2009 CodePlex Foundation
  13102. * Copyright (C) 2010-2013 OpenSeadragon contributors
  13103. *
  13104. * Redistribution and use in source and binary forms, with or without
  13105. * modification, are permitted provided that the following conditions are
  13106. * met:
  13107. *
  13108. * - Redistributions of source code must retain the above copyright notice,
  13109. * this list of conditions and the following disclaimer.
  13110. *
  13111. * - Redistributions in binary form must reproduce the above copyright
  13112. * notice, this list of conditions and the following disclaimer in the
  13113. * documentation and/or other materials provided with the distribution.
  13114. *
  13115. * - Neither the name of CodePlex Foundation nor the names of its
  13116. * contributors may be used to endorse or promote products derived from
  13117. * this software without specific prior written permission.
  13118. *
  13119. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  13120. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  13121. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  13122. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  13123. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  13124. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  13125. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  13126. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  13127. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  13128. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  13129. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  13130. */
  13131. (function( $ ){
  13132. /**
  13133. * @class ButtonGroup
  13134. * @classdesc Manages events on groups of buttons.
  13135. *
  13136. * @memberof OpenSeadragon
  13137. * @param {Object} options - A dictionary of settings applied against the entire group of buttons.
  13138. * @param {Array} options.buttons Array of buttons
  13139. * @param {Element} [options.element] Element to use as the container
  13140. **/
  13141. $.ButtonGroup = function( options ) {
  13142. $.extend( true, this, {
  13143. /**
  13144. * An array containing the buttons themselves.
  13145. * @member {Array} buttons
  13146. * @memberof OpenSeadragon.ButtonGroup#
  13147. */
  13148. buttons: [],
  13149. clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold,
  13150. clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold,
  13151. labelText: ""
  13152. }, options );
  13153. // copy the button elements TODO: Why?
  13154. var buttons = this.buttons.concat([]),
  13155. _this = this,
  13156. i;
  13157. /**
  13158. * The shared container for the buttons.
  13159. * @member {Element} element
  13160. * @memberof OpenSeadragon.ButtonGroup#
  13161. */
  13162. this.element = options.element || $.makeNeutralElement( "div" );
  13163. // TODO What if there IS an options.group specified?
  13164. if( !options.group ){
  13165. this.element.style.display = "inline-block";
  13166. //this.label = $.makeNeutralElement( "label" );
  13167. //TODO: support labels for ButtonGroups
  13168. //this.label.innerHTML = this.labelText;
  13169. //this.element.appendChild( this.label );
  13170. for ( i = 0; i < buttons.length; i++ ) {
  13171. this.element.appendChild( buttons[ i ].element );
  13172. }
  13173. }
  13174. $.setElementTouchActionNone( this.element );
  13175. /**
  13176. * Tracks mouse/touch/key events across the group of buttons.
  13177. * @member {OpenSeadragon.MouseTracker} tracker
  13178. * @memberof OpenSeadragon.ButtonGroup#
  13179. */
  13180. this.tracker = new $.MouseTracker({
  13181. element: this.element,
  13182. clickTimeThreshold: this.clickTimeThreshold,
  13183. clickDistThreshold: this.clickDistThreshold,
  13184. enterHandler: function ( event ) {
  13185. var i;
  13186. for ( i = 0; i < _this.buttons.length; i++ ) {
  13187. _this.buttons[ i ].notifyGroupEnter();
  13188. }
  13189. },
  13190. exitHandler: function ( event ) {
  13191. var i;
  13192. if ( !event.insideElementPressed ) {
  13193. for ( i = 0; i < _this.buttons.length; i++ ) {
  13194. _this.buttons[ i ].notifyGroupExit();
  13195. }
  13196. }
  13197. },
  13198. });
  13199. };
  13200. /** @lends OpenSeadragon.ButtonGroup.prototype */
  13201. $.ButtonGroup.prototype = {
  13202. /**
  13203. * TODO: Figure out why this is used on the public API and if a more useful
  13204. * api can be created.
  13205. * @function
  13206. * @private
  13207. */
  13208. emulateEnter: function() {
  13209. this.tracker.enterHandler( { eventSource: this.tracker } );
  13210. },
  13211. /**
  13212. * TODO: Figure out why this is used on the public API and if a more useful
  13213. * api can be created.
  13214. * @function
  13215. * @private
  13216. */
  13217. emulateExit: function() {
  13218. this.tracker.exitHandler( { eventSource: this.tracker } );
  13219. }
  13220. };
  13221. }( OpenSeadragon ));
  13222. /*
  13223. * OpenSeadragon - Rect
  13224. *
  13225. * Copyright (C) 2009 CodePlex Foundation
  13226. * Copyright (C) 2010-2013 OpenSeadragon contributors
  13227. *
  13228. * Redistribution and use in source and binary forms, with or without
  13229. * modification, are permitted provided that the following conditions are
  13230. * met:
  13231. *
  13232. * - Redistributions of source code must retain the above copyright notice,
  13233. * this list of conditions and the following disclaimer.
  13234. *
  13235. * - Redistributions in binary form must reproduce the above copyright
  13236. * notice, this list of conditions and the following disclaimer in the
  13237. * documentation and/or other materials provided with the distribution.
  13238. *
  13239. * - Neither the name of CodePlex Foundation nor the names of its
  13240. * contributors may be used to endorse or promote products derived from
  13241. * this software without specific prior written permission.
  13242. *
  13243. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  13244. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  13245. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  13246. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  13247. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  13248. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  13249. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  13250. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  13251. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  13252. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  13253. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  13254. */
  13255. (function($) {
  13256. /**
  13257. * @class Rect
  13258. * @classdesc A Rectangle is described by it top left coordinates (x, y), width,
  13259. * height and degrees of rotation around (x, y).
  13260. * Note that the coordinate system used is the one commonly used with images:
  13261. * x increases when going to the right
  13262. * y increases when going to the bottom
  13263. * degrees increases clockwise with 0 being the horizontal
  13264. *
  13265. * The constructor normalizes the rectangle to always have 0 <= degrees < 90
  13266. *
  13267. * @memberof OpenSeadragon
  13268. * @param {Number} [x=0] The vector component 'x'.
  13269. * @param {Number} [y=0] The vector component 'y'.
  13270. * @param {Number} [width=0] The vector component 'width'.
  13271. * @param {Number} [height=0] The vector component 'height'.
  13272. * @param {Number} [degrees=0] Rotation of the rectangle around (x,y) in degrees.
  13273. */
  13274. $.Rect = function(x, y, width, height, degrees) {
  13275. /**
  13276. * The vector component 'x'.
  13277. * @member {Number} x
  13278. * @memberof OpenSeadragon.Rect#
  13279. */
  13280. this.x = typeof (x) === "number" ? x : 0;
  13281. /**
  13282. * The vector component 'y'.
  13283. * @member {Number} y
  13284. * @memberof OpenSeadragon.Rect#
  13285. */
  13286. this.y = typeof (y) === "number" ? y : 0;
  13287. /**
  13288. * The vector component 'width'.
  13289. * @member {Number} width
  13290. * @memberof OpenSeadragon.Rect#
  13291. */
  13292. this.width = typeof (width) === "number" ? width : 0;
  13293. /**
  13294. * The vector component 'height'.
  13295. * @member {Number} height
  13296. * @memberof OpenSeadragon.Rect#
  13297. */
  13298. this.height = typeof (height) === "number" ? height : 0;
  13299. this.degrees = typeof (degrees) === "number" ? degrees : 0;
  13300. // Normalizes the rectangle.
  13301. this.degrees = $.positiveModulo(this.degrees, 360);
  13302. var newTopLeft, newWidth;
  13303. if (this.degrees >= 270) {
  13304. newTopLeft = this.getTopRight();
  13305. this.x = newTopLeft.x;
  13306. this.y = newTopLeft.y;
  13307. newWidth = this.height;
  13308. this.height = this.width;
  13309. this.width = newWidth;
  13310. this.degrees -= 270;
  13311. } else if (this.degrees >= 180) {
  13312. newTopLeft = this.getBottomRight();
  13313. this.x = newTopLeft.x;
  13314. this.y = newTopLeft.y;
  13315. this.degrees -= 180;
  13316. } else if (this.degrees >= 90) {
  13317. newTopLeft = this.getBottomLeft();
  13318. this.x = newTopLeft.x;
  13319. this.y = newTopLeft.y;
  13320. newWidth = this.height;
  13321. this.height = this.width;
  13322. this.width = newWidth;
  13323. this.degrees -= 90;
  13324. }
  13325. };
  13326. /**
  13327. * Builds a rectangle having the 3 specified points as summits.
  13328. * @static
  13329. * @memberof OpenSeadragon.Rect
  13330. * @param {OpenSeadragon.Point} topLeft
  13331. * @param {OpenSeadragon.Point} topRight
  13332. * @param {OpenSeadragon.Point} bottomLeft
  13333. * @returns {OpenSeadragon.Rect}
  13334. */
  13335. $.Rect.fromSummits = function(topLeft, topRight, bottomLeft) {
  13336. var width = topLeft.distanceTo(topRight);
  13337. var height = topLeft.distanceTo(bottomLeft);
  13338. var diff = topRight.minus(topLeft);
  13339. var radians = Math.atan(diff.y / diff.x);
  13340. if (diff.x < 0) {
  13341. radians += Math.PI;
  13342. } else if (diff.y < 0) {
  13343. radians += 2 * Math.PI;
  13344. }
  13345. return new $.Rect(
  13346. topLeft.x,
  13347. topLeft.y,
  13348. width,
  13349. height,
  13350. radians / Math.PI * 180);
  13351. };
  13352. /** @lends OpenSeadragon.Rect.prototype */
  13353. $.Rect.prototype = {
  13354. /**
  13355. * @function
  13356. * @returns {OpenSeadragon.Rect} a duplicate of this Rect
  13357. */
  13358. clone: function() {
  13359. return new $.Rect(
  13360. this.x,
  13361. this.y,
  13362. this.width,
  13363. this.height,
  13364. this.degrees);
  13365. },
  13366. /**
  13367. * The aspect ratio is simply the ratio of width to height.
  13368. * @function
  13369. * @returns {Number} The ratio of width to height.
  13370. */
  13371. getAspectRatio: function() {
  13372. return this.width / this.height;
  13373. },
  13374. /**
  13375. * Provides the coordinates of the upper-left corner of the rectangle as a
  13376. * point.
  13377. * @function
  13378. * @returns {OpenSeadragon.Point} The coordinate of the upper-left corner of
  13379. * the rectangle.
  13380. */
  13381. getTopLeft: function() {
  13382. return new $.Point(
  13383. this.x,
  13384. this.y
  13385. );
  13386. },
  13387. /**
  13388. * Provides the coordinates of the bottom-right corner of the rectangle as a
  13389. * point.
  13390. * @function
  13391. * @returns {OpenSeadragon.Point} The coordinate of the bottom-right corner of
  13392. * the rectangle.
  13393. */
  13394. getBottomRight: function() {
  13395. return new $.Point(this.x + this.width, this.y + this.height)
  13396. .rotate(this.degrees, this.getTopLeft());
  13397. },
  13398. /**
  13399. * Provides the coordinates of the top-right corner of the rectangle as a
  13400. * point.
  13401. * @function
  13402. * @returns {OpenSeadragon.Point} The coordinate of the top-right corner of
  13403. * the rectangle.
  13404. */
  13405. getTopRight: function() {
  13406. return new $.Point(this.x + this.width, this.y)
  13407. .rotate(this.degrees, this.getTopLeft());
  13408. },
  13409. /**
  13410. * Provides the coordinates of the bottom-left corner of the rectangle as a
  13411. * point.
  13412. * @function
  13413. * @returns {OpenSeadragon.Point} The coordinate of the bottom-left corner of
  13414. * the rectangle.
  13415. */
  13416. getBottomLeft: function() {
  13417. return new $.Point(this.x, this.y + this.height)
  13418. .rotate(this.degrees, this.getTopLeft());
  13419. },
  13420. /**
  13421. * Computes the center of the rectangle.
  13422. * @function
  13423. * @returns {OpenSeadragon.Point} The center of the rectangle as represented
  13424. * as represented by a 2-dimensional vector (x,y)
  13425. */
  13426. getCenter: function() {
  13427. return new $.Point(
  13428. this.x + this.width / 2.0,
  13429. this.y + this.height / 2.0
  13430. ).rotate(this.degrees, this.getTopLeft());
  13431. },
  13432. /**
  13433. * Returns the width and height component as a vector OpenSeadragon.Point
  13434. * @function
  13435. * @returns {OpenSeadragon.Point} The 2 dimensional vector representing the
  13436. * the width and height of the rectangle.
  13437. */
  13438. getSize: function() {
  13439. return new $.Point(this.width, this.height);
  13440. },
  13441. /**
  13442. * Determines if two Rectangles have equivalent components.
  13443. * @function
  13444. * @param {OpenSeadragon.Rect} rectangle The Rectangle to compare to.
  13445. * @return {Boolean} 'true' if all components are equal, otherwise 'false'.
  13446. */
  13447. equals: function(other) {
  13448. return (other instanceof $.Rect) &&
  13449. this.x === other.x &&
  13450. this.y === other.y &&
  13451. this.width === other.width &&
  13452. this.height === other.height &&
  13453. this.degrees === other.degrees;
  13454. },
  13455. /**
  13456. * Multiply all dimensions (except degrees) in this Rect by a factor and
  13457. * return a new Rect.
  13458. * @function
  13459. * @param {Number} factor The factor to multiply vector components.
  13460. * @returns {OpenSeadragon.Rect} A new rect representing the multiplication
  13461. * of the vector components by the factor
  13462. */
  13463. times: function(factor) {
  13464. return new $.Rect(
  13465. this.x * factor,
  13466. this.y * factor,
  13467. this.width * factor,
  13468. this.height * factor,
  13469. this.degrees);
  13470. },
  13471. /**
  13472. * Translate/move this Rect by a vector and return new Rect.
  13473. * @function
  13474. * @param {OpenSeadragon.Point} delta The translation vector.
  13475. * @returns {OpenSeadragon.Rect} A new rect with altered position
  13476. */
  13477. translate: function(delta) {
  13478. return new $.Rect(
  13479. this.x + delta.x,
  13480. this.y + delta.y,
  13481. this.width,
  13482. this.height,
  13483. this.degrees);
  13484. },
  13485. /**
  13486. * Returns the smallest rectangle that will contain this and the given
  13487. * rectangle bounding boxes.
  13488. * @param {OpenSeadragon.Rect} rect
  13489. * @return {OpenSeadragon.Rect} The new rectangle.
  13490. */
  13491. union: function(rect) {
  13492. var thisBoundingBox = this.getBoundingBox();
  13493. var otherBoundingBox = rect.getBoundingBox();
  13494. var left = Math.min(thisBoundingBox.x, otherBoundingBox.x);
  13495. var top = Math.min(thisBoundingBox.y, otherBoundingBox.y);
  13496. var right = Math.max(
  13497. thisBoundingBox.x + thisBoundingBox.width,
  13498. otherBoundingBox.x + otherBoundingBox.width);
  13499. var bottom = Math.max(
  13500. thisBoundingBox.y + thisBoundingBox.height,
  13501. otherBoundingBox.y + otherBoundingBox.height);
  13502. return new $.Rect(
  13503. left,
  13504. top,
  13505. right - left,
  13506. bottom - top);
  13507. },
  13508. /**
  13509. * Returns the bounding box of the intersection of this rectangle with the
  13510. * given rectangle.
  13511. * @param {OpenSeadragon.Rect} rect
  13512. * @return {OpenSeadragon.Rect} the bounding box of the intersection
  13513. * or null if the rectangles don't intersect.
  13514. */
  13515. intersection: function(rect) {
  13516. // Simplified version of Weiler Atherton clipping algorithm
  13517. // https://en.wikipedia.org/wiki/Weiler%E2%80%93Atherton_clipping_algorithm
  13518. // Because we just want the bounding box of the intersection,
  13519. // we can just compute the bounding box of:
  13520. // 1. all the summits of this which are inside rect
  13521. // 2. all the summits of rect which are inside this
  13522. // 3. all the intersections of rect and this
  13523. var EPSILON = 0.0000000001;
  13524. var intersectionPoints = [];
  13525. var thisTopLeft = this.getTopLeft();
  13526. if (rect.containsPoint(thisTopLeft, EPSILON)) {
  13527. intersectionPoints.push(thisTopLeft);
  13528. }
  13529. var thisTopRight = this.getTopRight();
  13530. if (rect.containsPoint(thisTopRight, EPSILON)) {
  13531. intersectionPoints.push(thisTopRight);
  13532. }
  13533. var thisBottomLeft = this.getBottomLeft();
  13534. if (rect.containsPoint(thisBottomLeft, EPSILON)) {
  13535. intersectionPoints.push(thisBottomLeft);
  13536. }
  13537. var thisBottomRight = this.getBottomRight();
  13538. if (rect.containsPoint(thisBottomRight, EPSILON)) {
  13539. intersectionPoints.push(thisBottomRight);
  13540. }
  13541. var rectTopLeft = rect.getTopLeft();
  13542. if (this.containsPoint(rectTopLeft, EPSILON)) {
  13543. intersectionPoints.push(rectTopLeft);
  13544. }
  13545. var rectTopRight = rect.getTopRight();
  13546. if (this.containsPoint(rectTopRight, EPSILON)) {
  13547. intersectionPoints.push(rectTopRight);
  13548. }
  13549. var rectBottomLeft = rect.getBottomLeft();
  13550. if (this.containsPoint(rectBottomLeft, EPSILON)) {
  13551. intersectionPoints.push(rectBottomLeft);
  13552. }
  13553. var rectBottomRight = rect.getBottomRight();
  13554. if (this.containsPoint(rectBottomRight, EPSILON)) {
  13555. intersectionPoints.push(rectBottomRight);
  13556. }
  13557. var thisSegments = this._getSegments();
  13558. var rectSegments = rect._getSegments();
  13559. for (var i = 0; i < thisSegments.length; i++) {
  13560. var thisSegment = thisSegments[i];
  13561. for (var j = 0; j < rectSegments.length; j++) {
  13562. var rectSegment = rectSegments[j];
  13563. var intersect = getIntersection(thisSegment[0], thisSegment[1],
  13564. rectSegment[0], rectSegment[1]);
  13565. if (intersect) {
  13566. intersectionPoints.push(intersect);
  13567. }
  13568. }
  13569. }
  13570. // Get intersection point of segments [a,b] and [c,d]
  13571. function getIntersection(a, b, c, d) {
  13572. // http://stackoverflow.com/a/1968345/1440403
  13573. var abVector = b.minus(a);
  13574. var cdVector = d.minus(c);
  13575. var denom = -cdVector.x * abVector.y + abVector.x * cdVector.y;
  13576. if (denom === 0) {
  13577. return null;
  13578. }
  13579. var s = (abVector.x * (a.y - c.y) - abVector.y * (a.x - c.x)) / denom;
  13580. var t = (cdVector.x * (a.y - c.y) - cdVector.y * (a.x - c.x)) / denom;
  13581. if (-EPSILON <= s && s <= 1 - EPSILON &&
  13582. -EPSILON <= t && t <= 1 - EPSILON) {
  13583. return new $.Point(a.x + t * abVector.x, a.y + t * abVector.y);
  13584. }
  13585. return null;
  13586. }
  13587. if (intersectionPoints.length === 0) {
  13588. return null;
  13589. }
  13590. var minX = intersectionPoints[0].x;
  13591. var maxX = intersectionPoints[0].x;
  13592. var minY = intersectionPoints[0].y;
  13593. var maxY = intersectionPoints[0].y;
  13594. for (var k = 1; k < intersectionPoints.length; k++) {
  13595. var point = intersectionPoints[k];
  13596. if (point.x < minX) {
  13597. minX = point.x;
  13598. }
  13599. if (point.x > maxX) {
  13600. maxX = point.x;
  13601. }
  13602. if (point.y < minY) {
  13603. minY = point.y;
  13604. }
  13605. if (point.y > maxY) {
  13606. maxY = point.y;
  13607. }
  13608. }
  13609. return new $.Rect(minX, minY, maxX - minX, maxY - minY);
  13610. },
  13611. // private
  13612. _getSegments: function() {
  13613. var topLeft = this.getTopLeft();
  13614. var topRight = this.getTopRight();
  13615. var bottomLeft = this.getBottomLeft();
  13616. var bottomRight = this.getBottomRight();
  13617. return [[topLeft, topRight],
  13618. [topRight, bottomRight],
  13619. [bottomRight, bottomLeft],
  13620. [bottomLeft, topLeft]];
  13621. },
  13622. /**
  13623. * Rotates a rectangle around a point.
  13624. * @function
  13625. * @param {Number} degrees The angle in degrees to rotate.
  13626. * @param {OpenSeadragon.Point} [pivot] The point about which to rotate.
  13627. * Defaults to the center of the rectangle.
  13628. * @return {OpenSeadragon.Rect}
  13629. */
  13630. rotate: function(degrees, pivot) {
  13631. degrees = $.positiveModulo(degrees, 360);
  13632. if (degrees === 0) {
  13633. return this.clone();
  13634. }
  13635. pivot = pivot || this.getCenter();
  13636. var newTopLeft = this.getTopLeft().rotate(degrees, pivot);
  13637. var newTopRight = this.getTopRight().rotate(degrees, pivot);
  13638. var diff = newTopRight.minus(newTopLeft);
  13639. // Handle floating point error
  13640. diff = diff.apply(function(x) {
  13641. var EPSILON = 1e-15;
  13642. return Math.abs(x) < EPSILON ? 0 : x;
  13643. });
  13644. var radians = Math.atan(diff.y / diff.x);
  13645. if (diff.x < 0) {
  13646. radians += Math.PI;
  13647. } else if (diff.y < 0) {
  13648. radians += 2 * Math.PI;
  13649. }
  13650. return new $.Rect(
  13651. newTopLeft.x,
  13652. newTopLeft.y,
  13653. this.width,
  13654. this.height,
  13655. radians / Math.PI * 180);
  13656. },
  13657. /**
  13658. * Retrieves the smallest horizontal (degrees=0) rectangle which contains
  13659. * this rectangle.
  13660. * @returns {OpenSeadragon.Rect}
  13661. */
  13662. getBoundingBox: function() {
  13663. if (this.degrees === 0) {
  13664. return this.clone();
  13665. }
  13666. var topLeft = this.getTopLeft();
  13667. var topRight = this.getTopRight();
  13668. var bottomLeft = this.getBottomLeft();
  13669. var bottomRight = this.getBottomRight();
  13670. var minX = Math.min(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x);
  13671. var maxX = Math.max(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x);
  13672. var minY = Math.min(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y);
  13673. var maxY = Math.max(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y);
  13674. return new $.Rect(
  13675. minX,
  13676. minY,
  13677. maxX - minX,
  13678. maxY - minY);
  13679. },
  13680. /**
  13681. * Retrieves the smallest horizontal (degrees=0) rectangle which contains
  13682. * this rectangle and has integers x, y, width and height
  13683. * @returns {OpenSeadragon.Rect}
  13684. */
  13685. getIntegerBoundingBox: function() {
  13686. var boundingBox = this.getBoundingBox();
  13687. var x = Math.floor(boundingBox.x);
  13688. var y = Math.floor(boundingBox.y);
  13689. var width = Math.ceil(boundingBox.width + boundingBox.x - x);
  13690. var height = Math.ceil(boundingBox.height + boundingBox.y - y);
  13691. return new $.Rect(x, y, width, height);
  13692. },
  13693. /**
  13694. * Determines whether a point is inside this rectangle (edge included).
  13695. * @function
  13696. * @param {OpenSeadragon.Point} point
  13697. * @param {Number} [epsilon=0] the margin of error allowed
  13698. * @returns {Boolean} true if the point is inside this rectangle, false
  13699. * otherwise.
  13700. */
  13701. containsPoint: function(point, epsilon) {
  13702. epsilon = epsilon || 0;
  13703. // See http://stackoverflow.com/a/2752754/1440403 for explanation
  13704. var topLeft = this.getTopLeft();
  13705. var topRight = this.getTopRight();
  13706. var bottomLeft = this.getBottomLeft();
  13707. var topDiff = topRight.minus(topLeft);
  13708. var leftDiff = bottomLeft.minus(topLeft);
  13709. return ((point.x - topLeft.x) * topDiff.x +
  13710. (point.y - topLeft.y) * topDiff.y >= -epsilon) &&
  13711. ((point.x - topRight.x) * topDiff.x +
  13712. (point.y - topRight.y) * topDiff.y <= epsilon) &&
  13713. ((point.x - topLeft.x) * leftDiff.x +
  13714. (point.y - topLeft.y) * leftDiff.y >= -epsilon) &&
  13715. ((point.x - bottomLeft.x) * leftDiff.x +
  13716. (point.y - bottomLeft.y) * leftDiff.y <= epsilon);
  13717. },
  13718. /**
  13719. * Provides a string representation of the rectangle which is useful for
  13720. * debugging.
  13721. * @function
  13722. * @returns {String} A string representation of the rectangle.
  13723. */
  13724. toString: function() {
  13725. return "[" +
  13726. (Math.round(this.x * 100) / 100) + ", " +
  13727. (Math.round(this.y * 100) / 100) + ", " +
  13728. (Math.round(this.width * 100) / 100) + "x" +
  13729. (Math.round(this.height * 100) / 100) + ", " +
  13730. (Math.round(this.degrees * 100) / 100) + "deg" +
  13731. "]";
  13732. }
  13733. };
  13734. }(OpenSeadragon));
  13735. /*
  13736. * OpenSeadragon - ReferenceStrip
  13737. *
  13738. * Copyright (C) 2009 CodePlex Foundation
  13739. * Copyright (C) 2010-2013 OpenSeadragon contributors
  13740. *
  13741. * Redistribution and use in source and binary forms, with or without
  13742. * modification, are permitted provided that the following conditions are
  13743. * met:
  13744. *
  13745. * - Redistributions of source code must retain the above copyright notice,
  13746. * this list of conditions and the following disclaimer.
  13747. *
  13748. * - Redistributions in binary form must reproduce the above copyright
  13749. * notice, this list of conditions and the following disclaimer in the
  13750. * documentation and/or other materials provided with the distribution.
  13751. *
  13752. * - Neither the name of CodePlex Foundation nor the names of its
  13753. * contributors may be used to endorse or promote products derived from
  13754. * this software without specific prior written permission.
  13755. *
  13756. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  13757. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  13758. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  13759. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  13760. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  13761. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  13762. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  13763. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  13764. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  13765. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  13766. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  13767. */
  13768. (function ( $ ) {
  13769. // dictionary from id to private properties
  13770. var THIS = {};
  13771. /**
  13772. * The CollectionDrawer is a reimplementation if the Drawer API that
  13773. * focuses on allowing a viewport to be redefined as a collection
  13774. * of smaller viewports, defined by a clear number of rows and / or
  13775. * columns of which each item in the matrix of viewports has its own
  13776. * source.
  13777. *
  13778. * This idea is a reexpression of the idea of dzi collections
  13779. * which allows a clearer algorithm to reuse the tile sources already
  13780. * supported by OpenSeadragon, in heterogenious or homogenious
  13781. * sequences just like mixed groups already supported by the viewer
  13782. * for the purpose of image sequnces.
  13783. *
  13784. * TODO: The difficult part of this feature is figuring out how to express
  13785. * this functionality as a combination of the functionality already
  13786. * provided by Drawer, Viewport, TileSource, and Navigator. It may
  13787. * require better abstraction at those points in order to efficiently
  13788. * reuse those paradigms.
  13789. */
  13790. /**
  13791. * @class ReferenceStrip
  13792. * @memberof OpenSeadragon
  13793. * @param {Object} options
  13794. */
  13795. $.ReferenceStrip = function ( options ) {
  13796. var _this = this,
  13797. viewer = options.viewer,
  13798. viewerSize = $.getElementSize( viewer.element ),
  13799. element,
  13800. style,
  13801. i;
  13802. //We may need to create a new element and id if they did not
  13803. //provide the id for the existing element
  13804. if ( !options.id ) {
  13805. options.id = 'referencestrip-' + $.now();
  13806. this.element = $.makeNeutralElement( "div" );
  13807. this.element.id = options.id;
  13808. this.element.className = 'referencestrip';
  13809. }
  13810. options = $.extend( true, {
  13811. sizeRatio: $.DEFAULT_SETTINGS.referenceStripSizeRatio,
  13812. position: $.DEFAULT_SETTINGS.referenceStripPosition,
  13813. scroll: $.DEFAULT_SETTINGS.referenceStripScroll,
  13814. clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold
  13815. }, options, {
  13816. //required overrides
  13817. element: this.element,
  13818. //These need to be overridden to prevent recursion since
  13819. //the navigator is a viewer and a viewer has a navigator
  13820. showNavigator: false,
  13821. mouseNavEnabled: false,
  13822. showNavigationControl: false,
  13823. showSequenceControl: false
  13824. } );
  13825. $.extend( this, options );
  13826. //Private state properties
  13827. THIS[this.id] = {
  13828. "animating": false
  13829. };
  13830. this.minPixelRatio = this.viewer.minPixelRatio;
  13831. style = this.element.style;
  13832. style.marginTop = '0px';
  13833. style.marginRight = '0px';
  13834. style.marginBottom = '0px';
  13835. style.marginLeft = '0px';
  13836. style.left = '0px';
  13837. style.bottom = '0px';
  13838. style.border = '0px';
  13839. style.background = '#000';
  13840. style.position = 'relative';
  13841. $.setElementTouchActionNone( this.element );
  13842. $.setElementOpacity( this.element, 0.8 );
  13843. this.viewer = viewer;
  13844. this.innerTracker = new $.MouseTracker( {
  13845. element: this.element,
  13846. dragHandler: $.delegate( this, onStripDrag ),
  13847. scrollHandler: $.delegate( this, onStripScroll ),
  13848. enterHandler: $.delegate( this, onStripEnter ),
  13849. exitHandler: $.delegate( this, onStripExit ),
  13850. keyDownHandler: $.delegate( this, onKeyDown ),
  13851. keyHandler: $.delegate( this, onKeyPress )
  13852. } );
  13853. //Controls the position and orientation of the reference strip and sets the
  13854. //appropriate width and height
  13855. if ( options.width && options.height ) {
  13856. this.element.style.width = options.width + 'px';
  13857. this.element.style.height = options.height + 'px';
  13858. viewer.addControl(
  13859. this.element,
  13860. { anchor: $.ControlAnchor.BOTTOM_LEFT }
  13861. );
  13862. } else {
  13863. if ( "horizontal" == options.scroll ) {
  13864. this.element.style.width = (
  13865. viewerSize.x *
  13866. options.sizeRatio *
  13867. viewer.tileSources.length
  13868. ) + ( 12 * viewer.tileSources.length ) + 'px';
  13869. this.element.style.height = (
  13870. viewerSize.y *
  13871. options.sizeRatio
  13872. ) + 'px';
  13873. viewer.addControl(
  13874. this.element,
  13875. { anchor: $.ControlAnchor.BOTTOM_LEFT }
  13876. );
  13877. } else {
  13878. this.element.style.height = (
  13879. viewerSize.y *
  13880. options.sizeRatio *
  13881. viewer.tileSources.length
  13882. ) + ( 12 * viewer.tileSources.length ) + 'px';
  13883. this.element.style.width = (
  13884. viewerSize.x *
  13885. options.sizeRatio
  13886. ) + 'px';
  13887. viewer.addControl(
  13888. this.element,
  13889. { anchor: $.ControlAnchor.TOP_LEFT }
  13890. );
  13891. }
  13892. }
  13893. this.panelWidth = ( viewerSize.x * this.sizeRatio ) + 8;
  13894. this.panelHeight = ( viewerSize.y * this.sizeRatio ) + 8;
  13895. this.panels = [];
  13896. this.miniViewers = {};
  13897. /*jshint loopfunc:true*/
  13898. for ( i = 0; i < viewer.tileSources.length; i++ ) {
  13899. element = $.makeNeutralElement( 'div' );
  13900. element.id = this.element.id + "-" + i;
  13901. element.style.width = _this.panelWidth + 'px';
  13902. element.style.height = _this.panelHeight + 'px';
  13903. element.style.display = 'inline';
  13904. element.style.float = 'left'; //Webkit
  13905. element.style.cssFloat = 'left'; //Firefox
  13906. element.style.styleFloat = 'left'; //IE
  13907. element.style.padding = '2px';
  13908. $.setElementTouchActionNone( element );
  13909. element.innerTracker = new $.MouseTracker( {
  13910. element: element,
  13911. clickTimeThreshold: this.clickTimeThreshold,
  13912. clickDistThreshold: this.clickDistThreshold,
  13913. pressHandler: function ( event ) {
  13914. event.eventSource.dragging = $.now();
  13915. },
  13916. releaseHandler: function ( event ) {
  13917. var tracker = event.eventSource,
  13918. id = tracker.element.id,
  13919. page = Number( id.split( '-' )[2] ),
  13920. now = $.now();
  13921. if ( event.insideElementPressed &&
  13922. event.insideElementReleased &&
  13923. tracker.dragging &&
  13924. ( now - tracker.dragging ) < tracker.clickTimeThreshold ) {
  13925. tracker.dragging = null;
  13926. viewer.goToPage( page );
  13927. }
  13928. }
  13929. } );
  13930. this.element.appendChild( element );
  13931. element.activePanel = false;
  13932. this.panels.push( element );
  13933. }
  13934. loadPanels( this, this.scroll == 'vertical' ? viewerSize.y : viewerSize.x, 0 );
  13935. this.setFocus( 0 );
  13936. };
  13937. $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.ReferenceStrip.prototype */{
  13938. /**
  13939. * @function
  13940. */
  13941. setFocus: function ( page ) {
  13942. var element = $.getElement( this.element.id + '-' + page ),
  13943. viewerSize = $.getElementSize( this.viewer.canvas ),
  13944. scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ),
  13945. scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
  13946. offsetLeft = -Number( this.element.style.marginLeft.replace( 'px', '' ) ),
  13947. offsetTop = -Number( this.element.style.marginTop.replace( 'px', '' ) ),
  13948. offset;
  13949. if ( this.currentSelected !== element ) {
  13950. if ( this.currentSelected ) {
  13951. this.currentSelected.style.background = '#000';
  13952. }
  13953. this.currentSelected = element;
  13954. this.currentSelected.style.background = '#999';
  13955. if ( 'horizontal' == this.scroll ) {
  13956. //right left
  13957. offset = ( Number( page ) ) * ( this.panelWidth + 3 );
  13958. if ( offset > offsetLeft + viewerSize.x - this.panelWidth ) {
  13959. offset = Math.min( offset, ( scrollWidth - viewerSize.x ) );
  13960. this.element.style.marginLeft = -offset + 'px';
  13961. loadPanels( this, viewerSize.x, -offset );
  13962. } else if ( offset < offsetLeft ) {
  13963. offset = Math.max( 0, offset - viewerSize.x / 2 );
  13964. this.element.style.marginLeft = -offset + 'px';
  13965. loadPanels( this, viewerSize.x, -offset );
  13966. }
  13967. } else {
  13968. offset = ( Number( page ) ) * ( this.panelHeight + 3 );
  13969. if ( offset > offsetTop + viewerSize.y - this.panelHeight ) {
  13970. offset = Math.min( offset, ( scrollHeight - viewerSize.y ) );
  13971. this.element.style.marginTop = -offset + 'px';
  13972. loadPanels( this, viewerSize.y, -offset );
  13973. } else if ( offset < offsetTop ) {
  13974. offset = Math.max( 0, offset - viewerSize.y / 2 );
  13975. this.element.style.marginTop = -offset + 'px';
  13976. loadPanels( this, viewerSize.y, -offset );
  13977. }
  13978. }
  13979. this.currentPage = page;
  13980. onStripEnter.call( this, { eventSource: this.innerTracker } );
  13981. }
  13982. },
  13983. /**
  13984. * @function
  13985. */
  13986. update: function () {
  13987. if ( THIS[this.id].animating ) {
  13988. $.console.log( 'image reference strip update' );
  13989. return true;
  13990. }
  13991. return false;
  13992. },
  13993. // Overrides Viewer.destroy
  13994. destroy: function() {
  13995. if (this.miniViewers) {
  13996. for (var key in this.miniViewers) {
  13997. this.miniViewers[key].destroy();
  13998. }
  13999. }
  14000. if (this.element) {
  14001. this.element.parentNode.removeChild(this.element);
  14002. }
  14003. }
  14004. } );
  14005. /**
  14006. * @private
  14007. * @inner
  14008. * @function
  14009. */
  14010. function onStripDrag( event ) {
  14011. var offsetLeft = Number( this.element.style.marginLeft.replace( 'px', '' ) ),
  14012. offsetTop = Number( this.element.style.marginTop.replace( 'px', '' ) ),
  14013. scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ),
  14014. scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
  14015. viewerSize = $.getElementSize( this.viewer.canvas );
  14016. this.dragging = true;
  14017. if ( this.element ) {
  14018. if ( 'horizontal' == this.scroll ) {
  14019. if ( -event.delta.x > 0 ) {
  14020. //forward
  14021. if ( offsetLeft > -( scrollWidth - viewerSize.x ) ) {
  14022. this.element.style.marginLeft = ( offsetLeft + ( event.delta.x * 2 ) ) + 'px';
  14023. loadPanels( this, viewerSize.x, offsetLeft + ( event.delta.x * 2 ) );
  14024. }
  14025. } else if ( -event.delta.x < 0 ) {
  14026. //reverse
  14027. if ( offsetLeft < 0 ) {
  14028. this.element.style.marginLeft = ( offsetLeft + ( event.delta.x * 2 ) ) + 'px';
  14029. loadPanels( this, viewerSize.x, offsetLeft + ( event.delta.x * 2 ) );
  14030. }
  14031. }
  14032. } else {
  14033. if ( -event.delta.y > 0 ) {
  14034. //forward
  14035. if ( offsetTop > -( scrollHeight - viewerSize.y ) ) {
  14036. this.element.style.marginTop = ( offsetTop + ( event.delta.y * 2 ) ) + 'px';
  14037. loadPanels( this, viewerSize.y, offsetTop + ( event.delta.y * 2 ) );
  14038. }
  14039. } else if ( -event.delta.y < 0 ) {
  14040. //reverse
  14041. if ( offsetTop < 0 ) {
  14042. this.element.style.marginTop = ( offsetTop + ( event.delta.y * 2 ) ) + 'px';
  14043. loadPanels( this, viewerSize.y, offsetTop + ( event.delta.y * 2 ) );
  14044. }
  14045. }
  14046. }
  14047. }
  14048. return false;
  14049. }
  14050. /**
  14051. * @private
  14052. * @inner
  14053. * @function
  14054. */
  14055. function onStripScroll( event ) {
  14056. var offsetLeft = Number( this.element.style.marginLeft.replace( 'px', '' ) ),
  14057. offsetTop = Number( this.element.style.marginTop.replace( 'px', '' ) ),
  14058. scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ),
  14059. scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
  14060. viewerSize = $.getElementSize( this.viewer.canvas );
  14061. if ( this.element ) {
  14062. if ( 'horizontal' == this.scroll ) {
  14063. if ( event.scroll > 0 ) {
  14064. //forward
  14065. if ( offsetLeft > -( scrollWidth - viewerSize.x ) ) {
  14066. this.element.style.marginLeft = ( offsetLeft - ( event.scroll * 60 ) ) + 'px';
  14067. loadPanels( this, viewerSize.x, offsetLeft - ( event.scroll * 60 ) );
  14068. }
  14069. } else if ( event.scroll < 0 ) {
  14070. //reverse
  14071. if ( offsetLeft < 0 ) {
  14072. this.element.style.marginLeft = ( offsetLeft - ( event.scroll * 60 ) ) + 'px';
  14073. loadPanels( this, viewerSize.x, offsetLeft - ( event.scroll * 60 ) );
  14074. }
  14075. }
  14076. } else {
  14077. if ( event.scroll < 0 ) {
  14078. //scroll up
  14079. if ( offsetTop > viewerSize.y - scrollHeight ) {
  14080. this.element.style.marginTop = ( offsetTop + ( event.scroll * 60 ) ) + 'px';
  14081. loadPanels( this, viewerSize.y, offsetTop + ( event.scroll * 60 ) );
  14082. }
  14083. } else if ( event.scroll > 0 ) {
  14084. //scroll dowm
  14085. if ( offsetTop < 0 ) {
  14086. this.element.style.marginTop = ( offsetTop + ( event.scroll * 60 ) ) + 'px';
  14087. loadPanels( this, viewerSize.y, offsetTop + ( event.scroll * 60 ) );
  14088. }
  14089. }
  14090. }
  14091. }
  14092. //cancels event
  14093. return false;
  14094. }
  14095. function loadPanels( strip, viewerSize, scroll ) {
  14096. var panelSize,
  14097. activePanelsStart,
  14098. activePanelsEnd,
  14099. miniViewer,
  14100. style,
  14101. i,
  14102. element;
  14103. if ( 'horizontal' == strip.scroll ) {
  14104. panelSize = strip.panelWidth;
  14105. } else {
  14106. panelSize = strip.panelHeight;
  14107. }
  14108. activePanelsStart = Math.ceil( viewerSize / panelSize ) + 5;
  14109. activePanelsEnd = Math.ceil( ( Math.abs( scroll ) + viewerSize ) / panelSize ) + 1;
  14110. activePanelsStart = activePanelsEnd - activePanelsStart;
  14111. activePanelsStart = activePanelsStart < 0 ? 0 : activePanelsStart;
  14112. for ( i = activePanelsStart; i < activePanelsEnd && i < strip.panels.length; i++ ) {
  14113. element = strip.panels[i];
  14114. if ( !element.activePanel ) {
  14115. var miniTileSource;
  14116. var originalTileSource = strip.viewer.tileSources[i];
  14117. if (originalTileSource.referenceStripThumbnailUrl) {
  14118. miniTileSource = {
  14119. type: 'image',
  14120. url: originalTileSource.referenceStripThumbnailUrl
  14121. };
  14122. } else {
  14123. miniTileSource = originalTileSource;
  14124. }
  14125. miniViewer = new $.Viewer( {
  14126. id: element.id,
  14127. tileSources: [miniTileSource],
  14128. element: element,
  14129. navigatorSizeRatio: strip.sizeRatio,
  14130. showNavigator: false,
  14131. mouseNavEnabled: false,
  14132. showNavigationControl: false,
  14133. showSequenceControl: false,
  14134. immediateRender: true,
  14135. blendTime: 0,
  14136. animationTime: 0
  14137. } );
  14138. miniViewer.displayRegion = $.makeNeutralElement( "div" );
  14139. miniViewer.displayRegion.id = element.id + '-displayregion';
  14140. miniViewer.displayRegion.className = 'displayregion';
  14141. style = miniViewer.displayRegion.style;
  14142. style.position = 'relative';
  14143. style.top = '0px';
  14144. style.left = '0px';
  14145. style.fontSize = '0px';
  14146. style.overflow = 'hidden';
  14147. style.float = 'left'; //Webkit
  14148. style.cssFloat = 'left'; //Firefox
  14149. style.styleFloat = 'left'; //IE
  14150. style.zIndex = 999999999;
  14151. style.cursor = 'default';
  14152. style.width = ( strip.panelWidth - 4 ) + 'px';
  14153. style.height = ( strip.panelHeight - 4 ) + 'px';
  14154. // TODO: What is this for? Future keyboard navigation support?
  14155. miniViewer.displayRegion.innerTracker = new $.MouseTracker( {
  14156. element: miniViewer.displayRegion,
  14157. startDisabled: true
  14158. } );
  14159. element.getElementsByTagName( 'div' )[0].appendChild(
  14160. miniViewer.displayRegion
  14161. );
  14162. strip.miniViewers[element.id] = miniViewer;
  14163. element.activePanel = true;
  14164. }
  14165. }
  14166. }
  14167. /**
  14168. * @private
  14169. * @inner
  14170. * @function
  14171. */
  14172. function onStripEnter( event ) {
  14173. var element = event.eventSource.element;
  14174. //$.setElementOpacity(element, 0.8);
  14175. //element.style.border = '1px solid #555';
  14176. //element.style.background = '#000';
  14177. if ( 'horizontal' == this.scroll ) {
  14178. //element.style.paddingTop = "0px";
  14179. element.style.marginBottom = "0px";
  14180. } else {
  14181. //element.style.paddingRight = "0px";
  14182. element.style.marginLeft = "0px";
  14183. }
  14184. return false;
  14185. }
  14186. /**
  14187. * @private
  14188. * @inner
  14189. * @function
  14190. */
  14191. function onStripExit( event ) {
  14192. var element = event.eventSource.element;
  14193. if ( 'horizontal' == this.scroll ) {
  14194. //element.style.paddingTop = "10px";
  14195. element.style.marginBottom = "-" + ( $.getElementSize( element ).y / 2 ) + "px";
  14196. } else {
  14197. //element.style.paddingRight = "10px";
  14198. element.style.marginLeft = "-" + ( $.getElementSize( element ).x / 2 ) + "px";
  14199. }
  14200. return false;
  14201. }
  14202. /**
  14203. * @private
  14204. * @inner
  14205. * @function
  14206. */
  14207. function onKeyDown( event ) {
  14208. //console.log( event.keyCode );
  14209. if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
  14210. switch ( event.keyCode ) {
  14211. case 38: //up arrow
  14212. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
  14213. return false;
  14214. case 40: //down arrow
  14215. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
  14216. return false;
  14217. case 37: //left arrow
  14218. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
  14219. return false;
  14220. case 39: //right arrow
  14221. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
  14222. return false;
  14223. default:
  14224. //console.log( 'navigator keycode %s', event.keyCode );
  14225. return true;
  14226. }
  14227. } else {
  14228. return true;
  14229. }
  14230. }
  14231. /**
  14232. * @private
  14233. * @inner
  14234. * @function
  14235. */
  14236. function onKeyPress( event ) {
  14237. //console.log( event.keyCode );
  14238. if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
  14239. switch ( event.keyCode ) {
  14240. case 61: //=|+
  14241. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
  14242. return false;
  14243. case 45: //-|_
  14244. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
  14245. return false;
  14246. case 48: //0|)
  14247. case 119: //w
  14248. case 87: //W
  14249. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
  14250. return false;
  14251. case 115: //s
  14252. case 83: //S
  14253. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
  14254. return false;
  14255. case 97: //a
  14256. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
  14257. return false;
  14258. case 100: //d
  14259. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
  14260. return false;
  14261. default:
  14262. //console.log( 'navigator keycode %s', event.keyCode );
  14263. return true;
  14264. }
  14265. } else {
  14266. return true;
  14267. }
  14268. }
  14269. }(OpenSeadragon));
  14270. /*
  14271. * OpenSeadragon - DisplayRect
  14272. *
  14273. * Copyright (C) 2009 CodePlex Foundation
  14274. * Copyright (C) 2010-2013 OpenSeadragon contributors
  14275. *
  14276. * Redistribution and use in source and binary forms, with or without
  14277. * modification, are permitted provided that the following conditions are
  14278. * met:
  14279. *
  14280. * - Redistributions of source code must retain the above copyright notice,
  14281. * this list of conditions and the following disclaimer.
  14282. *
  14283. * - Redistributions in binary form must reproduce the above copyright
  14284. * notice, this list of conditions and the following disclaimer in the
  14285. * documentation and/or other materials provided with the distribution.
  14286. *
  14287. * - Neither the name of CodePlex Foundation nor the names of its
  14288. * contributors may be used to endorse or promote products derived from
  14289. * this software without specific prior written permission.
  14290. *
  14291. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  14292. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  14293. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  14294. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  14295. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  14296. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  14297. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  14298. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  14299. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  14300. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  14301. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14302. */
  14303. (function( $ ){
  14304. /**
  14305. * @class DisplayRect
  14306. * @classdesc A display rectangle is very similar to {@link OpenSeadragon.Rect} but adds two
  14307. * fields, 'minLevel' and 'maxLevel' which denote the supported zoom levels
  14308. * for this rectangle.
  14309. *
  14310. * @memberof OpenSeadragon
  14311. * @extends OpenSeadragon.Rect
  14312. * @param {Number} x The vector component 'x'.
  14313. * @param {Number} y The vector component 'y'.
  14314. * @param {Number} width The vector component 'height'.
  14315. * @param {Number} height The vector component 'width'.
  14316. * @param {Number} minLevel The lowest zoom level supported.
  14317. * @param {Number} maxLevel The highest zoom level supported.
  14318. */
  14319. $.DisplayRect = function( x, y, width, height, minLevel, maxLevel ) {
  14320. $.Rect.apply( this, [ x, y, width, height ] );
  14321. /**
  14322. * The lowest zoom level supported.
  14323. * @member {Number} minLevel
  14324. * @memberof OpenSeadragon.DisplayRect#
  14325. */
  14326. this.minLevel = minLevel;
  14327. /**
  14328. * The highest zoom level supported.
  14329. * @member {Number} maxLevel
  14330. * @memberof OpenSeadragon.DisplayRect#
  14331. */
  14332. this.maxLevel = maxLevel;
  14333. };
  14334. $.extend( $.DisplayRect.prototype, $.Rect.prototype );
  14335. }( OpenSeadragon ));
  14336. /*
  14337. * OpenSeadragon - Spring
  14338. *
  14339. * Copyright (C) 2009 CodePlex Foundation
  14340. * Copyright (C) 2010-2013 OpenSeadragon contributors
  14341. *
  14342. * Redistribution and use in source and binary forms, with or without
  14343. * modification, are permitted provided that the following conditions are
  14344. * met:
  14345. *
  14346. * - Redistributions of source code must retain the above copyright notice,
  14347. * this list of conditions and the following disclaimer.
  14348. *
  14349. * - Redistributions in binary form must reproduce the above copyright
  14350. * notice, this list of conditions and the following disclaimer in the
  14351. * documentation and/or other materials provided with the distribution.
  14352. *
  14353. * - Neither the name of CodePlex Foundation nor the names of its
  14354. * contributors may be used to endorse or promote products derived from
  14355. * this software without specific prior written permission.
  14356. *
  14357. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  14358. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  14359. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  14360. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  14361. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  14362. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  14363. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  14364. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  14365. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  14366. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  14367. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14368. */
  14369. (function( $ ){
  14370. /**
  14371. * @class Spring
  14372. * @memberof OpenSeadragon
  14373. * @param {Object} options - Spring configuration settings.
  14374. * @param {Number} options.springStiffness - Spring stiffness. Must be greater than zero.
  14375. * The closer to zero, the closer to linear animation.
  14376. * @param {Number} options.animationTime - Animation duration per spring, in seconds.
  14377. * Must be zero or greater.
  14378. * @param {Number} [options.initial=0] - Initial value of spring.
  14379. * @param {Boolean} [options.exponential=false] - Whether this spring represents
  14380. * an exponential scale (such as zoom) and should be animated accordingly. Note that
  14381. * exponential springs must have non-zero values.
  14382. */
  14383. $.Spring = function( options ) {
  14384. var args = arguments;
  14385. if( typeof ( options ) != 'object' ){
  14386. //allows backward compatible use of ( initialValue, config ) as
  14387. //constructor parameters
  14388. options = {
  14389. initial: args.length && typeof ( args[ 0 ] ) == "number" ?
  14390. args[ 0 ] :
  14391. undefined,
  14392. /**
  14393. * Spring stiffness.
  14394. * @member {Number} springStiffness
  14395. * @memberof OpenSeadragon.Spring#
  14396. */
  14397. springStiffness: args.length > 1 ?
  14398. args[ 1 ].springStiffness :
  14399. 5.0,
  14400. /**
  14401. * Animation duration per spring.
  14402. * @member {Number} animationTime
  14403. * @memberof OpenSeadragon.Spring#
  14404. */
  14405. animationTime: args.length > 1 ?
  14406. args[ 1 ].animationTime :
  14407. 1.5
  14408. };
  14409. }
  14410. $.console.assert(typeof options.springStiffness === "number" && options.springStiffness !== 0,
  14411. "[OpenSeadragon.Spring] options.springStiffness must be a non-zero number");
  14412. $.console.assert(typeof options.animationTime === "number" && options.animationTime >= 0,
  14413. "[OpenSeadragon.Spring] options.animationTime must be a number greater than or equal to 0");
  14414. if (options.exponential) {
  14415. this._exponential = true;
  14416. delete options.exponential;
  14417. }
  14418. $.extend( true, this, options);
  14419. /**
  14420. * @member {Object} current
  14421. * @memberof OpenSeadragon.Spring#
  14422. * @property {Number} value
  14423. * @property {Number} time
  14424. */
  14425. this.current = {
  14426. value: typeof ( this.initial ) == "number" ?
  14427. this.initial :
  14428. (this._exponential ? 0 : 1),
  14429. time: $.now() // always work in milliseconds
  14430. };
  14431. $.console.assert(!this._exponential || this.current.value !== 0,
  14432. "[OpenSeadragon.Spring] value must be non-zero for exponential springs");
  14433. /**
  14434. * @member {Object} start
  14435. * @memberof OpenSeadragon.Spring#
  14436. * @property {Number} value
  14437. * @property {Number} time
  14438. */
  14439. this.start = {
  14440. value: this.current.value,
  14441. time: this.current.time
  14442. };
  14443. /**
  14444. * @member {Object} target
  14445. * @memberof OpenSeadragon.Spring#
  14446. * @property {Number} value
  14447. * @property {Number} time
  14448. */
  14449. this.target = {
  14450. value: this.current.value,
  14451. time: this.current.time
  14452. };
  14453. if (this._exponential) {
  14454. this.start._logValue = Math.log(this.start.value);
  14455. this.target._logValue = Math.log(this.target.value);
  14456. this.current._logValue = Math.log(this.current.value);
  14457. }
  14458. };
  14459. /** @lends OpenSeadragon.Spring.prototype */
  14460. $.Spring.prototype = {
  14461. /**
  14462. * @function
  14463. * @param {Number} target
  14464. */
  14465. resetTo: function( target ) {
  14466. $.console.assert(!this._exponential || target !== 0,
  14467. "[OpenSeadragon.Spring.resetTo] target must be non-zero for exponential springs");
  14468. this.start.value = this.target.value = this.current.value = target;
  14469. this.start.time = this.target.time = this.current.time = $.now();
  14470. if (this._exponential) {
  14471. this.start._logValue = Math.log(this.start.value);
  14472. this.target._logValue = Math.log(this.target.value);
  14473. this.current._logValue = Math.log(this.current.value);
  14474. }
  14475. },
  14476. /**
  14477. * @function
  14478. * @param {Number} target
  14479. */
  14480. springTo: function( target ) {
  14481. $.console.assert(!this._exponential || target !== 0,
  14482. "[OpenSeadragon.Spring.springTo] target must be non-zero for exponential springs");
  14483. this.start.value = this.current.value;
  14484. this.start.time = this.current.time;
  14485. this.target.value = target;
  14486. this.target.time = this.start.time + 1000 * this.animationTime;
  14487. if (this._exponential) {
  14488. this.start._logValue = Math.log(this.start.value);
  14489. this.target._logValue = Math.log(this.target.value);
  14490. }
  14491. },
  14492. /**
  14493. * @function
  14494. * @param {Number} delta
  14495. */
  14496. shiftBy: function( delta ) {
  14497. this.start.value += delta;
  14498. this.target.value += delta;
  14499. if (this._exponential) {
  14500. $.console.assert(this.target.value !== 0 && this.start.value !== 0,
  14501. "[OpenSeadragon.Spring.shiftBy] spring value must be non-zero for exponential springs");
  14502. this.start._logValue = Math.log(this.start.value);
  14503. this.target._logValue = Math.log(this.target.value);
  14504. }
  14505. },
  14506. setExponential: function(value) {
  14507. this._exponential = value;
  14508. if (this._exponential) {
  14509. $.console.assert(this.current.value !== 0 && this.target.value !== 0 && this.start.value !== 0,
  14510. "[OpenSeadragon.Spring.setExponential] spring value must be non-zero for exponential springs");
  14511. this.start._logValue = Math.log(this.start.value);
  14512. this.target._logValue = Math.log(this.target.value);
  14513. this.current._logValue = Math.log(this.current.value);
  14514. }
  14515. },
  14516. /**
  14517. * @function
  14518. * @returns true if the value got updated, false otherwise
  14519. */
  14520. update: function() {
  14521. this.current.time = $.now();
  14522. var startValue, targetValue;
  14523. if (this._exponential) {
  14524. startValue = this.start._logValue;
  14525. targetValue = this.target._logValue;
  14526. } else {
  14527. startValue = this.start.value;
  14528. targetValue = this.target.value;
  14529. }
  14530. var currentValue = (this.current.time >= this.target.time) ?
  14531. targetValue :
  14532. startValue +
  14533. ( targetValue - startValue ) *
  14534. transform(
  14535. this.springStiffness,
  14536. ( this.current.time - this.start.time ) /
  14537. ( this.target.time - this.start.time )
  14538. );
  14539. var oldValue = this.current.value;
  14540. if (this._exponential) {
  14541. this.current.value = Math.exp(currentValue);
  14542. } else {
  14543. this.current.value = currentValue;
  14544. }
  14545. return oldValue != this.current.value;
  14546. },
  14547. /**
  14548. * Returns whether the spring is at the target value
  14549. * @function
  14550. * @returns {Boolean} True if at target value, false otherwise
  14551. */
  14552. isAtTargetValue: function() {
  14553. return this.current.value === this.target.value;
  14554. }
  14555. };
  14556. /**
  14557. * @private
  14558. */
  14559. function transform( stiffness, x ) {
  14560. return ( 1.0 - Math.exp( stiffness * -x ) ) /
  14561. ( 1.0 - Math.exp( -stiffness ) );
  14562. }
  14563. }( OpenSeadragon ));
  14564. /*
  14565. * OpenSeadragon - ImageLoader
  14566. *
  14567. * Copyright (C) 2009 CodePlex Foundation
  14568. * Copyright (C) 2010-2013 OpenSeadragon contributors
  14569. * Redistribution and use in source and binary forms, with or without
  14570. * modification, are permitted provided that the following conditions are
  14571. * met:
  14572. *
  14573. * - Redistributions of source code must retain the above copyright notice,
  14574. * this list of conditions and the following disclaimer.
  14575. *
  14576. * - Redistributions in binary form must reproduce the above copyright
  14577. * notice, this list of conditions and the following disclaimer in the
  14578. * documentation and/or other materials provided with the distribution.
  14579. *
  14580. * - Neither the name of CodePlex Foundation nor the names of its
  14581. * contributors may be used to endorse or promote products derived from
  14582. * this software without specific prior written permission.
  14583. *
  14584. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  14585. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  14586. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  14587. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  14588. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  14589. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  14590. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  14591. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  14592. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  14593. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  14594. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14595. */
  14596. (function($){
  14597. /**
  14598. * @private
  14599. * @class ImageJob
  14600. * @classdesc Handles downloading of a single image.
  14601. * @param {Object} options - Options for this ImageJob.
  14602. * @param {String} [options.src] - URL of image to download.
  14603. * @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
  14604. * @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX.
  14605. * @param {String} [options.crossOriginPolicy] - CORS policy to use for downloads
  14606. * @param {Function} [options.callback] - Called once image has been downloaded.
  14607. * @param {Function} [options.abort] - Called when this image job is aborted.
  14608. * @param {Number} [options.timeout] - The max number of milliseconds that this image job may take to complete.
  14609. */
  14610. function ImageJob (options) {
  14611. $.extend(true, this, {
  14612. timeout: $.DEFAULT_SETTINGS.timeout,
  14613. jobId: null
  14614. }, options);
  14615. /**
  14616. * Image object which will contain downloaded image.
  14617. * @member {Image} image
  14618. * @memberof OpenSeadragon.ImageJob#
  14619. */
  14620. this.image = null;
  14621. }
  14622. ImageJob.prototype = {
  14623. errorMsg: null,
  14624. /**
  14625. * Starts the image job.
  14626. * @method
  14627. */
  14628. start: function(){
  14629. var self = this;
  14630. var selfAbort = this.abort;
  14631. this.image = new Image();
  14632. this.image.onload = function(){
  14633. self.finish(true);
  14634. };
  14635. this.image.onabort = this.image.onerror = function() {
  14636. self.errorMsg = "Image load aborted";
  14637. self.finish(false);
  14638. };
  14639. this.jobId = window.setTimeout(function(){
  14640. self.errorMsg = "Image load exceeded timeout (" + self.timeout + " ms)";
  14641. self.finish(false);
  14642. }, this.timeout);
  14643. // Load the tile with an AJAX request if the loadWithAjax option is
  14644. // set. Otherwise load the image by setting the source proprety of the image object.
  14645. if (this.loadWithAjax) {
  14646. this.request = $.makeAjaxRequest({
  14647. url: this.src,
  14648. withCredentials: this.ajaxWithCredentials,
  14649. headers: this.ajaxHeaders,
  14650. responseType: "arraybuffer",
  14651. success: function(request) {
  14652. var blb;
  14653. // Make the raw data into a blob.
  14654. // BlobBuilder fallback adapted from
  14655. // http://stackoverflow.com/questions/15293694/blob-constructor-browser-compatibility
  14656. try {
  14657. blb = new window.Blob([request.response]);
  14658. } catch (e) {
  14659. var BlobBuilder = (
  14660. window.BlobBuilder ||
  14661. window.WebKitBlobBuilder ||
  14662. window.MozBlobBuilder ||
  14663. window.MSBlobBuilder
  14664. );
  14665. if (e.name === 'TypeError' && BlobBuilder) {
  14666. var bb = new BlobBuilder();
  14667. bb.append(request.response);
  14668. blb = bb.getBlob();
  14669. }
  14670. }
  14671. // If the blob is empty for some reason consider the image load a failure.
  14672. if (blb.size === 0) {
  14673. self.errorMsg = "Empty image response.";
  14674. self.finish(false);
  14675. }
  14676. // Create a URL for the blob data and make it the source of the image object.
  14677. // This will still trigger Image.onload to indicate a successful tile load.
  14678. var url = (window.URL || window.webkitURL).createObjectURL(blb);
  14679. self.image.src = url;
  14680. },
  14681. error: function(request) {
  14682. self.errorMsg = "Image load aborted - XHR error";
  14683. self.finish(false);
  14684. }
  14685. });
  14686. // Provide a function to properly abort the request.
  14687. this.abort = function() {
  14688. self.request.abort();
  14689. // Call the existing abort function if available
  14690. if (typeof selfAbort === "function") {
  14691. selfAbort();
  14692. }
  14693. };
  14694. } else {
  14695. if (this.crossOriginPolicy !== false) {
  14696. this.image.crossOrigin = this.crossOriginPolicy;
  14697. }
  14698. this.image.src = this.src;
  14699. }
  14700. },
  14701. finish: function(successful) {
  14702. this.image.onload = this.image.onerror = this.image.onabort = null;
  14703. if (!successful) {
  14704. this.image = null;
  14705. }
  14706. if (this.jobId) {
  14707. window.clearTimeout(this.jobId);
  14708. }
  14709. this.callback(this);
  14710. }
  14711. };
  14712. /**
  14713. * @class ImageLoader
  14714. * @memberof OpenSeadragon
  14715. * @classdesc Handles downloading of a set of images using asynchronous queue pattern.
  14716. * You generally won't have to interact with the ImageLoader directly.
  14717. * @param {Object} options - Options for this ImageLoader.
  14718. * @param {Number} [options.jobLimit] - The number of concurrent image requests. See imageLoaderLimit in {@link OpenSeadragon.Options} for details.
  14719. * @param {Number} [options.timeout] - The max number of milliseconds that an image job may take to complete.
  14720. */
  14721. $.ImageLoader = function(options) {
  14722. $.extend(true, this, {
  14723. jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
  14724. timeout: $.DEFAULT_SETTINGS.timeout,
  14725. jobQueue: [],
  14726. jobsInProgress: 0
  14727. }, options);
  14728. };
  14729. /** @lends OpenSeadragon.ImageLoader.prototype */
  14730. $.ImageLoader.prototype = {
  14731. /**
  14732. * Add an unloaded image to the loader queue.
  14733. * @method
  14734. * @param {Object} options - Options for this job.
  14735. * @param {String} [options.src] - URL of image to download.
  14736. * @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
  14737. * @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX.
  14738. * @param {String|Boolean} [options.crossOriginPolicy] - CORS policy to use for downloads
  14739. * @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX
  14740. * requests.
  14741. * @param {Function} [options.callback] - Called once image has been downloaded.
  14742. * @param {Function} [options.abort] - Called when this image job is aborted.
  14743. */
  14744. addJob: function(options) {
  14745. var _this = this,
  14746. complete = function(job) {
  14747. completeJob(_this, job, options.callback);
  14748. },
  14749. jobOptions = {
  14750. src: options.src,
  14751. loadWithAjax: options.loadWithAjax,
  14752. ajaxHeaders: options.loadWithAjax ? options.ajaxHeaders : null,
  14753. crossOriginPolicy: options.crossOriginPolicy,
  14754. ajaxWithCredentials: options.ajaxWithCredentials,
  14755. callback: complete,
  14756. abort: options.abort,
  14757. timeout: this.timeout
  14758. },
  14759. newJob = new ImageJob(jobOptions);
  14760. if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) {
  14761. newJob.start();
  14762. this.jobsInProgress++;
  14763. }
  14764. else {
  14765. this.jobQueue.push( newJob );
  14766. }
  14767. },
  14768. /**
  14769. * Clear any unstarted image loading jobs from the queue.
  14770. * @method
  14771. */
  14772. clear: function() {
  14773. for( var i = 0; i < this.jobQueue.length; i++ ) {
  14774. var job = this.jobQueue[i];
  14775. if ( typeof job.abort === "function" ) {
  14776. job.abort();
  14777. }
  14778. }
  14779. this.jobQueue = [];
  14780. }
  14781. };
  14782. /**
  14783. * Cleans up ImageJob once completed.
  14784. * @method
  14785. * @private
  14786. * @param loader - ImageLoader used to start job.
  14787. * @param job - The ImageJob that has completed.
  14788. * @param callback - Called once cleanup is finished.
  14789. */
  14790. function completeJob(loader, job, callback) {
  14791. var nextJob;
  14792. loader.jobsInProgress--;
  14793. if ((!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
  14794. nextJob = loader.jobQueue.shift();
  14795. nextJob.start();
  14796. loader.jobsInProgress++;
  14797. }
  14798. callback(job.image, job.errorMsg, job.request);
  14799. }
  14800. }(OpenSeadragon));
  14801. /*
  14802. * OpenSeadragon - Tile
  14803. *
  14804. * Copyright (C) 2009 CodePlex Foundation
  14805. * Copyright (C) 2010-2013 OpenSeadragon contributors
  14806. *
  14807. * Redistribution and use in source and binary forms, with or without
  14808. * modification, are permitted provided that the following conditions are
  14809. * met:
  14810. *
  14811. * - Redistributions of source code must retain the above copyright notice,
  14812. * this list of conditions and the following disclaimer.
  14813. *
  14814. * - Redistributions in binary form must reproduce the above copyright
  14815. * notice, this list of conditions and the following disclaimer in the
  14816. * documentation and/or other materials provided with the distribution.
  14817. *
  14818. * - Neither the name of CodePlex Foundation nor the names of its
  14819. * contributors may be used to endorse or promote products derived from
  14820. * this software without specific prior written permission.
  14821. *
  14822. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  14823. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  14824. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  14825. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  14826. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  14827. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  14828. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  14829. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  14830. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  14831. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  14832. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14833. */
  14834. (function( $ ){
  14835. /**
  14836. * @class Tile
  14837. * @memberof OpenSeadragon
  14838. * @param {Number} level The zoom level this tile belongs to.
  14839. * @param {Number} x The vector component 'x'.
  14840. * @param {Number} y The vector component 'y'.
  14841. * @param {OpenSeadragon.Rect} bounds Where this tile fits, in normalized
  14842. * coordinates.
  14843. * @param {Boolean} exists Is this tile a part of a sparse image? ( Also has
  14844. * this tile failed to load? )
  14845. * @param {String} url The URL of this tile's image.
  14846. * @param {CanvasRenderingContext2D} context2D The context2D of this tile if it
  14847. * is provided directly by the tile source.
  14848. * @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request .
  14849. * @param {Object} ajaxHeaders The headers to send with this tile's AJAX request (if applicable).
  14850. * @param {OpenSeadragon.Rect} sourceBounds The portion of the tile to use as the source of the
  14851. * drawing operation, in pixels. Note that this only works when drawing with canvas; when drawing
  14852. * with HTML the entire tile is always used.
  14853. */
  14854. $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders, sourceBounds) {
  14855. /**
  14856. * The zoom level this tile belongs to.
  14857. * @member {Number} level
  14858. * @memberof OpenSeadragon.Tile#
  14859. */
  14860. this.level = level;
  14861. /**
  14862. * The vector component 'x'.
  14863. * @member {Number} x
  14864. * @memberof OpenSeadragon.Tile#
  14865. */
  14866. this.x = x;
  14867. /**
  14868. * The vector component 'y'.
  14869. * @member {Number} y
  14870. * @memberof OpenSeadragon.Tile#
  14871. */
  14872. this.y = y;
  14873. /**
  14874. * Where this tile fits, in normalized coordinates
  14875. * @member {OpenSeadragon.Rect} bounds
  14876. * @memberof OpenSeadragon.Tile#
  14877. */
  14878. this.bounds = bounds;
  14879. /**
  14880. * The portion of the tile to use as the source of the drawing operation, in pixels. Note that
  14881. * this only works when drawing with canvas; when drawing with HTML the entire tile is always used.
  14882. * @member {OpenSeadragon.Rect} sourceBounds
  14883. * @memberof OpenSeadragon.Tile#
  14884. */
  14885. this.sourceBounds = sourceBounds;
  14886. /**
  14887. * Is this tile a part of a sparse image? Also has this tile failed to load?
  14888. * @member {Boolean} exists
  14889. * @memberof OpenSeadragon.Tile#
  14890. */
  14891. this.exists = exists;
  14892. /**
  14893. * The URL of this tile's image.
  14894. * @member {String} url
  14895. * @memberof OpenSeadragon.Tile#
  14896. */
  14897. this.url = url;
  14898. /**
  14899. * The context2D of this tile if it is provided directly by the tile source.
  14900. * @member {CanvasRenderingContext2D} context2D
  14901. * @memberOf OpenSeadragon.Tile#
  14902. */
  14903. this.context2D = context2D;
  14904. /**
  14905. * Whether to load this tile's image with an AJAX request.
  14906. * @member {Boolean} loadWithAjax
  14907. * @memberof OpenSeadragon.Tile#
  14908. */
  14909. this.loadWithAjax = loadWithAjax;
  14910. /**
  14911. * The headers to be used in requesting this tile's image.
  14912. * Only used if loadWithAjax is set to true.
  14913. * @member {Object} ajaxHeaders
  14914. * @memberof OpenSeadragon.Tile#
  14915. */
  14916. this.ajaxHeaders = ajaxHeaders;
  14917. /**
  14918. * The unique cache key for this tile.
  14919. * @member {String} cacheKey
  14920. * @memberof OpenSeadragon.Tile#
  14921. */
  14922. if (this.ajaxHeaders) {
  14923. this.cacheKey = this.url + "+" + JSON.stringify(this.ajaxHeaders);
  14924. } else {
  14925. this.cacheKey = this.url;
  14926. }
  14927. /**
  14928. * Is this tile loaded?
  14929. * @member {Boolean} loaded
  14930. * @memberof OpenSeadragon.Tile#
  14931. */
  14932. this.loaded = false;
  14933. /**
  14934. * Is this tile loading?
  14935. * @member {Boolean} loading
  14936. * @memberof OpenSeadragon.Tile#
  14937. */
  14938. this.loading = false;
  14939. /**
  14940. * The HTML div element for this tile
  14941. * @member {Element} element
  14942. * @memberof OpenSeadragon.Tile#
  14943. */
  14944. this.element = null;
  14945. /**
  14946. * The HTML img element for this tile.
  14947. * @member {Element} imgElement
  14948. * @memberof OpenSeadragon.Tile#
  14949. */
  14950. this.imgElement = null;
  14951. /**
  14952. * The Image object for this tile.
  14953. * @member {Object} image
  14954. * @memberof OpenSeadragon.Tile#
  14955. */
  14956. this.image = null;
  14957. /**
  14958. * The alias of this.element.style.
  14959. * @member {String} style
  14960. * @memberof OpenSeadragon.Tile#
  14961. */
  14962. this.style = null;
  14963. /**
  14964. * This tile's position on screen, in pixels.
  14965. * @member {OpenSeadragon.Point} position
  14966. * @memberof OpenSeadragon.Tile#
  14967. */
  14968. this.position = null;
  14969. /**
  14970. * This tile's size on screen, in pixels.
  14971. * @member {OpenSeadragon.Point} size
  14972. * @memberof OpenSeadragon.Tile#
  14973. */
  14974. this.size = null;
  14975. /**
  14976. * The start time of this tile's blending.
  14977. * @member {Number} blendStart
  14978. * @memberof OpenSeadragon.Tile#
  14979. */
  14980. this.blendStart = null;
  14981. /**
  14982. * The current opacity this tile should be.
  14983. * @member {Number} opacity
  14984. * @memberof OpenSeadragon.Tile#
  14985. */
  14986. this.opacity = null;
  14987. /**
  14988. * The squared distance of this tile to the viewport center.
  14989. * Use for comparing tiles.
  14990. * @private
  14991. * @member {Number} squaredDistance
  14992. * @memberof OpenSeadragon.Tile#
  14993. */
  14994. this.squaredDistance = null;
  14995. /**
  14996. * The visibility score of this tile.
  14997. * @member {Number} visibility
  14998. * @memberof OpenSeadragon.Tile#
  14999. */
  15000. this.visibility = null;
  15001. /**
  15002. * Whether this tile is currently being drawn.
  15003. * @member {Boolean} beingDrawn
  15004. * @memberof OpenSeadragon.Tile#
  15005. */
  15006. this.beingDrawn = false;
  15007. /**
  15008. * Timestamp the tile was last touched.
  15009. * @member {Number} lastTouchTime
  15010. * @memberof OpenSeadragon.Tile#
  15011. */
  15012. this.lastTouchTime = 0;
  15013. /**
  15014. * Whether this tile is in the right-most column for its level.
  15015. * @member {Boolean} isRightMost
  15016. * @memberof OpenSeadragon.Tile#
  15017. */
  15018. this.isRightMost = false;
  15019. /**
  15020. * Whether this tile is in the bottom-most row for its level.
  15021. * @member {Boolean} isBottomMost
  15022. * @memberof OpenSeadragon.Tile#
  15023. */
  15024. this.isBottomMost = false;
  15025. };
  15026. /** @lends OpenSeadragon.Tile.prototype */
  15027. $.Tile.prototype = {
  15028. /**
  15029. * Provides a string representation of this tiles level and (x,y)
  15030. * components.
  15031. * @function
  15032. * @returns {String}
  15033. */
  15034. toString: function() {
  15035. return this.level + "/" + this.x + "_" + this.y;
  15036. },
  15037. // private
  15038. _hasTransparencyChannel: function() {
  15039. return !!this.context2D || this.url.match('.png');
  15040. },
  15041. /**
  15042. * Renders the tile in an html container.
  15043. * @function
  15044. * @param {Element} container
  15045. */
  15046. drawHTML: function( container ) {
  15047. if (!this.cacheImageRecord) {
  15048. $.console.warn(
  15049. '[Tile.drawHTML] attempting to draw tile %s when it\'s not cached',
  15050. this.toString());
  15051. return;
  15052. }
  15053. if ( !this.loaded ) {
  15054. $.console.warn(
  15055. "Attempting to draw tile %s when it's not yet loaded.",
  15056. this.toString()
  15057. );
  15058. return;
  15059. }
  15060. //EXPERIMENTAL - trying to figure out how to scale the container
  15061. // content during animation of the container size.
  15062. if ( !this.element ) {
  15063. this.element = $.makeNeutralElement( "div" );
  15064. this.imgElement = this.cacheImageRecord.getImage().cloneNode();
  15065. this.imgElement.style.msInterpolationMode = "nearest-neighbor";
  15066. this.imgElement.style.width = "100%";
  15067. this.imgElement.style.height = "100%";
  15068. this.style = this.element.style;
  15069. this.style.position = "absolute";
  15070. }
  15071. if ( this.element.parentNode != container ) {
  15072. container.appendChild( this.element );
  15073. }
  15074. if ( this.imgElement.parentNode != this.element ) {
  15075. this.element.appendChild( this.imgElement );
  15076. }
  15077. this.style.top = this.position.y + "px";
  15078. this.style.left = this.position.x + "px";
  15079. this.style.height = this.size.y + "px";
  15080. this.style.width = this.size.x + "px";
  15081. $.setElementOpacity( this.element, this.opacity );
  15082. },
  15083. /**
  15084. * Renders the tile in a canvas-based context.
  15085. * @function
  15086. * @param {Canvas} context
  15087. * @param {Function} drawingHandler - Method for firing the drawing event.
  15088. * drawingHandler({context, tile, rendered})
  15089. * where <code>rendered</code> is the context with the pre-drawn image.
  15090. * @param {Number} [scale=1] - Apply a scale to position and size
  15091. * @param {OpenSeadragon.Point} [translate] - A translation vector
  15092. */
  15093. drawCanvas: function( context, drawingHandler, scale, translate ) {
  15094. var position = this.position.times($.pixelDensityRatio),
  15095. size = this.size.times($.pixelDensityRatio),
  15096. rendered;
  15097. if (!this.context2D && !this.cacheImageRecord) {
  15098. $.console.warn(
  15099. '[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached',
  15100. this.toString());
  15101. return;
  15102. }
  15103. rendered = this.context2D || this.cacheImageRecord.getRenderedContext();
  15104. if ( !this.loaded || !rendered ){
  15105. $.console.warn(
  15106. "Attempting to draw tile %s when it's not yet loaded.",
  15107. this.toString()
  15108. );
  15109. return;
  15110. }
  15111. context.save();
  15112. context.globalAlpha = this.opacity;
  15113. if (typeof scale === 'number' && scale !== 1) {
  15114. // draw tile at a different scale
  15115. position = position.times(scale);
  15116. size = size.times(scale);
  15117. }
  15118. if (translate instanceof $.Point) {
  15119. // shift tile position slightly
  15120. position = position.plus(translate);
  15121. }
  15122. //if we are supposed to be rendering fully opaque rectangle,
  15123. //ie its done fading or fading is turned off, and if we are drawing
  15124. //an image with an alpha channel, then the only way
  15125. //to avoid seeing the tile underneath is to clear the rectangle
  15126. if (context.globalAlpha === 1 && this._hasTransparencyChannel()) {
  15127. //clearing only the inside of the rectangle occupied
  15128. //by the png prevents edge flikering
  15129. context.clearRect(
  15130. position.x,
  15131. position.y,
  15132. size.x,
  15133. size.y
  15134. );
  15135. }
  15136. // This gives the application a chance to make image manipulation
  15137. // changes as we are rendering the image
  15138. drawingHandler({context: context, tile: this, rendered: rendered});
  15139. var sourceWidth, sourceHeight;
  15140. if (this.sourceBounds) {
  15141. sourceWidth = Math.min(this.sourceBounds.width, rendered.canvas.width);
  15142. sourceHeight = Math.min(this.sourceBounds.height, rendered.canvas.height);
  15143. } else {
  15144. sourceWidth = rendered.canvas.width;
  15145. sourceHeight = rendered.canvas.height;
  15146. }
  15147. context.drawImage(
  15148. rendered.canvas,
  15149. 0,
  15150. 0,
  15151. sourceWidth,
  15152. sourceHeight,
  15153. position.x,
  15154. position.y,
  15155. size.x,
  15156. size.y
  15157. );
  15158. context.restore();
  15159. },
  15160. /**
  15161. * Get the ratio between current and original size.
  15162. * @function
  15163. * @return {Float}
  15164. */
  15165. getScaleForEdgeSmoothing: function() {
  15166. var context;
  15167. if (this.cacheImageRecord) {
  15168. context = this.cacheImageRecord.getRenderedContext();
  15169. } else if (this.context2D) {
  15170. context = this.context2D;
  15171. } else {
  15172. $.console.warn(
  15173. '[Tile.drawCanvas] attempting to get tile scale %s when tile\'s not cached',
  15174. this.toString());
  15175. return 1;
  15176. }
  15177. return context.canvas.width / (this.size.x * $.pixelDensityRatio);
  15178. },
  15179. /**
  15180. * Get a translation vector that when applied to the tile position produces integer coordinates.
  15181. * Needed to avoid swimming and twitching.
  15182. * @function
  15183. * @param {Number} [scale=1] - Scale to be applied to position.
  15184. * @return {OpenSeadragon.Point}
  15185. */
  15186. getTranslationForEdgeSmoothing: function(scale, canvasSize, sketchCanvasSize) {
  15187. // The translation vector must have positive values, otherwise the image goes a bit off
  15188. // the sketch canvas to the top and left and we must use negative coordinates to repaint it
  15189. // to the main canvas. In that case, some browsers throw:
  15190. // INDEX_SIZE_ERR: DOM Exception 1: Index or size was negative, or greater than the allowed value.
  15191. var x = Math.max(1, Math.ceil((sketchCanvasSize.x - canvasSize.x) / 2));
  15192. var y = Math.max(1, Math.ceil((sketchCanvasSize.y - canvasSize.y) / 2));
  15193. return new $.Point(x, y).minus(
  15194. this.position
  15195. .times($.pixelDensityRatio)
  15196. .times(scale || 1)
  15197. .apply(function(x) {
  15198. return x % 1;
  15199. })
  15200. );
  15201. },
  15202. /**
  15203. * Removes tile from its container.
  15204. * @function
  15205. */
  15206. unload: function() {
  15207. if ( this.imgElement && this.imgElement.parentNode ) {
  15208. this.imgElement.parentNode.removeChild( this.imgElement );
  15209. }
  15210. if ( this.element && this.element.parentNode ) {
  15211. this.element.parentNode.removeChild( this.element );
  15212. }
  15213. this.element = null;
  15214. this.imgElement = null;
  15215. this.loaded = false;
  15216. this.loading = false;
  15217. }
  15218. };
  15219. }( OpenSeadragon ));
  15220. /*
  15221. * OpenSeadragon - Overlay
  15222. *
  15223. * Copyright (C) 2009 CodePlex Foundation
  15224. * Copyright (C) 2010-2013 OpenSeadragon contributors
  15225. *
  15226. * Redistribution and use in source and binary forms, with or without
  15227. * modification, are permitted provided that the following conditions are
  15228. * met:
  15229. *
  15230. * - Redistributions of source code must retain the above copyright notice,
  15231. * this list of conditions and the following disclaimer.
  15232. *
  15233. * - Redistributions in binary form must reproduce the above copyright
  15234. * notice, this list of conditions and the following disclaimer in the
  15235. * documentation and/or other materials provided with the distribution.
  15236. *
  15237. * - Neither the name of CodePlex Foundation nor the names of its
  15238. * contributors may be used to endorse or promote products derived from
  15239. * this software without specific prior written permission.
  15240. *
  15241. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  15242. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  15243. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  15244. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  15245. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  15246. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  15247. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  15248. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  15249. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  15250. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  15251. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  15252. */
  15253. (function($) {
  15254. /**
  15255. * An enumeration of positions that an overlay may be assigned relative to
  15256. * the viewport.
  15257. * It is identical to OpenSeadragon.Placement but is kept for backward
  15258. * compatibility.
  15259. * @member OverlayPlacement
  15260. * @memberof OpenSeadragon
  15261. * @see OpenSeadragon.Placement
  15262. * @static
  15263. * @readonly
  15264. * @type {Object}
  15265. * @property {Number} CENTER
  15266. * @property {Number} TOP_LEFT
  15267. * @property {Number} TOP
  15268. * @property {Number} TOP_RIGHT
  15269. * @property {Number} RIGHT
  15270. * @property {Number} BOTTOM_RIGHT
  15271. * @property {Number} BOTTOM
  15272. * @property {Number} BOTTOM_LEFT
  15273. * @property {Number} LEFT
  15274. */
  15275. $.OverlayPlacement = $.Placement;
  15276. /**
  15277. * An enumeration of possible ways to handle overlays rotation
  15278. * @member OverlayRotationMode
  15279. * @memberOf OpenSeadragon
  15280. * @static
  15281. * @readonly
  15282. * @property {Number} NO_ROTATION The overlay ignore the viewport rotation.
  15283. * @property {Number} EXACT The overlay use CSS 3 transforms to rotate with
  15284. * the viewport. If the overlay contains text, it will get rotated as well.
  15285. * @property {Number} BOUNDING_BOX The overlay adjusts for rotation by
  15286. * taking the size of the bounding box of the rotated bounds.
  15287. * Only valid for overlays with Rect location and scalable in both directions.
  15288. */
  15289. $.OverlayRotationMode = $.freezeObject({
  15290. NO_ROTATION: 1,
  15291. EXACT: 2,
  15292. BOUNDING_BOX: 3
  15293. });
  15294. /**
  15295. * @class Overlay
  15296. * @classdesc Provides a way to float an HTML element on top of the viewer element.
  15297. *
  15298. * @memberof OpenSeadragon
  15299. * @param {Object} options
  15300. * @param {Element} options.element
  15301. * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The
  15302. * location of the overlay on the image. If a {@link OpenSeadragon.Point}
  15303. * is specified, the overlay will be located at this location with respect
  15304. * to the placement option. If a {@link OpenSeadragon.Rect} is specified,
  15305. * the overlay will be placed at this location with the corresponding width
  15306. * and height and placement TOP_LEFT.
  15307. * @param {OpenSeadragon.Placement} [options.placement=OpenSeadragon.Placement.TOP_LEFT]
  15308. * Defines what part of the overlay should be at the specified options.location
  15309. * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw]
  15310. * @param {Boolean} [options.checkResize=true] Set to false to avoid to
  15311. * check the size of the overlay every time it is drawn in the directions
  15312. * which are not scaled. It will improve performances but will cause a
  15313. * misalignment if the overlay size changes.
  15314. * @param {Number} [options.width] The width of the overlay in viewport
  15315. * coordinates. If specified, the width of the overlay will be adjusted when
  15316. * the zoom changes.
  15317. * @param {Number} [options.height] The height of the overlay in viewport
  15318. * coordinates. If specified, the height of the overlay will be adjusted when
  15319. * the zoom changes.
  15320. * @param {Boolean} [options.rotationMode=OpenSeadragon.OverlayRotationMode.EXACT]
  15321. * How to handle the rotation of the viewport.
  15322. */
  15323. $.Overlay = function(element, location, placement) {
  15324. /**
  15325. * onDraw callback signature used by {@link OpenSeadragon.Overlay}.
  15326. *
  15327. * @callback OnDrawCallback
  15328. * @memberof OpenSeadragon.Overlay
  15329. * @param {OpenSeadragon.Point} position
  15330. * @param {OpenSeadragon.Point} size
  15331. * @param {Element} element
  15332. */
  15333. var options;
  15334. if ($.isPlainObject(element)) {
  15335. options = element;
  15336. } else {
  15337. options = {
  15338. element: element,
  15339. location: location,
  15340. placement: placement
  15341. };
  15342. }
  15343. this.element = options.element;
  15344. this.style = options.element.style;
  15345. this._init(options);
  15346. };
  15347. /** @lends OpenSeadragon.Overlay.prototype */
  15348. $.Overlay.prototype = {
  15349. // private
  15350. _init: function(options) {
  15351. this.location = options.location;
  15352. this.placement = options.placement === undefined ?
  15353. $.Placement.TOP_LEFT : options.placement;
  15354. this.onDraw = options.onDraw;
  15355. this.checkResize = options.checkResize === undefined ?
  15356. true : options.checkResize;
  15357. // When this.width is not null, the overlay get scaled horizontally
  15358. this.width = options.width === undefined ? null : options.width;
  15359. // When this.height is not null, the overlay get scaled vertically
  15360. this.height = options.height === undefined ? null : options.height;
  15361. this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT;
  15362. // Having a rect as location is a syntactic sugar
  15363. if (this.location instanceof $.Rect) {
  15364. this.width = this.location.width;
  15365. this.height = this.location.height;
  15366. this.location = this.location.getTopLeft();
  15367. this.placement = $.Placement.TOP_LEFT;
  15368. }
  15369. // Deprecated properties kept for backward compatibility.
  15370. this.scales = this.width !== null && this.height !== null;
  15371. this.bounds = new $.Rect(
  15372. this.location.x, this.location.y, this.width, this.height);
  15373. this.position = this.location;
  15374. },
  15375. /**
  15376. * Internal function to adjust the position of an overlay
  15377. * depending on it size and placement.
  15378. * @function
  15379. * @param {OpenSeadragon.Point} position
  15380. * @param {OpenSeadragon.Point} size
  15381. */
  15382. adjust: function(position, size) {
  15383. var properties = $.Placement.properties[this.placement];
  15384. if (!properties) {
  15385. return;
  15386. }
  15387. if (properties.isHorizontallyCentered) {
  15388. position.x -= size.x / 2;
  15389. } else if (properties.isRight) {
  15390. position.x -= size.x;
  15391. }
  15392. if (properties.isVerticallyCentered) {
  15393. position.y -= size.y / 2;
  15394. } else if (properties.isBottom) {
  15395. position.y -= size.y;
  15396. }
  15397. },
  15398. /**
  15399. * @function
  15400. */
  15401. destroy: function() {
  15402. var element = this.element;
  15403. var style = this.style;
  15404. if (element.parentNode) {
  15405. element.parentNode.removeChild(element);
  15406. //this should allow us to preserve overlays when required between
  15407. //pages
  15408. if (element.prevElementParent) {
  15409. style.display = 'none';
  15410. //element.prevElementParent.insertBefore(
  15411. // element,
  15412. // element.prevNextSibling
  15413. //);
  15414. document.body.appendChild(element);
  15415. }
  15416. }
  15417. // clear the onDraw callback
  15418. this.onDraw = null;
  15419. style.top = "";
  15420. style.left = "";
  15421. style.position = "";
  15422. if (this.width !== null) {
  15423. style.width = "";
  15424. }
  15425. if (this.height !== null) {
  15426. style.height = "";
  15427. }
  15428. var transformOriginProp = $.getCssPropertyWithVendorPrefix(
  15429. 'transformOrigin');
  15430. var transformProp = $.getCssPropertyWithVendorPrefix(
  15431. 'transform');
  15432. if (transformOriginProp && transformProp) {
  15433. style[transformOriginProp] = "";
  15434. style[transformProp] = "";
  15435. }
  15436. },
  15437. /**
  15438. * @function
  15439. * @param {Element} container
  15440. */
  15441. drawHTML: function(container, viewport) {
  15442. var element = this.element;
  15443. if (element.parentNode !== container) {
  15444. //save the source parent for later if we need it
  15445. element.prevElementParent = element.parentNode;
  15446. element.prevNextSibling = element.nextSibling;
  15447. container.appendChild(element);
  15448. // have to set position before calculating size, fix #1116
  15449. this.style.position = "absolute";
  15450. // this.size is used by overlays which don't get scaled in at
  15451. // least one direction when this.checkResize is set to false.
  15452. this.size = $.getElementSize(element);
  15453. }
  15454. var positionAndSize = this._getOverlayPositionAndSize(viewport);
  15455. var position = positionAndSize.position;
  15456. var size = this.size = positionAndSize.size;
  15457. var rotate = positionAndSize.rotate;
  15458. // call the onDraw callback if it exists to allow one to overwrite
  15459. // the drawing/positioning/sizing of the overlay
  15460. if (this.onDraw) {
  15461. this.onDraw(position, size, this.element);
  15462. } else {
  15463. var style = this.style;
  15464. style.left = position.x + "px";
  15465. style.top = position.y + "px";
  15466. if (this.width !== null) {
  15467. style.width = size.x + "px";
  15468. }
  15469. if (this.height !== null) {
  15470. style.height = size.y + "px";
  15471. }
  15472. var transformOriginProp = $.getCssPropertyWithVendorPrefix(
  15473. 'transformOrigin');
  15474. var transformProp = $.getCssPropertyWithVendorPrefix(
  15475. 'transform');
  15476. if (transformOriginProp && transformProp) {
  15477. if (rotate) {
  15478. style[transformOriginProp] = this._getTransformOrigin();
  15479. style[transformProp] = "rotate(" + rotate + "deg)";
  15480. } else {
  15481. style[transformOriginProp] = "";
  15482. style[transformProp] = "";
  15483. }
  15484. }
  15485. if (style.display !== 'none') {
  15486. style.display = 'block';
  15487. }
  15488. }
  15489. },
  15490. // private
  15491. _getOverlayPositionAndSize: function(viewport) {
  15492. var position = viewport.pixelFromPoint(this.location, true);
  15493. var size = this._getSizeInPixels(viewport);
  15494. this.adjust(position, size);
  15495. var rotate = 0;
  15496. if (viewport.degrees &&
  15497. this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) {
  15498. // BOUNDING_BOX is only valid if both directions get scaled.
  15499. // Get replaced by EXACT otherwise.
  15500. if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX &&
  15501. this.width !== null && this.height !== null) {
  15502. var rect = new $.Rect(position.x, position.y, size.x, size.y);
  15503. var boundingBox = this._getBoundingBox(rect, viewport.degrees);
  15504. position = boundingBox.getTopLeft();
  15505. size = boundingBox.getSize();
  15506. } else {
  15507. rotate = viewport.degrees;
  15508. }
  15509. }
  15510. return {
  15511. position: position,
  15512. size: size,
  15513. rotate: rotate
  15514. };
  15515. },
  15516. // private
  15517. _getSizeInPixels: function(viewport) {
  15518. var width = this.size.x;
  15519. var height = this.size.y;
  15520. if (this.width !== null || this.height !== null) {
  15521. var scaledSize = viewport.deltaPixelsFromPointsNoRotate(
  15522. new $.Point(this.width || 0, this.height || 0), true);
  15523. if (this.width !== null) {
  15524. width = scaledSize.x;
  15525. }
  15526. if (this.height !== null) {
  15527. height = scaledSize.y;
  15528. }
  15529. }
  15530. if (this.checkResize &&
  15531. (this.width === null || this.height === null)) {
  15532. var eltSize = this.size = $.getElementSize(this.element);
  15533. if (this.width === null) {
  15534. width = eltSize.x;
  15535. }
  15536. if (this.height === null) {
  15537. height = eltSize.y;
  15538. }
  15539. }
  15540. return new $.Point(width, height);
  15541. },
  15542. // private
  15543. _getBoundingBox: function(rect, degrees) {
  15544. var refPoint = this._getPlacementPoint(rect);
  15545. return rect.rotate(degrees, refPoint).getBoundingBox();
  15546. },
  15547. // private
  15548. _getPlacementPoint: function(rect) {
  15549. var result = new $.Point(rect.x, rect.y);
  15550. var properties = $.Placement.properties[this.placement];
  15551. if (properties) {
  15552. if (properties.isHorizontallyCentered) {
  15553. result.x += rect.width / 2;
  15554. } else if (properties.isRight) {
  15555. result.x += rect.width;
  15556. }
  15557. if (properties.isVerticallyCentered) {
  15558. result.y += rect.height / 2;
  15559. } else if (properties.isBottom) {
  15560. result.y += rect.height;
  15561. }
  15562. }
  15563. return result;
  15564. },
  15565. // private
  15566. _getTransformOrigin: function() {
  15567. var result = "";
  15568. var properties = $.Placement.properties[this.placement];
  15569. if (!properties) {
  15570. return result;
  15571. }
  15572. if (properties.isLeft) {
  15573. result = "left";
  15574. } else if (properties.isRight) {
  15575. result = "right";
  15576. }
  15577. if (properties.isTop) {
  15578. result += " top";
  15579. } else if (properties.isBottom) {
  15580. result += " bottom";
  15581. }
  15582. return result;
  15583. },
  15584. /**
  15585. * Changes the overlay settings.
  15586. * @function
  15587. * @param {OpenSeadragon.Point|OpenSeadragon.Rect|Object} location
  15588. * If an object is specified, the options are the same than the constructor
  15589. * except for the element which can not be changed.
  15590. * @param {OpenSeadragon.Placement} placement
  15591. */
  15592. update: function(location, placement) {
  15593. var options = $.isPlainObject(location) ? location : {
  15594. location: location,
  15595. placement: placement
  15596. };
  15597. this._init({
  15598. location: options.location || this.location,
  15599. placement: options.placement !== undefined ?
  15600. options.placement : this.placement,
  15601. onDraw: options.onDraw || this.onDraw,
  15602. checkResize: options.checkResize || this.checkResize,
  15603. width: options.width !== undefined ? options.width : this.width,
  15604. height: options.height !== undefined ? options.height : this.height,
  15605. rotationMode: options.rotationMode || this.rotationMode
  15606. });
  15607. },
  15608. /**
  15609. * Returns the current bounds of the overlay in viewport coordinates
  15610. * @function
  15611. * @param {OpenSeadragon.Viewport} viewport the viewport
  15612. * @returns {OpenSeadragon.Rect} overlay bounds
  15613. */
  15614. getBounds: function(viewport) {
  15615. $.console.assert(viewport,
  15616. 'A viewport must now be passed to Overlay.getBounds.');
  15617. var width = this.width;
  15618. var height = this.height;
  15619. if (width === null || height === null) {
  15620. var size = viewport.deltaPointsFromPixelsNoRotate(this.size, true);
  15621. if (width === null) {
  15622. width = size.x;
  15623. }
  15624. if (height === null) {
  15625. height = size.y;
  15626. }
  15627. }
  15628. var location = this.location.clone();
  15629. this.adjust(location, new $.Point(width, height));
  15630. return this._adjustBoundsForRotation(
  15631. viewport, new $.Rect(location.x, location.y, width, height));
  15632. },
  15633. // private
  15634. _adjustBoundsForRotation: function(viewport, bounds) {
  15635. if (!viewport ||
  15636. viewport.degrees === 0 ||
  15637. this.rotationMode === $.OverlayRotationMode.EXACT) {
  15638. return bounds;
  15639. }
  15640. if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX) {
  15641. // If overlay not fully scalable, BOUNDING_BOX falls back to EXACT
  15642. if (this.width === null || this.height === null) {
  15643. return bounds;
  15644. }
  15645. // It is easier to just compute the position and size and
  15646. // convert to viewport coordinates.
  15647. var positionAndSize = this._getOverlayPositionAndSize(viewport);
  15648. return viewport.viewerElementToViewportRectangle(new $.Rect(
  15649. positionAndSize.position.x,
  15650. positionAndSize.position.y,
  15651. positionAndSize.size.x,
  15652. positionAndSize.size.y));
  15653. }
  15654. // NO_ROTATION case
  15655. return bounds.rotate(-viewport.degrees,
  15656. this._getPlacementPoint(bounds));
  15657. }
  15658. };
  15659. }(OpenSeadragon));
  15660. /*
  15661. * OpenSeadragon - Drawer
  15662. *
  15663. * Copyright (C) 2009 CodePlex Foundation
  15664. * Copyright (C) 2010-2013 OpenSeadragon contributors
  15665. *
  15666. * Redistribution and use in source and binary forms, with or without
  15667. * modification, are permitted provided that the following conditions are
  15668. * met:
  15669. *
  15670. * - Redistributions of source code must retain the above copyright notice,
  15671. * this list of conditions and the following disclaimer.
  15672. *
  15673. * - Redistributions in binary form must reproduce the above copyright
  15674. * notice, this list of conditions and the following disclaimer in the
  15675. * documentation and/or other materials provided with the distribution.
  15676. *
  15677. * - Neither the name of CodePlex Foundation nor the names of its
  15678. * contributors may be used to endorse or promote products derived from
  15679. * this software without specific prior written permission.
  15680. *
  15681. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  15682. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  15683. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  15684. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  15685. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  15686. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  15687. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  15688. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  15689. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  15690. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  15691. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  15692. */
  15693. (function( $ ){
  15694. /**
  15695. * @class Drawer
  15696. * @memberof OpenSeadragon
  15697. * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
  15698. * @param {Object} options - Options for this Drawer.
  15699. * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
  15700. * @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
  15701. * @param {Element} options.element - Parent element.
  15702. * @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
  15703. */
  15704. $.Drawer = function( options ) {
  15705. $.console.assert( options.viewer, "[Drawer] options.viewer is required" );
  15706. //backward compatibility for positional args while preferring more
  15707. //idiomatic javascript options object as the only argument
  15708. var args = arguments;
  15709. if( !$.isPlainObject( options ) ){
  15710. options = {
  15711. source: args[ 0 ], // Reference to Viewer tile source.
  15712. viewport: args[ 1 ], // Reference to Viewer viewport.
  15713. element: args[ 2 ] // Parent element.
  15714. };
  15715. }
  15716. $.console.assert( options.viewport, "[Drawer] options.viewport is required" );
  15717. $.console.assert( options.element, "[Drawer] options.element is required" );
  15718. if ( options.source ) {
  15719. $.console.error( "[Drawer] options.source is no longer accepted; use TiledImage instead" );
  15720. }
  15721. this.viewer = options.viewer;
  15722. this.viewport = options.viewport;
  15723. this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
  15724. if (options.opacity) {
  15725. $.console.error( "[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead" );
  15726. }
  15727. this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );
  15728. /**
  15729. * The parent element of this Drawer instance, passed in when the Drawer was created.
  15730. * The parent of {@link OpenSeadragon.Drawer#canvas}.
  15731. * @member {Element} container
  15732. * @memberof OpenSeadragon.Drawer#
  15733. */
  15734. this.container = $.getElement( options.element );
  15735. /**
  15736. * A &lt;canvas&gt; element if the browser supports them, otherwise a &lt;div&gt; element.
  15737. * Child element of {@link OpenSeadragon.Drawer#container}.
  15738. * @member {Element} canvas
  15739. * @memberof OpenSeadragon.Drawer#
  15740. */
  15741. this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" );
  15742. /**
  15743. * 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a &lt;canvas&gt; element, otherwise null.
  15744. * @member {Object} context
  15745. * @memberof OpenSeadragon.Drawer#
  15746. */
  15747. this.context = this.useCanvas ? this.canvas.getContext( "2d" ) : null;
  15748. /**
  15749. * Sketch canvas used to temporarily draw tiles which cannot be drawn directly
  15750. * to the main canvas due to opacity. Lazily initialized.
  15751. */
  15752. this.sketchCanvas = null;
  15753. this.sketchContext = null;
  15754. /**
  15755. * @member {Element} element
  15756. * @memberof OpenSeadragon.Drawer#
  15757. * @deprecated Alias for {@link OpenSeadragon.Drawer#container}.
  15758. */
  15759. this.element = this.container;
  15760. // We force our container to ltr because our drawing math doesn't work in rtl.
  15761. // This issue only affects our canvas renderer, but we do it always for consistency.
  15762. // Note that this means overlays you want to be rtl need to be explicitly set to rtl.
  15763. this.container.dir = 'ltr';
  15764. // check canvas available width and height, set canvas width and height such that the canvas backing store is set to the proper pixel density
  15765. if (this.useCanvas) {
  15766. var viewportSize = this._calculateCanvasSize();
  15767. this.canvas.width = viewportSize.x;
  15768. this.canvas.height = viewportSize.y;
  15769. }
  15770. this.canvas.style.width = "100%";
  15771. this.canvas.style.height = "100%";
  15772. this.canvas.style.position = "absolute";
  15773. $.setElementOpacity( this.canvas, this.opacity, true );
  15774. // explicit left-align
  15775. this.container.style.textAlign = "left";
  15776. this.container.appendChild( this.canvas );
  15777. // Image smoothing for canvas rendering (only if canvas is used).
  15778. // Canvas default is "true", so this will only be changed if user specified "false".
  15779. this._imageSmoothingEnabled = true;
  15780. };
  15781. /** @lends OpenSeadragon.Drawer.prototype */
  15782. $.Drawer.prototype = {
  15783. // deprecated
  15784. addOverlay: function( element, location, placement, onDraw ) {
  15785. $.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead.");
  15786. this.viewer.addOverlay( element, location, placement, onDraw );
  15787. return this;
  15788. },
  15789. // deprecated
  15790. updateOverlay: function( element, location, placement ) {
  15791. $.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead.");
  15792. this.viewer.updateOverlay( element, location, placement );
  15793. return this;
  15794. },
  15795. // deprecated
  15796. removeOverlay: function( element ) {
  15797. $.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead.");
  15798. this.viewer.removeOverlay( element );
  15799. return this;
  15800. },
  15801. // deprecated
  15802. clearOverlays: function() {
  15803. $.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead.");
  15804. this.viewer.clearOverlays();
  15805. return this;
  15806. },
  15807. /**
  15808. * Set the opacity of the drawer.
  15809. * @param {Number} opacity
  15810. * @return {OpenSeadragon.Drawer} Chainable.
  15811. */
  15812. setOpacity: function( opacity ) {
  15813. $.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.");
  15814. var world = this.viewer.world;
  15815. for (var i = 0; i < world.getItemCount(); i++) {
  15816. world.getItemAt( i ).setOpacity( opacity );
  15817. }
  15818. return this;
  15819. },
  15820. /**
  15821. * Get the opacity of the drawer.
  15822. * @returns {Number}
  15823. */
  15824. getOpacity: function() {
  15825. $.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.");
  15826. var world = this.viewer.world;
  15827. var maxOpacity = 0;
  15828. for (var i = 0; i < world.getItemCount(); i++) {
  15829. var opacity = world.getItemAt( i ).getOpacity();
  15830. if ( opacity > maxOpacity ) {
  15831. maxOpacity = opacity;
  15832. }
  15833. }
  15834. return maxOpacity;
  15835. },
  15836. // deprecated
  15837. needsUpdate: function() {
  15838. $.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead." );
  15839. return this.viewer.world.needsDraw();
  15840. },
  15841. // deprecated
  15842. numTilesLoaded: function() {
  15843. $.console.error( "[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead." );
  15844. return this.viewer.tileCache.numTilesLoaded();
  15845. },
  15846. // deprecated
  15847. reset: function() {
  15848. $.console.error( "[Drawer.reset] this function is deprecated. Use World.resetItems instead." );
  15849. this.viewer.world.resetItems();
  15850. return this;
  15851. },
  15852. // deprecated
  15853. update: function() {
  15854. $.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead." );
  15855. this.clear();
  15856. this.viewer.world.draw();
  15857. return this;
  15858. },
  15859. /**
  15860. * @return {Boolean} True if rotation is supported.
  15861. */
  15862. canRotate: function() {
  15863. return this.useCanvas;
  15864. },
  15865. /**
  15866. * Destroy the drawer (unload current loaded tiles)
  15867. */
  15868. destroy: function() {
  15869. //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)
  15870. this.canvas.width = 1;
  15871. this.canvas.height = 1;
  15872. this.sketchCanvas = null;
  15873. this.sketchContext = null;
  15874. },
  15875. /**
  15876. * Clears the Drawer so it's ready to draw another frame.
  15877. */
  15878. clear: function() {
  15879. this.canvas.innerHTML = "";
  15880. if ( this.useCanvas ) {
  15881. var viewportSize = this._calculateCanvasSize();
  15882. if( this.canvas.width != viewportSize.x ||
  15883. this.canvas.height != viewportSize.y ) {
  15884. this.canvas.width = viewportSize.x;
  15885. this.canvas.height = viewportSize.y;
  15886. this._updateImageSmoothingEnabled(this.context);
  15887. if ( this.sketchCanvas !== null ) {
  15888. var sketchCanvasSize = this._calculateSketchCanvasSize();
  15889. this.sketchCanvas.width = sketchCanvasSize.x;
  15890. this.sketchCanvas.height = sketchCanvasSize.y;
  15891. this._updateImageSmoothingEnabled(this.sketchContext);
  15892. }
  15893. }
  15894. this._clear();
  15895. }
  15896. },
  15897. _clear: function (useSketch, bounds) {
  15898. if (!this.useCanvas) {
  15899. return;
  15900. }
  15901. var context = this._getContext(useSketch);
  15902. if (bounds) {
  15903. context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height);
  15904. } else {
  15905. var canvas = context.canvas;
  15906. context.clearRect(0, 0, canvas.width, canvas.height);
  15907. }
  15908. },
  15909. /**
  15910. * Scale from OpenSeadragon viewer rectangle to drawer rectangle
  15911. * (ignoring rotation)
  15912. * @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
  15913. * @return {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
  15914. */
  15915. viewportToDrawerRectangle: function(rectangle) {
  15916. var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);
  15917. var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);
  15918. return new $.Rect(
  15919. topLeft.x * $.pixelDensityRatio,
  15920. topLeft.y * $.pixelDensityRatio,
  15921. size.x * $.pixelDensityRatio,
  15922. size.y * $.pixelDensityRatio
  15923. );
  15924. },
  15925. /**
  15926. * Draws the given tile.
  15927. * @param {OpenSeadragon.Tile} tile - The tile to draw.
  15928. * @param {Function} drawingHandler - Method for firing the drawing event if using canvas.
  15929. * drawingHandler({context, tile, rendered})
  15930. * @param {Boolean} useSketch - Whether to use the sketch canvas or not.
  15931. * where <code>rendered</code> is the context with the pre-drawn image.
  15932. * @param {Float} [scale=1] - Apply a scale to tile position and size. Defaults to 1.
  15933. * @param {OpenSeadragon.Point} [translate] A translation vector to offset tile position
  15934. */
  15935. drawTile: function(tile, drawingHandler, useSketch, scale, translate) {
  15936. $.console.assert(tile, '[Drawer.drawTile] tile is required');
  15937. $.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');
  15938. if (this.useCanvas) {
  15939. var context = this._getContext(useSketch);
  15940. scale = scale || 1;
  15941. tile.drawCanvas(context, drawingHandler, scale, translate);
  15942. } else {
  15943. tile.drawHTML( this.canvas );
  15944. }
  15945. },
  15946. _getContext: function( useSketch ) {
  15947. var context = this.context;
  15948. if ( useSketch ) {
  15949. if (this.sketchCanvas === null) {
  15950. this.sketchCanvas = document.createElement( "canvas" );
  15951. var sketchCanvasSize = this._calculateSketchCanvasSize();
  15952. this.sketchCanvas.width = sketchCanvasSize.x;
  15953. this.sketchCanvas.height = sketchCanvasSize.y;
  15954. this.sketchContext = this.sketchCanvas.getContext( "2d" );
  15955. // If the viewport is not currently rotated, the sketchCanvas
  15956. // will have the same size as the main canvas. However, if
  15957. // the viewport get rotated later on, we will need to resize it.
  15958. if (this.viewport.getRotation() === 0) {
  15959. var self = this;
  15960. this.viewer.addHandler('rotate', function resizeSketchCanvas() {
  15961. if (self.viewport.getRotation() === 0) {
  15962. return;
  15963. }
  15964. self.viewer.removeHandler('rotate', resizeSketchCanvas);
  15965. var sketchCanvasSize = self._calculateSketchCanvasSize();
  15966. self.sketchCanvas.width = sketchCanvasSize.x;
  15967. self.sketchCanvas.height = sketchCanvasSize.y;
  15968. });
  15969. }
  15970. this._updateImageSmoothingEnabled(this.sketchContext);
  15971. }
  15972. context = this.sketchContext;
  15973. }
  15974. return context;
  15975. },
  15976. // private
  15977. saveContext: function( useSketch ) {
  15978. if (!this.useCanvas) {
  15979. return;
  15980. }
  15981. this._getContext( useSketch ).save();
  15982. },
  15983. // private
  15984. restoreContext: function( useSketch ) {
  15985. if (!this.useCanvas) {
  15986. return;
  15987. }
  15988. this._getContext( useSketch ).restore();
  15989. },
  15990. // private
  15991. setClip: function(rect, useSketch) {
  15992. if (!this.useCanvas) {
  15993. return;
  15994. }
  15995. var context = this._getContext( useSketch );
  15996. context.beginPath();
  15997. context.rect(rect.x, rect.y, rect.width, rect.height);
  15998. context.clip();
  15999. },
  16000. // private
  16001. drawRectangle: function(rect, fillStyle, useSketch) {
  16002. if (!this.useCanvas) {
  16003. return;
  16004. }
  16005. var context = this._getContext( useSketch );
  16006. context.save();
  16007. context.fillStyle = fillStyle;
  16008. context.fillRect(rect.x, rect.y, rect.width, rect.height);
  16009. context.restore();
  16010. },
  16011. /**
  16012. * Blends the sketch canvas in the main canvas.
  16013. * @param {Object} options The options
  16014. * @param {Float} options.opacity The opacity of the blending.
  16015. * @param {Float} [options.scale=1] The scale at which tiles were drawn on
  16016. * the sketch. Default is 1.
  16017. * Use scale to draw at a lower scale and then enlarge onto the main canvas.
  16018. * @param {OpenSeadragon.Point} [options.translate] A translation vector
  16019. * that was used to draw the tiles
  16020. * @param {String} [options.compositeOperation] - How the image is
  16021. * composited onto other images; see compositeOperation in
  16022. * {@link OpenSeadragon.Options} for possible values.
  16023. * @param {OpenSeadragon.Rect} [options.bounds] The part of the sketch
  16024. * canvas to blend in the main canvas. If specified, options.scale and
  16025. * options.translate get ignored.
  16026. */
  16027. blendSketch: function(opacity, scale, translate, compositeOperation) {
  16028. var options = opacity;
  16029. if (!$.isPlainObject(options)) {
  16030. options = {
  16031. opacity: opacity,
  16032. scale: scale,
  16033. translate: translate,
  16034. compositeOperation: compositeOperation
  16035. };
  16036. }
  16037. if (!this.useCanvas || !this.sketchCanvas) {
  16038. return;
  16039. }
  16040. opacity = options.opacity;
  16041. compositeOperation = options.compositeOperation;
  16042. var bounds = options.bounds;
  16043. this.context.save();
  16044. this.context.globalAlpha = opacity;
  16045. if (compositeOperation) {
  16046. this.context.globalCompositeOperation = compositeOperation;
  16047. }
  16048. if (bounds) {
  16049. // Internet Explorer, Microsoft Edge, and Safari have problems
  16050. // when you call context.drawImage with negative x or y
  16051. // or x + width or y + height greater than the canvas width or height respectively.
  16052. if (bounds.x < 0) {
  16053. bounds.width += bounds.x;
  16054. bounds.x = 0;
  16055. }
  16056. if (bounds.x + bounds.width > this.canvas.width) {
  16057. bounds.width = this.canvas.width - bounds.x;
  16058. }
  16059. if (bounds.y < 0) {
  16060. bounds.height += bounds.y;
  16061. bounds.y = 0;
  16062. }
  16063. if (bounds.y + bounds.height > this.canvas.height) {
  16064. bounds.height = this.canvas.height - bounds.y;
  16065. }
  16066. this.context.drawImage(
  16067. this.sketchCanvas,
  16068. bounds.x,
  16069. bounds.y,
  16070. bounds.width,
  16071. bounds.height,
  16072. bounds.x,
  16073. bounds.y,
  16074. bounds.width,
  16075. bounds.height
  16076. );
  16077. } else {
  16078. scale = options.scale || 1;
  16079. translate = options.translate;
  16080. var position = translate instanceof $.Point ?
  16081. translate : new $.Point(0, 0);
  16082. var widthExt = 0;
  16083. var heightExt = 0;
  16084. if (translate) {
  16085. var widthDiff = this.sketchCanvas.width - this.canvas.width;
  16086. var heightDiff = this.sketchCanvas.height - this.canvas.height;
  16087. widthExt = Math.round(widthDiff / 2);
  16088. heightExt = Math.round(heightDiff / 2);
  16089. }
  16090. this.context.drawImage(
  16091. this.sketchCanvas,
  16092. position.x - widthExt * scale,
  16093. position.y - heightExt * scale,
  16094. (this.canvas.width + 2 * widthExt) * scale,
  16095. (this.canvas.height + 2 * heightExt) * scale,
  16096. -widthExt,
  16097. -heightExt,
  16098. this.canvas.width + 2 * widthExt,
  16099. this.canvas.height + 2 * heightExt
  16100. );
  16101. }
  16102. this.context.restore();
  16103. },
  16104. // private
  16105. drawDebugInfo: function(tile, count, i, tiledImage) {
  16106. if ( !this.useCanvas ) {
  16107. return;
  16108. }
  16109. var colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;
  16110. var context = this.context;
  16111. context.save();
  16112. context.lineWidth = 2 * $.pixelDensityRatio;
  16113. context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
  16114. context.strokeStyle = this.debugGridColor[colorIndex];
  16115. context.fillStyle = this.debugGridColor[colorIndex];
  16116. if ( this.viewport.degrees !== 0 ) {
  16117. this._offsetForRotation({degrees: this.viewport.degrees});
  16118. }
  16119. if (tiledImage.getRotation(true) % 360 !== 0) {
  16120. this._offsetForRotation({
  16121. degrees: tiledImage.getRotation(true),
  16122. point: tiledImage.viewport.pixelFromPointNoRotate(
  16123. tiledImage._getRotationPoint(true), true)
  16124. });
  16125. }
  16126. if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){
  16127. if(tiledImage._drawer.viewer.viewport.getFlip()) {
  16128. tiledImage._drawer._flip();
  16129. }
  16130. }
  16131. context.strokeRect(
  16132. tile.position.x * $.pixelDensityRatio,
  16133. tile.position.y * $.pixelDensityRatio,
  16134. tile.size.x * $.pixelDensityRatio,
  16135. tile.size.y * $.pixelDensityRatio
  16136. );
  16137. var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;
  16138. var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;
  16139. // Rotate the text the right way around.
  16140. context.translate( tileCenterX, tileCenterY );
  16141. context.rotate( Math.PI / 180 * -this.viewport.degrees );
  16142. context.translate( -tileCenterX, -tileCenterY );
  16143. if( tile.x === 0 && tile.y === 0 ){
  16144. context.fillText(
  16145. "Zoom: " + this.viewport.getZoom(),
  16146. tile.position.x * $.pixelDensityRatio,
  16147. (tile.position.y - 30) * $.pixelDensityRatio
  16148. );
  16149. context.fillText(
  16150. "Pan: " + this.viewport.getBounds().toString(),
  16151. tile.position.x * $.pixelDensityRatio,
  16152. (tile.position.y - 20) * $.pixelDensityRatio
  16153. );
  16154. }
  16155. context.fillText(
  16156. "Level: " + tile.level,
  16157. (tile.position.x + 10) * $.pixelDensityRatio,
  16158. (tile.position.y + 20) * $.pixelDensityRatio
  16159. );
  16160. context.fillText(
  16161. "Column: " + tile.x,
  16162. (tile.position.x + 10) * $.pixelDensityRatio,
  16163. (tile.position.y + 30) * $.pixelDensityRatio
  16164. );
  16165. context.fillText(
  16166. "Row: " + tile.y,
  16167. (tile.position.x + 10) * $.pixelDensityRatio,
  16168. (tile.position.y + 40) * $.pixelDensityRatio
  16169. );
  16170. context.fillText(
  16171. "Order: " + i + " of " + count,
  16172. (tile.position.x + 10) * $.pixelDensityRatio,
  16173. (tile.position.y + 50) * $.pixelDensityRatio
  16174. );
  16175. context.fillText(
  16176. "Size: " + tile.size.toString(),
  16177. (tile.position.x + 10) * $.pixelDensityRatio,
  16178. (tile.position.y + 60) * $.pixelDensityRatio
  16179. );
  16180. context.fillText(
  16181. "Position: " + tile.position.toString(),
  16182. (tile.position.x + 10) * $.pixelDensityRatio,
  16183. (tile.position.y + 70) * $.pixelDensityRatio
  16184. );
  16185. if ( this.viewport.degrees !== 0 ) {
  16186. this._restoreRotationChanges();
  16187. }
  16188. if (tiledImage.getRotation(true) % 360 !== 0) {
  16189. this._restoreRotationChanges();
  16190. }
  16191. if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){
  16192. if(tiledImage._drawer.viewer.viewport.getFlip()) {
  16193. tiledImage._drawer._flip();
  16194. }
  16195. }
  16196. context.restore();
  16197. },
  16198. // private
  16199. debugRect: function(rect) {
  16200. if ( this.useCanvas ) {
  16201. var context = this.context;
  16202. context.save();
  16203. context.lineWidth = 2 * $.pixelDensityRatio;
  16204. context.strokeStyle = this.debugGridColor[0];
  16205. context.fillStyle = this.debugGridColor[0];
  16206. context.strokeRect(
  16207. rect.x * $.pixelDensityRatio,
  16208. rect.y * $.pixelDensityRatio,
  16209. rect.width * $.pixelDensityRatio,
  16210. rect.height * $.pixelDensityRatio
  16211. );
  16212. context.restore();
  16213. }
  16214. },
  16215. /**
  16216. * Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property.
  16217. *
  16218. * @function
  16219. * @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
  16220. * drawn smoothly on the canvas; see imageSmoothingEnabled in
  16221. * {@link OpenSeadragon.Options} for more explanation.
  16222. */
  16223. setImageSmoothingEnabled: function(imageSmoothingEnabled){
  16224. if ( this.useCanvas ) {
  16225. this._imageSmoothingEnabled = imageSmoothingEnabled;
  16226. this._updateImageSmoothingEnabled(this.context);
  16227. this.viewer.forceRedraw();
  16228. }
  16229. },
  16230. // private
  16231. _updateImageSmoothingEnabled: function(context){
  16232. context.mozImageSmoothingEnabled = this._imageSmoothingEnabled;
  16233. context.webkitImageSmoothingEnabled = this._imageSmoothingEnabled;
  16234. context.msImageSmoothingEnabled = this._imageSmoothingEnabled;
  16235. context.imageSmoothingEnabled = this._imageSmoothingEnabled;
  16236. },
  16237. /**
  16238. * Get the canvas size
  16239. * @param {Boolean} sketch If set to true return the size of the sketch canvas
  16240. * @returns {OpenSeadragon.Point} The size of the canvas
  16241. */
  16242. getCanvasSize: function(sketch) {
  16243. var canvas = this._getContext(sketch).canvas;
  16244. return new $.Point(canvas.width, canvas.height);
  16245. },
  16246. getCanvasCenter: function() {
  16247. return new $.Point(this.canvas.width / 2, this.canvas.height / 2);
  16248. },
  16249. // private
  16250. _offsetForRotation: function(options) {
  16251. var point = options.point ?
  16252. options.point.times($.pixelDensityRatio) :
  16253. this.getCanvasCenter();
  16254. var context = this._getContext(options.useSketch);
  16255. context.save();
  16256. context.translate(point.x, point.y);
  16257. if(this.viewer.viewport.flipped){
  16258. context.rotate(Math.PI / 180 * -options.degrees);
  16259. context.scale(-1, 1);
  16260. } else{
  16261. context.rotate(Math.PI / 180 * options.degrees);
  16262. }
  16263. context.translate(-point.x, -point.y);
  16264. },
  16265. // private
  16266. _flip: function(options) {
  16267. options = options || {};
  16268. var point = options.point ?
  16269. options.point.times($.pixelDensityRatio) :
  16270. this.getCanvasCenter();
  16271. var context = this._getContext(options.useSketch);
  16272. context.translate(point.x, 0);
  16273. context.scale(-1, 1);
  16274. context.translate(-point.x, 0);
  16275. },
  16276. // private
  16277. _restoreRotationChanges: function(useSketch) {
  16278. var context = this._getContext(useSketch);
  16279. context.restore();
  16280. },
  16281. // private
  16282. _calculateCanvasSize: function() {
  16283. var pixelDensityRatio = $.pixelDensityRatio;
  16284. var viewportSize = this.viewport.getContainerSize();
  16285. return {
  16286. // canvas width and height are integers
  16287. x: Math.round(viewportSize.x * pixelDensityRatio),
  16288. y: Math.round(viewportSize.y * pixelDensityRatio)
  16289. };
  16290. },
  16291. // private
  16292. _calculateSketchCanvasSize: function() {
  16293. var canvasSize = this._calculateCanvasSize();
  16294. if (this.viewport.getRotation() === 0) {
  16295. return canvasSize;
  16296. }
  16297. // If the viewport is rotated, we need a larger sketch canvas in order
  16298. // to support edge smoothing.
  16299. var sketchCanvasSize = Math.ceil(Math.sqrt(
  16300. canvasSize.x * canvasSize.x +
  16301. canvasSize.y * canvasSize.y));
  16302. return {
  16303. x: sketchCanvasSize,
  16304. y: sketchCanvasSize
  16305. };
  16306. }
  16307. };
  16308. }( OpenSeadragon ));
  16309. /*
  16310. * OpenSeadragon - Viewport
  16311. *
  16312. * Copyright (C) 2009 CodePlex Foundation
  16313. * Copyright (C) 2010-2013 OpenSeadragon contributors
  16314. *
  16315. * Redistribution and use in source and binary forms, with or without
  16316. * modification, are permitted provided that the following conditions are
  16317. * met:
  16318. *
  16319. * - Redistributions of source code must retain the above copyright notice,
  16320. * this list of conditions and the following disclaimer.
  16321. *
  16322. * - Redistributions in binary form must reproduce the above copyright
  16323. * notice, this list of conditions and the following disclaimer in the
  16324. * documentation and/or other materials provided with the distribution.
  16325. *
  16326. * - Neither the name of CodePlex Foundation nor the names of its
  16327. * contributors may be used to endorse or promote products derived from
  16328. * this software without specific prior written permission.
  16329. *
  16330. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  16331. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  16332. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  16333. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  16334. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  16335. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  16336. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  16337. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  16338. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  16339. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  16340. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16341. */
  16342. (function( $ ){
  16343. /**
  16344. * @class Viewport
  16345. * @memberof OpenSeadragon
  16346. * @classdesc Handles coordinate-related functionality (zoom, pan, rotation, etc.)
  16347. * for an {@link OpenSeadragon.Viewer}.
  16348. * @param {Object} options - Options for this Viewport.
  16349. * @param {Object} [options.margins] - See viewportMargins in {@link OpenSeadragon.Options}.
  16350. * @param {Number} [options.springStiffness] - See springStiffness in {@link OpenSeadragon.Options}.
  16351. * @param {Number} [options.animationTime] - See animationTime in {@link OpenSeadragon.Options}.
  16352. * @param {Number} [options.minZoomImageRatio] - See minZoomImageRatio in {@link OpenSeadragon.Options}.
  16353. * @param {Number} [options.maxZoomPixelRatio] - See maxZoomPixelRatio in {@link OpenSeadragon.Options}.
  16354. * @param {Number} [options.visibilityRatio] - See visibilityRatio in {@link OpenSeadragon.Options}.
  16355. * @param {Boolean} [options.wrapHorizontal] - See wrapHorizontal in {@link OpenSeadragon.Options}.
  16356. * @param {Boolean} [options.wrapVertical] - See wrapVertical in {@link OpenSeadragon.Options}.
  16357. * @param {Number} [options.defaultZoomLevel] - See defaultZoomLevel in {@link OpenSeadragon.Options}.
  16358. * @param {Number} [options.minZoomLevel] - See minZoomLevel in {@link OpenSeadragon.Options}.
  16359. * @param {Number} [options.maxZoomLevel] - See maxZoomLevel in {@link OpenSeadragon.Options}.
  16360. * @param {Number} [options.degrees] - See degrees in {@link OpenSeadragon.Options}.
  16361. * @param {Boolean} [options.homeFillsViewer] - See homeFillsViewer in {@link OpenSeadragon.Options}.
  16362. */
  16363. $.Viewport = function( options ) {
  16364. //backward compatibility for positional args while preferring more
  16365. //idiomatic javascript options object as the only argument
  16366. var args = arguments;
  16367. if (args.length && args[0] instanceof $.Point) {
  16368. options = {
  16369. containerSize: args[0],
  16370. contentSize: args[1],
  16371. config: args[2]
  16372. };
  16373. }
  16374. //options.config and the general config argument are deprecated
  16375. //in favor of the more direct specification of optional settings
  16376. //being passed directly on the options object
  16377. if ( options.config ){
  16378. $.extend( true, options, options.config );
  16379. delete options.config;
  16380. }
  16381. this._margins = $.extend({
  16382. left: 0,
  16383. top: 0,
  16384. right: 0,
  16385. bottom: 0
  16386. }, options.margins || {});
  16387. delete options.margins;
  16388. $.extend( true, this, {
  16389. //required settings
  16390. containerSize: null,
  16391. contentSize: null,
  16392. //internal state properties
  16393. zoomPoint: null,
  16394. viewer: null,
  16395. //configurable options
  16396. springStiffness: $.DEFAULT_SETTINGS.springStiffness,
  16397. animationTime: $.DEFAULT_SETTINGS.animationTime,
  16398. minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
  16399. maxZoomPixelRatio: $.DEFAULT_SETTINGS.maxZoomPixelRatio,
  16400. visibilityRatio: $.DEFAULT_SETTINGS.visibilityRatio,
  16401. wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
  16402. wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
  16403. defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel,
  16404. minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel,
  16405. maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel,
  16406. degrees: $.DEFAULT_SETTINGS.degrees,
  16407. flipped: $.DEFAULT_SETTINGS.flipped,
  16408. homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer
  16409. }, options );
  16410. this._updateContainerInnerSize();
  16411. this.centerSpringX = new $.Spring({
  16412. initial: 0,
  16413. springStiffness: this.springStiffness,
  16414. animationTime: this.animationTime
  16415. });
  16416. this.centerSpringY = new $.Spring({
  16417. initial: 0,
  16418. springStiffness: this.springStiffness,
  16419. animationTime: this.animationTime
  16420. });
  16421. this.zoomSpring = new $.Spring({
  16422. exponential: true,
  16423. initial: 1,
  16424. springStiffness: this.springStiffness,
  16425. animationTime: this.animationTime
  16426. });
  16427. this._oldCenterX = this.centerSpringX.current.value;
  16428. this._oldCenterY = this.centerSpringY.current.value;
  16429. this._oldZoom = this.zoomSpring.current.value;
  16430. this._setContentBounds(new $.Rect(0, 0, 1, 1), 1);
  16431. this.goHome(true);
  16432. this.update();
  16433. };
  16434. /** @lends OpenSeadragon.Viewport.prototype */
  16435. $.Viewport.prototype = {
  16436. /**
  16437. * Updates the viewport's home bounds and constraints for the given content size.
  16438. * @function
  16439. * @param {OpenSeadragon.Point} contentSize - size of the content in content units
  16440. * @return {OpenSeadragon.Viewport} Chainable.
  16441. * @fires OpenSeadragon.Viewer.event:reset-size
  16442. */
  16443. resetContentSize: function(contentSize) {
  16444. $.console.assert(contentSize, "[Viewport.resetContentSize] contentSize is required");
  16445. $.console.assert(contentSize instanceof $.Point, "[Viewport.resetContentSize] contentSize must be an OpenSeadragon.Point");
  16446. $.console.assert(contentSize.x > 0, "[Viewport.resetContentSize] contentSize.x must be greater than 0");
  16447. $.console.assert(contentSize.y > 0, "[Viewport.resetContentSize] contentSize.y must be greater than 0");
  16448. this._setContentBounds(new $.Rect(0, 0, 1, contentSize.y / contentSize.x), contentSize.x);
  16449. return this;
  16450. },
  16451. // deprecated
  16452. setHomeBounds: function(bounds, contentFactor) {
  16453. $.console.error("[Viewport.setHomeBounds] this function is deprecated; The content bounds should not be set manually.");
  16454. this._setContentBounds(bounds, contentFactor);
  16455. },
  16456. // Set the viewport's content bounds
  16457. // @param {OpenSeadragon.Rect} bounds - the new bounds in viewport coordinates
  16458. // without rotation
  16459. // @param {Number} contentFactor - how many content units per viewport unit
  16460. // @fires OpenSeadragon.Viewer.event:reset-size
  16461. // @private
  16462. _setContentBounds: function(bounds, contentFactor) {
  16463. $.console.assert(bounds, "[Viewport._setContentBounds] bounds is required");
  16464. $.console.assert(bounds instanceof $.Rect, "[Viewport._setContentBounds] bounds must be an OpenSeadragon.Rect");
  16465. $.console.assert(bounds.width > 0, "[Viewport._setContentBounds] bounds.width must be greater than 0");
  16466. $.console.assert(bounds.height > 0, "[Viewport._setContentBounds] bounds.height must be greater than 0");
  16467. this._contentBoundsNoRotate = bounds.clone();
  16468. this._contentSizeNoRotate = this._contentBoundsNoRotate.getSize().times(
  16469. contentFactor);
  16470. this._contentBounds = bounds.rotate(this.degrees).getBoundingBox();
  16471. this._contentSize = this._contentBounds.getSize().times(contentFactor);
  16472. this._contentAspectRatio = this._contentSize.x / this._contentSize.y;
  16473. if (this.viewer) {
  16474. /**
  16475. * Raised when the viewer's content size or home bounds are reset
  16476. * (see {@link OpenSeadragon.Viewport#resetContentSize}).
  16477. *
  16478. * @event reset-size
  16479. * @memberof OpenSeadragon.Viewer
  16480. * @type {object}
  16481. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  16482. * @property {OpenSeadragon.Point} contentSize
  16483. * @property {OpenSeadragon.Rect} contentBounds - Content bounds.
  16484. * @property {OpenSeadragon.Rect} homeBounds - Content bounds.
  16485. * Deprecated use contentBounds instead.
  16486. * @property {Number} contentFactor
  16487. * @property {?Object} userData - Arbitrary subscriber-defined object.
  16488. */
  16489. this.viewer.raiseEvent('reset-size', {
  16490. contentSize: this._contentSizeNoRotate.clone(),
  16491. contentFactor: contentFactor,
  16492. homeBounds: this._contentBoundsNoRotate.clone(),
  16493. contentBounds: this._contentBounds.clone()
  16494. });
  16495. }
  16496. },
  16497. /**
  16498. * Returns the home zoom in "viewport zoom" value.
  16499. * @function
  16500. * @returns {Number} The home zoom in "viewport zoom".
  16501. */
  16502. getHomeZoom: function() {
  16503. if (this.defaultZoomLevel) {
  16504. return this.defaultZoomLevel;
  16505. }
  16506. var aspectFactor = this._contentAspectRatio / this.getAspectRatio();
  16507. var output;
  16508. if (this.homeFillsViewer) { // fill the viewer and clip the image
  16509. output = aspectFactor >= 1 ? aspectFactor : 1;
  16510. } else {
  16511. output = aspectFactor >= 1 ? 1 : aspectFactor;
  16512. }
  16513. return output / this._contentBounds.width;
  16514. },
  16515. /**
  16516. * Returns the home bounds in viewport coordinates.
  16517. * @function
  16518. * @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates.
  16519. */
  16520. getHomeBounds: function() {
  16521. return this.getHomeBoundsNoRotate().rotate(-this.getRotation());
  16522. },
  16523. /**
  16524. * Returns the home bounds in viewport coordinates.
  16525. * This method ignores the viewport rotation. Use
  16526. * {@link OpenSeadragon.Viewport#getHomeBounds} to take it into account.
  16527. * @function
  16528. * @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates.
  16529. */
  16530. getHomeBoundsNoRotate: function() {
  16531. var center = this._contentBounds.getCenter();
  16532. var width = 1.0 / this.getHomeZoom();
  16533. var height = width / this.getAspectRatio();
  16534. return new $.Rect(
  16535. center.x - (width / 2.0),
  16536. center.y - (height / 2.0),
  16537. width,
  16538. height
  16539. );
  16540. },
  16541. /**
  16542. * @function
  16543. * @param {Boolean} immediately
  16544. * @fires OpenSeadragon.Viewer.event:home
  16545. */
  16546. goHome: function(immediately) {
  16547. if (this.viewer) {
  16548. /**
  16549. * Raised when the "home" operation occurs (see {@link OpenSeadragon.Viewport#goHome}).
  16550. *
  16551. * @event home
  16552. * @memberof OpenSeadragon.Viewer
  16553. * @type {object}
  16554. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  16555. * @property {Boolean} immediately
  16556. * @property {?Object} userData - Arbitrary subscriber-defined object.
  16557. */
  16558. this.viewer.raiseEvent('home', {
  16559. immediately: immediately
  16560. });
  16561. }
  16562. return this.fitBounds(this.getHomeBounds(), immediately);
  16563. },
  16564. /**
  16565. * @function
  16566. */
  16567. getMinZoom: function() {
  16568. var homeZoom = this.getHomeZoom(),
  16569. zoom = this.minZoomLevel ?
  16570. this.minZoomLevel :
  16571. this.minZoomImageRatio * homeZoom;
  16572. return zoom;
  16573. },
  16574. /**
  16575. * @function
  16576. */
  16577. getMaxZoom: function() {
  16578. var zoom = this.maxZoomLevel;
  16579. if (!zoom) {
  16580. zoom = this._contentSize.x * this.maxZoomPixelRatio / this._containerInnerSize.x;
  16581. zoom /= this._contentBounds.width;
  16582. }
  16583. return Math.max( zoom, this.getHomeZoom() );
  16584. },
  16585. /**
  16586. * @function
  16587. */
  16588. getAspectRatio: function() {
  16589. return this._containerInnerSize.x / this._containerInnerSize.y;
  16590. },
  16591. /**
  16592. * @function
  16593. * @returns {OpenSeadragon.Point} The size of the container, in screen coordinates.
  16594. */
  16595. getContainerSize: function() {
  16596. return new $.Point(
  16597. this.containerSize.x,
  16598. this.containerSize.y
  16599. );
  16600. },
  16601. /**
  16602. * The margins push the "home" region in from the sides by the specified amounts.
  16603. * @function
  16604. * @returns {Object} Properties (Numbers, in screen coordinates): left, top, right, bottom.
  16605. */
  16606. getMargins: function() {
  16607. return $.extend({}, this._margins); // Make a copy so we are not returning our original
  16608. },
  16609. /**
  16610. * The margins push the "home" region in from the sides by the specified amounts.
  16611. * @function
  16612. * @param {Object} margins - Properties (Numbers, in screen coordinates): left, top, right, bottom.
  16613. */
  16614. setMargins: function(margins) {
  16615. $.console.assert($.type(margins) === 'object', '[Viewport.setMargins] margins must be an object');
  16616. this._margins = $.extend({
  16617. left: 0,
  16618. top: 0,
  16619. right: 0,
  16620. bottom: 0
  16621. }, margins);
  16622. this._updateContainerInnerSize();
  16623. if (this.viewer) {
  16624. this.viewer.forceRedraw();
  16625. }
  16626. },
  16627. /**
  16628. * Returns the bounds of the visible area in viewport coordinates.
  16629. * @function
  16630. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  16631. * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.
  16632. */
  16633. getBounds: function(current) {
  16634. return this.getBoundsNoRotate(current).rotate(-this.getRotation());
  16635. },
  16636. /**
  16637. * Returns the bounds of the visible area in viewport coordinates.
  16638. * This method ignores the viewport rotation. Use
  16639. * {@link OpenSeadragon.Viewport#getBounds} to take it into account.
  16640. * @function
  16641. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  16642. * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.
  16643. */
  16644. getBoundsNoRotate: function(current) {
  16645. var center = this.getCenter(current);
  16646. var width = 1.0 / this.getZoom(current);
  16647. var height = width / this.getAspectRatio();
  16648. return new $.Rect(
  16649. center.x - (width / 2.0),
  16650. center.y - (height / 2.0),
  16651. width,
  16652. height
  16653. );
  16654. },
  16655. /**
  16656. * @function
  16657. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  16658. * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to,
  16659. * including the space taken by margins, in viewport coordinates.
  16660. */
  16661. getBoundsWithMargins: function(current) {
  16662. return this.getBoundsNoRotateWithMargins(current).rotate(
  16663. -this.getRotation(), this.getCenter(current));
  16664. },
  16665. /**
  16666. * @function
  16667. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  16668. * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to,
  16669. * including the space taken by margins, in viewport coordinates.
  16670. */
  16671. getBoundsNoRotateWithMargins: function(current) {
  16672. var bounds = this.getBoundsNoRotate(current);
  16673. var factor = this._containerInnerSize.x * this.getZoom(current);
  16674. bounds.x -= this._margins.left / factor;
  16675. bounds.y -= this._margins.top / factor;
  16676. bounds.width += (this._margins.left + this._margins.right) / factor;
  16677. bounds.height += (this._margins.top + this._margins.bottom) / factor;
  16678. return bounds;
  16679. },
  16680. /**
  16681. * @function
  16682. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  16683. */
  16684. getCenter: function( current ) {
  16685. var centerCurrent = new $.Point(
  16686. this.centerSpringX.current.value,
  16687. this.centerSpringY.current.value
  16688. ),
  16689. centerTarget = new $.Point(
  16690. this.centerSpringX.target.value,
  16691. this.centerSpringY.target.value
  16692. ),
  16693. oldZoomPixel,
  16694. zoom,
  16695. width,
  16696. height,
  16697. bounds,
  16698. newZoomPixel,
  16699. deltaZoomPixels,
  16700. deltaZoomPoints;
  16701. if ( current ) {
  16702. return centerCurrent;
  16703. } else if ( !this.zoomPoint ) {
  16704. return centerTarget;
  16705. }
  16706. oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
  16707. zoom = this.getZoom();
  16708. width = 1.0 / zoom;
  16709. height = width / this.getAspectRatio();
  16710. bounds = new $.Rect(
  16711. centerCurrent.x - width / 2.0,
  16712. centerCurrent.y - height / 2.0,
  16713. width,
  16714. height
  16715. );
  16716. newZoomPixel = this._pixelFromPoint(this.zoomPoint, bounds);
  16717. deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
  16718. deltaZoomPoints = deltaZoomPixels.divide( this._containerInnerSize.x * zoom );
  16719. return centerTarget.plus( deltaZoomPoints );
  16720. },
  16721. /**
  16722. * @function
  16723. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  16724. */
  16725. getZoom: function( current ) {
  16726. if ( current ) {
  16727. return this.zoomSpring.current.value;
  16728. } else {
  16729. return this.zoomSpring.target.value;
  16730. }
  16731. },
  16732. // private
  16733. _applyZoomConstraints: function(zoom) {
  16734. return Math.max(
  16735. Math.min(zoom, this.getMaxZoom()),
  16736. this.getMinZoom());
  16737. },
  16738. /**
  16739. * @function
  16740. * @private
  16741. * @param {OpenSeadragon.Rect} bounds
  16742. * @return {OpenSeadragon.Rect} constrained bounds.
  16743. */
  16744. _applyBoundaryConstraints: function(bounds) {
  16745. var newBounds = new $.Rect(
  16746. bounds.x,
  16747. bounds.y,
  16748. bounds.width,
  16749. bounds.height);
  16750. if (this.wrapHorizontal) {
  16751. //do nothing
  16752. } else {
  16753. var horizontalThreshold = this.visibilityRatio * newBounds.width;
  16754. var boundsRight = newBounds.x + newBounds.width;
  16755. var contentRight = this._contentBoundsNoRotate.x + this._contentBoundsNoRotate.width;
  16756. var leftDx = this._contentBoundsNoRotate.x - boundsRight + horizontalThreshold;
  16757. var rightDx = contentRight - newBounds.x - horizontalThreshold;
  16758. if (horizontalThreshold > this._contentBoundsNoRotate.width) {
  16759. newBounds.x += (leftDx + rightDx) / 2;
  16760. } else if (rightDx < 0) {
  16761. newBounds.x += rightDx;
  16762. } else if (leftDx > 0) {
  16763. newBounds.x += leftDx;
  16764. }
  16765. }
  16766. if (this.wrapVertical) {
  16767. //do nothing
  16768. } else {
  16769. var verticalThreshold = this.visibilityRatio * newBounds.height;
  16770. var boundsBottom = newBounds.y + newBounds.height;
  16771. var contentBottom = this._contentBoundsNoRotate.y + this._contentBoundsNoRotate.height;
  16772. var topDy = this._contentBoundsNoRotate.y - boundsBottom + verticalThreshold;
  16773. var bottomDy = contentBottom - newBounds.y - verticalThreshold;
  16774. if (verticalThreshold > this._contentBoundsNoRotate.height) {
  16775. newBounds.y += (topDy + bottomDy) / 2;
  16776. } else if (bottomDy < 0) {
  16777. newBounds.y += bottomDy;
  16778. } else if (topDy > 0) {
  16779. newBounds.y += topDy;
  16780. }
  16781. }
  16782. return newBounds;
  16783. },
  16784. /**
  16785. * @function
  16786. * @private
  16787. * @param {Boolean} [immediately=false] - whether the function that triggered this event was
  16788. * called with the "immediately" flag
  16789. */
  16790. _raiseConstraintsEvent: function(immediately) {
  16791. if (this.viewer) {
  16792. /**
  16793. * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}).
  16794. *
  16795. * @event constrain
  16796. * @memberof OpenSeadragon.Viewer
  16797. * @type {object}
  16798. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  16799. * @property {Boolean} immediately - whether the function that triggered this event was
  16800. * called with the "immediately" flag
  16801. * @property {?Object} userData - Arbitrary subscriber-defined object.
  16802. */
  16803. this.viewer.raiseEvent( 'constrain', {
  16804. immediately: immediately
  16805. });
  16806. }
  16807. },
  16808. /**
  16809. * Enforces the minZoom, maxZoom and visibilityRatio constraints by
  16810. * zooming and panning to the closest acceptable zoom and location.
  16811. * @function
  16812. * @param {Boolean} [immediately=false]
  16813. * @return {OpenSeadragon.Viewport} Chainable.
  16814. * @fires OpenSeadragon.Viewer.event:constrain
  16815. */
  16816. applyConstraints: function(immediately) {
  16817. var actualZoom = this.getZoom();
  16818. var constrainedZoom = this._applyZoomConstraints(actualZoom);
  16819. if (actualZoom !== constrainedZoom) {
  16820. this.zoomTo(constrainedZoom, this.zoomPoint, immediately);
  16821. }
  16822. var bounds = this.getBoundsNoRotate();
  16823. var constrainedBounds = this._applyBoundaryConstraints(bounds);
  16824. this._raiseConstraintsEvent(immediately);
  16825. if (bounds.x !== constrainedBounds.x ||
  16826. bounds.y !== constrainedBounds.y ||
  16827. immediately) {
  16828. this.fitBounds(
  16829. constrainedBounds.rotate(-this.getRotation()),
  16830. immediately);
  16831. }
  16832. return this;
  16833. },
  16834. /**
  16835. * Equivalent to {@link OpenSeadragon.Viewport#applyConstraints}
  16836. * @function
  16837. * @param {Boolean} [immediately=false]
  16838. * @return {OpenSeadragon.Viewport} Chainable.
  16839. * @fires OpenSeadragon.Viewer.event:constrain
  16840. */
  16841. ensureVisible: function(immediately) {
  16842. return this.applyConstraints(immediately);
  16843. },
  16844. /**
  16845. * @function
  16846. * @private
  16847. * @param {OpenSeadragon.Rect} bounds
  16848. * @param {Object} options (immediately=false, constraints=false)
  16849. * @return {OpenSeadragon.Viewport} Chainable.
  16850. */
  16851. _fitBounds: function(bounds, options) {
  16852. options = options || {};
  16853. var immediately = options.immediately || false;
  16854. var constraints = options.constraints || false;
  16855. var aspect = this.getAspectRatio();
  16856. var center = bounds.getCenter();
  16857. // Compute width and height of bounding box.
  16858. var newBounds = new $.Rect(
  16859. bounds.x,
  16860. bounds.y,
  16861. bounds.width,
  16862. bounds.height,
  16863. bounds.degrees + this.getRotation())
  16864. .getBoundingBox();
  16865. if (newBounds.getAspectRatio() >= aspect) {
  16866. newBounds.height = newBounds.width / aspect;
  16867. } else {
  16868. newBounds.width = newBounds.height * aspect;
  16869. }
  16870. // Compute x and y from width, height and center position
  16871. newBounds.x = center.x - newBounds.width / 2;
  16872. newBounds.y = center.y - newBounds.height / 2;
  16873. var newZoom = 1.0 / newBounds.width;
  16874. if (constraints) {
  16875. var newBoundsAspectRatio = newBounds.getAspectRatio();
  16876. var newConstrainedZoom = this._applyZoomConstraints(newZoom);
  16877. if (newZoom !== newConstrainedZoom) {
  16878. newZoom = newConstrainedZoom;
  16879. newBounds.width = 1.0 / newZoom;
  16880. newBounds.x = center.x - newBounds.width / 2;
  16881. newBounds.height = newBounds.width / newBoundsAspectRatio;
  16882. newBounds.y = center.y - newBounds.height / 2;
  16883. }
  16884. newBounds = this._applyBoundaryConstraints(newBounds);
  16885. center = newBounds.getCenter();
  16886. this._raiseConstraintsEvent(immediately);
  16887. }
  16888. if (immediately) {
  16889. this.panTo(center, true);
  16890. return this.zoomTo(newZoom, null, true);
  16891. }
  16892. this.panTo(this.getCenter(true), true);
  16893. this.zoomTo(this.getZoom(true), null, true);
  16894. var oldBounds = this.getBounds();
  16895. var oldZoom = this.getZoom();
  16896. if (oldZoom === 0 || Math.abs(newZoom / oldZoom - 1) < 0.00000001) {
  16897. this.zoomTo(newZoom, true);
  16898. return this.panTo(center, immediately);
  16899. }
  16900. newBounds = newBounds.rotate(-this.getRotation());
  16901. var referencePoint = newBounds.getTopLeft().times(newZoom)
  16902. .minus(oldBounds.getTopLeft().times(oldZoom))
  16903. .divide(newZoom - oldZoom);
  16904. return this.zoomTo(newZoom, referencePoint, immediately);
  16905. },
  16906. /**
  16907. * Makes the viewport zoom and pan so that the specified bounds take
  16908. * as much space as possible in the viewport.
  16909. * Note: this method ignores the constraints (minZoom, maxZoom and
  16910. * visibilityRatio).
  16911. * Use {@link OpenSeadragon.Viewport#fitBoundsWithConstraints} to enforce
  16912. * them.
  16913. * @function
  16914. * @param {OpenSeadragon.Rect} bounds
  16915. * @param {Boolean} [immediately=false]
  16916. * @return {OpenSeadragon.Viewport} Chainable.
  16917. */
  16918. fitBounds: function(bounds, immediately) {
  16919. return this._fitBounds(bounds, {
  16920. immediately: immediately,
  16921. constraints: false
  16922. });
  16923. },
  16924. /**
  16925. * Makes the viewport zoom and pan so that the specified bounds take
  16926. * as much space as possible in the viewport while enforcing the constraints
  16927. * (minZoom, maxZoom and visibilityRatio).
  16928. * Note: because this method enforces the constraints, part of the
  16929. * provided bounds may end up outside of the viewport.
  16930. * Use {@link OpenSeadragon.Viewport#fitBounds} to ignore them.
  16931. * @function
  16932. * @param {OpenSeadragon.Rect} bounds
  16933. * @param {Boolean} [immediately=false]
  16934. * @return {OpenSeadragon.Viewport} Chainable.
  16935. */
  16936. fitBoundsWithConstraints: function(bounds, immediately) {
  16937. return this._fitBounds(bounds, {
  16938. immediately: immediately,
  16939. constraints: true
  16940. });
  16941. },
  16942. /**
  16943. * Zooms so the image just fills the viewer vertically.
  16944. * @param {Boolean} immediately
  16945. * @return {OpenSeadragon.Viewport} Chainable.
  16946. */
  16947. fitVertically: function(immediately) {
  16948. var box = new $.Rect(
  16949. this._contentBounds.x + (this._contentBounds.width / 2),
  16950. this._contentBounds.y,
  16951. 0,
  16952. this._contentBounds.height);
  16953. return this.fitBounds(box, immediately);
  16954. },
  16955. /**
  16956. * Zooms so the image just fills the viewer horizontally.
  16957. * @param {Boolean} immediately
  16958. * @return {OpenSeadragon.Viewport} Chainable.
  16959. */
  16960. fitHorizontally: function(immediately) {
  16961. var box = new $.Rect(
  16962. this._contentBounds.x,
  16963. this._contentBounds.y + (this._contentBounds.height / 2),
  16964. this._contentBounds.width,
  16965. 0);
  16966. return this.fitBounds(box, immediately);
  16967. },
  16968. /**
  16969. * Returns bounds taking constraints into account
  16970. * Added to improve constrained panning
  16971. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  16972. * @return {OpenSeadragon.Viewport} Chainable.
  16973. */
  16974. getConstrainedBounds: function(current) {
  16975. var bounds,
  16976. constrainedBounds;
  16977. bounds = this.getBounds(current);
  16978. constrainedBounds = this._applyBoundaryConstraints(bounds);
  16979. return constrainedBounds;
  16980. },
  16981. /**
  16982. * @function
  16983. * @param {OpenSeadragon.Point} delta
  16984. * @param {Boolean} immediately
  16985. * @return {OpenSeadragon.Viewport} Chainable.
  16986. * @fires OpenSeadragon.Viewer.event:pan
  16987. */
  16988. panBy: function( delta, immediately ) {
  16989. var center = new $.Point(
  16990. this.centerSpringX.target.value,
  16991. this.centerSpringY.target.value
  16992. );
  16993. return this.panTo( center.plus( delta ), immediately );
  16994. },
  16995. /**
  16996. * @function
  16997. * @param {OpenSeadragon.Point} center
  16998. * @param {Boolean} immediately
  16999. * @return {OpenSeadragon.Viewport} Chainable.
  17000. * @fires OpenSeadragon.Viewer.event:pan
  17001. */
  17002. panTo: function( center, immediately ) {
  17003. if ( immediately ) {
  17004. this.centerSpringX.resetTo( center.x );
  17005. this.centerSpringY.resetTo( center.y );
  17006. } else {
  17007. this.centerSpringX.springTo( center.x );
  17008. this.centerSpringY.springTo( center.y );
  17009. }
  17010. if( this.viewer ){
  17011. /**
  17012. * Raised when the viewport is panned (see {@link OpenSeadragon.Viewport#panBy} and {@link OpenSeadragon.Viewport#panTo}).
  17013. *
  17014. * @event pan
  17015. * @memberof OpenSeadragon.Viewer
  17016. * @type {object}
  17017. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  17018. * @property {OpenSeadragon.Point} center
  17019. * @property {Boolean} immediately
  17020. * @property {?Object} userData - Arbitrary subscriber-defined object.
  17021. */
  17022. this.viewer.raiseEvent( 'pan', {
  17023. center: center,
  17024. immediately: immediately
  17025. });
  17026. }
  17027. return this;
  17028. },
  17029. /**
  17030. * @function
  17031. * @return {OpenSeadragon.Viewport} Chainable.
  17032. * @fires OpenSeadragon.Viewer.event:zoom
  17033. */
  17034. zoomBy: function(factor, refPoint, immediately) {
  17035. return this.zoomTo(
  17036. this.zoomSpring.target.value * factor, refPoint, immediately);
  17037. },
  17038. /**
  17039. * Zooms to the specified zoom level
  17040. * @function
  17041. * @param {Number} zoom The zoom level to zoom to.
  17042. * @param {OpenSeadragon.Point} [refPoint] The point which will stay at
  17043. * the same screen location. Defaults to the viewport center.
  17044. * @param {Boolean} [immediately=false]
  17045. * @return {OpenSeadragon.Viewport} Chainable.
  17046. * @fires OpenSeadragon.Viewer.event:zoom
  17047. */
  17048. zoomTo: function(zoom, refPoint, immediately) {
  17049. var _this = this;
  17050. this.zoomPoint = refPoint instanceof $.Point &&
  17051. !isNaN(refPoint.x) &&
  17052. !isNaN(refPoint.y) ?
  17053. refPoint :
  17054. null;
  17055. if (immediately) {
  17056. this._adjustCenterSpringsForZoomPoint(function() {
  17057. _this.zoomSpring.resetTo(zoom);
  17058. });
  17059. } else {
  17060. this.zoomSpring.springTo(zoom);
  17061. }
  17062. if (this.viewer) {
  17063. /**
  17064. * Raised when the viewport zoom level changes (see {@link OpenSeadragon.Viewport#zoomBy} and {@link OpenSeadragon.Viewport#zoomTo}).
  17065. *
  17066. * @event zoom
  17067. * @memberof OpenSeadragon.Viewer
  17068. * @type {object}
  17069. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  17070. * @property {Number} zoom
  17071. * @property {OpenSeadragon.Point} refPoint
  17072. * @property {Boolean} immediately
  17073. * @property {?Object} userData - Arbitrary subscriber-defined object.
  17074. */
  17075. this.viewer.raiseEvent('zoom', {
  17076. zoom: zoom,
  17077. refPoint: refPoint,
  17078. immediately: immediately
  17079. });
  17080. }
  17081. return this;
  17082. },
  17083. /**
  17084. * Rotates this viewport to the angle specified.
  17085. * @function
  17086. * @return {OpenSeadragon.Viewport} Chainable.
  17087. */
  17088. setRotation: function(degrees) {
  17089. if (!this.viewer || !this.viewer.drawer.canRotate()) {
  17090. return this;
  17091. }
  17092. this.degrees = $.positiveModulo(degrees, 360);
  17093. this._setContentBounds(
  17094. this.viewer.world.getHomeBounds(),
  17095. this.viewer.world.getContentFactor());
  17096. this.viewer.forceRedraw();
  17097. /**
  17098. * Raised when rotation has been changed.
  17099. *
  17100. * @event rotate
  17101. * @memberof OpenSeadragon.Viewer
  17102. * @type {object}
  17103. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  17104. * @property {Number} degrees - The number of degrees the rotation was set to.
  17105. * @property {?Object} userData - Arbitrary subscriber-defined object.
  17106. */
  17107. this.viewer.raiseEvent('rotate', {"degrees": degrees});
  17108. return this;
  17109. },
  17110. /**
  17111. * Gets the current rotation in degrees.
  17112. * @function
  17113. * @return {Number} The current rotation in degrees.
  17114. */
  17115. getRotation: function() {
  17116. return this.degrees;
  17117. },
  17118. /**
  17119. * @function
  17120. * @return {OpenSeadragon.Viewport} Chainable.
  17121. * @fires OpenSeadragon.Viewer.event:resize
  17122. */
  17123. resize: function( newContainerSize, maintain ) {
  17124. var oldBounds = this.getBoundsNoRotate(),
  17125. newBounds = oldBounds,
  17126. widthDeltaFactor;
  17127. this.containerSize.x = newContainerSize.x;
  17128. this.containerSize.y = newContainerSize.y;
  17129. this._updateContainerInnerSize();
  17130. if ( maintain ) {
  17131. // TODO: widthDeltaFactor will always be 1; probably not what's intended
  17132. widthDeltaFactor = newContainerSize.x / this.containerSize.x;
  17133. newBounds.width = oldBounds.width * widthDeltaFactor;
  17134. newBounds.height = newBounds.width / this.getAspectRatio();
  17135. }
  17136. if( this.viewer ){
  17137. /**
  17138. * Raised when the viewer is resized (see {@link OpenSeadragon.Viewport#resize}).
  17139. *
  17140. * @event resize
  17141. * @memberof OpenSeadragon.Viewer
  17142. * @type {object}
  17143. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
  17144. * @property {OpenSeadragon.Point} newContainerSize
  17145. * @property {Boolean} maintain
  17146. * @property {?Object} userData - Arbitrary subscriber-defined object.
  17147. */
  17148. this.viewer.raiseEvent( 'resize', {
  17149. newContainerSize: newContainerSize,
  17150. maintain: maintain
  17151. });
  17152. }
  17153. return this.fitBounds( newBounds, true );
  17154. },
  17155. // private
  17156. _updateContainerInnerSize: function() {
  17157. this._containerInnerSize = new $.Point(
  17158. Math.max(1, this.containerSize.x - (this._margins.left + this._margins.right)),
  17159. Math.max(1, this.containerSize.y - (this._margins.top + this._margins.bottom))
  17160. );
  17161. },
  17162. /**
  17163. * Update the zoom and center (X and Y) springs.
  17164. * @function
  17165. * @returns {Boolean} True if any change has been made, false otherwise.
  17166. */
  17167. update: function() {
  17168. var _this = this;
  17169. this._adjustCenterSpringsForZoomPoint(function() {
  17170. _this.zoomSpring.update();
  17171. });
  17172. this.centerSpringX.update();
  17173. this.centerSpringY.update();
  17174. var changed = this.centerSpringX.current.value !== this._oldCenterX ||
  17175. this.centerSpringY.current.value !== this._oldCenterY ||
  17176. this.zoomSpring.current.value !== this._oldZoom;
  17177. this._oldCenterX = this.centerSpringX.current.value;
  17178. this._oldCenterY = this.centerSpringY.current.value;
  17179. this._oldZoom = this.zoomSpring.current.value;
  17180. return changed;
  17181. },
  17182. _adjustCenterSpringsForZoomPoint: function(zoomSpringHandler) {
  17183. if (this.zoomPoint) {
  17184. var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
  17185. zoomSpringHandler();
  17186. var newZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
  17187. var deltaZoomPixels = newZoomPixel.minus(oldZoomPixel);
  17188. var deltaZoomPoints = this.deltaPointsFromPixels(
  17189. deltaZoomPixels, true);
  17190. this.centerSpringX.shiftBy(deltaZoomPoints.x);
  17191. this.centerSpringY.shiftBy(deltaZoomPoints.y);
  17192. if (this.zoomSpring.isAtTargetValue()) {
  17193. this.zoomPoint = null;
  17194. }
  17195. } else {
  17196. zoomSpringHandler();
  17197. }
  17198. },
  17199. /**
  17200. * Convert a delta (translation vector) from viewport coordinates to pixels
  17201. * coordinates. This method does not take rotation into account.
  17202. * Consider using deltaPixelsFromPoints if you need to account for rotation.
  17203. * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert.
  17204. * @param {Boolean} [current=false] - Pass true for the current location;
  17205. * defaults to false (target location).
  17206. * @returns {OpenSeadragon.Point}
  17207. */
  17208. deltaPixelsFromPointsNoRotate: function(deltaPoints, current) {
  17209. return deltaPoints.times(
  17210. this._containerInnerSize.x * this.getZoom(current)
  17211. );
  17212. },
  17213. /**
  17214. * Convert a delta (translation vector) from viewport coordinates to pixels
  17215. * coordinates.
  17216. * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert.
  17217. * @param {Boolean} [current=false] - Pass true for the current location;
  17218. * defaults to false (target location).
  17219. * @returns {OpenSeadragon.Point}
  17220. */
  17221. deltaPixelsFromPoints: function(deltaPoints, current) {
  17222. return this.deltaPixelsFromPointsNoRotate(
  17223. deltaPoints.rotate(this.getRotation()),
  17224. current);
  17225. },
  17226. /**
  17227. * Convert a delta (translation vector) from pixels coordinates to viewport
  17228. * coordinates. This method does not take rotation into account.
  17229. * Consider using deltaPointsFromPixels if you need to account for rotation.
  17230. * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert.
  17231. * @param {Boolean} [current=false] - Pass true for the current location;
  17232. * defaults to false (target location).
  17233. * @returns {OpenSeadragon.Point}
  17234. */
  17235. deltaPointsFromPixelsNoRotate: function(deltaPixels, current) {
  17236. return deltaPixels.divide(
  17237. this._containerInnerSize.x * this.getZoom(current)
  17238. );
  17239. },
  17240. /**
  17241. * Convert a delta (translation vector) from pixels coordinates to viewport
  17242. * coordinates.
  17243. * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert.
  17244. * @param {Boolean} [current=false] - Pass true for the current location;
  17245. * defaults to false (target location).
  17246. * @returns {OpenSeadragon.Point}
  17247. */
  17248. deltaPointsFromPixels: function(deltaPixels, current) {
  17249. return this.deltaPointsFromPixelsNoRotate(deltaPixels, current)
  17250. .rotate(-this.getRotation());
  17251. },
  17252. /**
  17253. * Convert viewport coordinates to pixels coordinates.
  17254. * This method does not take rotation into account.
  17255. * Consider using pixelFromPoint if you need to account for rotation.
  17256. * @param {OpenSeadragon.Point} point the viewport coordinates
  17257. * @param {Boolean} [current=false] - Pass true for the current location;
  17258. * defaults to false (target location).
  17259. * @returns {OpenSeadragon.Point}
  17260. */
  17261. pixelFromPointNoRotate: function(point, current) {
  17262. return this._pixelFromPointNoRotate(
  17263. point, this.getBoundsNoRotate(current));
  17264. },
  17265. /**
  17266. * Convert viewport coordinates to pixel coordinates.
  17267. * @param {OpenSeadragon.Point} point the viewport coordinates
  17268. * @param {Boolean} [current=false] - Pass true for the current location;
  17269. * defaults to false (target location).
  17270. * @returns {OpenSeadragon.Point}
  17271. */
  17272. pixelFromPoint: function(point, current) {
  17273. return this._pixelFromPoint(point, this.getBoundsNoRotate(current));
  17274. },
  17275. // private
  17276. _pixelFromPointNoRotate: function(point, bounds) {
  17277. return point.minus(
  17278. bounds.getTopLeft()
  17279. ).times(
  17280. this._containerInnerSize.x / bounds.width
  17281. ).plus(
  17282. new $.Point(this._margins.left, this._margins.top)
  17283. );
  17284. },
  17285. // private
  17286. _pixelFromPoint: function(point, bounds) {
  17287. return this._pixelFromPointNoRotate(
  17288. point.rotate(this.getRotation(), this.getCenter(true)),
  17289. bounds);
  17290. },
  17291. /**
  17292. * Convert pixel coordinates to viewport coordinates.
  17293. * This method does not take rotation into account.
  17294. * Consider using pointFromPixel if you need to account for rotation.
  17295. * @param {OpenSeadragon.Point} pixel Pixel coordinates
  17296. * @param {Boolean} [current=false] - Pass true for the current location;
  17297. * defaults to false (target location).
  17298. * @returns {OpenSeadragon.Point}
  17299. */
  17300. pointFromPixelNoRotate: function(pixel, current) {
  17301. var bounds = this.getBoundsNoRotate(current);
  17302. return pixel.minus(
  17303. new $.Point(this._margins.left, this._margins.top)
  17304. ).divide(
  17305. this._containerInnerSize.x / bounds.width
  17306. ).plus(
  17307. bounds.getTopLeft()
  17308. );
  17309. },
  17310. /**
  17311. * Convert pixel coordinates to viewport coordinates.
  17312. * @param {OpenSeadragon.Point} pixel Pixel coordinates
  17313. * @param {Boolean} [current=false] - Pass true for the current location;
  17314. * defaults to false (target location).
  17315. * @returns {OpenSeadragon.Point}
  17316. */
  17317. pointFromPixel: function(pixel, current) {
  17318. return this.pointFromPixelNoRotate(pixel, current).rotate(
  17319. -this.getRotation(),
  17320. this.getCenter(true)
  17321. );
  17322. },
  17323. // private
  17324. _viewportToImageDelta: function( viewerX, viewerY ) {
  17325. var scale = this._contentBoundsNoRotate.width;
  17326. return new $.Point(
  17327. viewerX * this._contentSizeNoRotate.x / scale,
  17328. viewerY * this._contentSizeNoRotate.x / scale);
  17329. },
  17330. /**
  17331. * Translates from OpenSeadragon viewer coordinate system to image coordinate system.
  17332. * This method can be called either by passing X,Y coordinates or an
  17333. * OpenSeadragon.Point
  17334. * Note: not accurate with multi-image; use TiledImage.viewportToImageCoordinates instead.
  17335. * @function
  17336. * @param {(OpenSeadragon.Point|Number)} viewerX either a point or the X
  17337. * coordinate in viewport coordinate system.
  17338. * @param {Number} [viewerY] Y coordinate in viewport coordinate system.
  17339. * @return {OpenSeadragon.Point} a point representing the coordinates in the image.
  17340. */
  17341. viewportToImageCoordinates: function(viewerX, viewerY) {
  17342. if (viewerX instanceof $.Point) {
  17343. //they passed a point instead of individual components
  17344. return this.viewportToImageCoordinates(viewerX.x, viewerX.y);
  17345. }
  17346. if (this.viewer) {
  17347. var count = this.viewer.world.getItemCount();
  17348. if (count > 1) {
  17349. $.console.error('[Viewport.viewportToImageCoordinates] is not accurate ' +
  17350. 'with multi-image; use TiledImage.viewportToImageCoordinates instead.');
  17351. } else if (count === 1) {
  17352. // It is better to use TiledImage.viewportToImageCoordinates
  17353. // because this._contentBoundsNoRotate can not be relied on
  17354. // with clipping.
  17355. var item = this.viewer.world.getItemAt(0);
  17356. return item.viewportToImageCoordinates(viewerX, viewerY, true);
  17357. }
  17358. }
  17359. return this._viewportToImageDelta(
  17360. viewerX - this._contentBoundsNoRotate.x,
  17361. viewerY - this._contentBoundsNoRotate.y);
  17362. },
  17363. // private
  17364. _imageToViewportDelta: function( imageX, imageY ) {
  17365. var scale = this._contentBoundsNoRotate.width;
  17366. return new $.Point(
  17367. imageX / this._contentSizeNoRotate.x * scale,
  17368. imageY / this._contentSizeNoRotate.x * scale);
  17369. },
  17370. /**
  17371. * Translates from image coordinate system to OpenSeadragon viewer coordinate system
  17372. * This method can be called either by passing X,Y coordinates or an
  17373. * OpenSeadragon.Point
  17374. * Note: not accurate with multi-image; use TiledImage.imageToViewportCoordinates instead.
  17375. * @function
  17376. * @param {(OpenSeadragon.Point | Number)} imageX the point or the
  17377. * X coordinate in image coordinate system.
  17378. * @param {Number} [imageY] Y coordinate in image coordinate system.
  17379. * @return {OpenSeadragon.Point} a point representing the coordinates in the viewport.
  17380. */
  17381. imageToViewportCoordinates: function(imageX, imageY) {
  17382. if (imageX instanceof $.Point) {
  17383. //they passed a point instead of individual components
  17384. return this.imageToViewportCoordinates(imageX.x, imageX.y);
  17385. }
  17386. if (this.viewer) {
  17387. var count = this.viewer.world.getItemCount();
  17388. if (count > 1) {
  17389. $.console.error('[Viewport.imageToViewportCoordinates] is not accurate ' +
  17390. 'with multi-image; use TiledImage.imageToViewportCoordinates instead.');
  17391. } else if (count === 1) {
  17392. // It is better to use TiledImage.viewportToImageCoordinates
  17393. // because this._contentBoundsNoRotate can not be relied on
  17394. // with clipping.
  17395. var item = this.viewer.world.getItemAt(0);
  17396. return item.imageToViewportCoordinates(imageX, imageY, true);
  17397. }
  17398. }
  17399. var point = this._imageToViewportDelta(imageX, imageY);
  17400. point.x += this._contentBoundsNoRotate.x;
  17401. point.y += this._contentBoundsNoRotate.y;
  17402. return point;
  17403. },
  17404. /**
  17405. * Translates from a rectangle which describes a portion of the image in
  17406. * pixel coordinates to OpenSeadragon viewport rectangle coordinates.
  17407. * This method can be called either by passing X,Y,width,height or an
  17408. * OpenSeadragon.Rect
  17409. * Note: not accurate with multi-image; use TiledImage.imageToViewportRectangle instead.
  17410. * @function
  17411. * @param {(OpenSeadragon.Rect | Number)} imageX the rectangle or the X
  17412. * coordinate of the top left corner of the rectangle in image coordinate system.
  17413. * @param {Number} [imageY] the Y coordinate of the top left corner of the rectangle
  17414. * in image coordinate system.
  17415. * @param {Number} [pixelWidth] the width in pixel of the rectangle.
  17416. * @param {Number} [pixelHeight] the height in pixel of the rectangle.
  17417. * @returns {OpenSeadragon.Rect} This image's bounds in viewport coordinates
  17418. */
  17419. imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight) {
  17420. var rect = imageX;
  17421. if (!(rect instanceof $.Rect)) {
  17422. //they passed individual components instead of a rectangle
  17423. rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight);
  17424. }
  17425. if (this.viewer) {
  17426. var count = this.viewer.world.getItemCount();
  17427. if (count > 1) {
  17428. $.console.error('[Viewport.imageToViewportRectangle] is not accurate ' +
  17429. 'with multi-image; use TiledImage.imageToViewportRectangle instead.');
  17430. } else if (count === 1) {
  17431. // It is better to use TiledImage.imageToViewportRectangle
  17432. // because this._contentBoundsNoRotate can not be relied on
  17433. // with clipping.
  17434. var item = this.viewer.world.getItemAt(0);
  17435. return item.imageToViewportRectangle(
  17436. imageX, imageY, pixelWidth, pixelHeight, true);
  17437. }
  17438. }
  17439. var coordA = this.imageToViewportCoordinates(rect.x, rect.y);
  17440. var coordB = this._imageToViewportDelta(rect.width, rect.height);
  17441. return new $.Rect(
  17442. coordA.x,
  17443. coordA.y,
  17444. coordB.x,
  17445. coordB.y,
  17446. rect.degrees
  17447. );
  17448. },
  17449. /**
  17450. * Translates from a rectangle which describes a portion of
  17451. * the viewport in point coordinates to image rectangle coordinates.
  17452. * This method can be called either by passing X,Y,width,height or an
  17453. * OpenSeadragon.Rect
  17454. * Note: not accurate with multi-image; use TiledImage.viewportToImageRectangle instead.
  17455. * @function
  17456. * @param {(OpenSeadragon.Rect | Number)} viewerX either a rectangle or
  17457. * the X coordinate of the top left corner of the rectangle in viewport
  17458. * coordinate system.
  17459. * @param {Number} [viewerY] the Y coordinate of the top left corner of the rectangle
  17460. * in viewport coordinate system.
  17461. * @param {Number} [pointWidth] the width of the rectangle in viewport coordinate system.
  17462. * @param {Number} [pointHeight] the height of the rectangle in viewport coordinate system.
  17463. */
  17464. viewportToImageRectangle: function(viewerX, viewerY, pointWidth, pointHeight) {
  17465. var rect = viewerX;
  17466. if (!(rect instanceof $.Rect)) {
  17467. //they passed individual components instead of a rectangle
  17468. rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight);
  17469. }
  17470. if (this.viewer) {
  17471. var count = this.viewer.world.getItemCount();
  17472. if (count > 1) {
  17473. $.console.error('[Viewport.viewportToImageRectangle] is not accurate ' +
  17474. 'with multi-image; use TiledImage.viewportToImageRectangle instead.');
  17475. } else if (count === 1) {
  17476. // It is better to use TiledImage.viewportToImageCoordinates
  17477. // because this._contentBoundsNoRotate can not be relied on
  17478. // with clipping.
  17479. var item = this.viewer.world.getItemAt(0);
  17480. return item.viewportToImageRectangle(
  17481. viewerX, viewerY, pointWidth, pointHeight, true);
  17482. }
  17483. }
  17484. var coordA = this.viewportToImageCoordinates(rect.x, rect.y);
  17485. var coordB = this._viewportToImageDelta(rect.width, rect.height);
  17486. return new $.Rect(
  17487. coordA.x,
  17488. coordA.y,
  17489. coordB.x,
  17490. coordB.y,
  17491. rect.degrees
  17492. );
  17493. },
  17494. /**
  17495. * Convert pixel coordinates relative to the viewer element to image
  17496. * coordinates.
  17497. * Note: not accurate with multi-image.
  17498. * @param {OpenSeadragon.Point} pixel
  17499. * @returns {OpenSeadragon.Point}
  17500. */
  17501. viewerElementToImageCoordinates: function( pixel ) {
  17502. var point = this.pointFromPixel( pixel, true );
  17503. return this.viewportToImageCoordinates( point );
  17504. },
  17505. /**
  17506. * Convert pixel coordinates relative to the image to
  17507. * viewer element coordinates.
  17508. * Note: not accurate with multi-image.
  17509. * @param {OpenSeadragon.Point} pixel
  17510. * @returns {OpenSeadragon.Point}
  17511. */
  17512. imageToViewerElementCoordinates: function( pixel ) {
  17513. var point = this.imageToViewportCoordinates( pixel );
  17514. return this.pixelFromPoint( point, true );
  17515. },
  17516. /**
  17517. * Convert pixel coordinates relative to the window to image coordinates.
  17518. * Note: not accurate with multi-image.
  17519. * @param {OpenSeadragon.Point} pixel
  17520. * @returns {OpenSeadragon.Point}
  17521. */
  17522. windowToImageCoordinates: function(pixel) {
  17523. $.console.assert(this.viewer,
  17524. "[Viewport.windowToImageCoordinates] the viewport must have a viewer.");
  17525. var viewerCoordinates = pixel.minus(
  17526. $.getElementPosition(this.viewer.element));
  17527. return this.viewerElementToImageCoordinates(viewerCoordinates);
  17528. },
  17529. /**
  17530. * Convert image coordinates to pixel coordinates relative to the window.
  17531. * Note: not accurate with multi-image.
  17532. * @param {OpenSeadragon.Point} pixel
  17533. * @returns {OpenSeadragon.Point}
  17534. */
  17535. imageToWindowCoordinates: function(pixel) {
  17536. $.console.assert(this.viewer,
  17537. "[Viewport.imageToWindowCoordinates] the viewport must have a viewer.");
  17538. var viewerCoordinates = this.imageToViewerElementCoordinates(pixel);
  17539. return viewerCoordinates.plus(
  17540. $.getElementPosition(this.viewer.element));
  17541. },
  17542. /**
  17543. * Convert pixel coordinates relative to the viewer element to viewport
  17544. * coordinates.
  17545. * @param {OpenSeadragon.Point} pixel
  17546. * @returns {OpenSeadragon.Point}
  17547. */
  17548. viewerElementToViewportCoordinates: function( pixel ) {
  17549. return this.pointFromPixel( pixel, true );
  17550. },
  17551. /**
  17552. * Convert viewport coordinates to pixel coordinates relative to the
  17553. * viewer element.
  17554. * @param {OpenSeadragon.Point} point
  17555. * @returns {OpenSeadragon.Point}
  17556. */
  17557. viewportToViewerElementCoordinates: function( point ) {
  17558. return this.pixelFromPoint( point, true );
  17559. },
  17560. /**
  17561. * Convert a rectangle in pixel coordinates relative to the viewer element
  17562. * to viewport coordinates.
  17563. * @param {OpenSeadragon.Rect} rectangle the rectangle to convert
  17564. * @returns {OpenSeadragon.Rect} the converted rectangle
  17565. */
  17566. viewerElementToViewportRectangle: function(rectangle) {
  17567. return $.Rect.fromSummits(
  17568. this.pointFromPixel(rectangle.getTopLeft(), true),
  17569. this.pointFromPixel(rectangle.getTopRight(), true),
  17570. this.pointFromPixel(rectangle.getBottomLeft(), true)
  17571. );
  17572. },
  17573. /**
  17574. * Convert a rectangle in viewport coordinates to pixel coordinates relative
  17575. * to the viewer element.
  17576. * @param {OpenSeadragon.Rect} rectangle the rectangle to convert
  17577. * @returns {OpenSeadragon.Rect} the converted rectangle
  17578. */
  17579. viewportToViewerElementRectangle: function(rectangle) {
  17580. return $.Rect.fromSummits(
  17581. this.pixelFromPoint(rectangle.getTopLeft(), true),
  17582. this.pixelFromPoint(rectangle.getTopRight(), true),
  17583. this.pixelFromPoint(rectangle.getBottomLeft(), true)
  17584. );
  17585. },
  17586. /**
  17587. * Convert pixel coordinates relative to the window to viewport coordinates.
  17588. * @param {OpenSeadragon.Point} pixel
  17589. * @returns {OpenSeadragon.Point}
  17590. */
  17591. windowToViewportCoordinates: function(pixel) {
  17592. $.console.assert(this.viewer,
  17593. "[Viewport.windowToViewportCoordinates] the viewport must have a viewer.");
  17594. var viewerCoordinates = pixel.minus(
  17595. $.getElementPosition(this.viewer.element));
  17596. return this.viewerElementToViewportCoordinates(viewerCoordinates);
  17597. },
  17598. /**
  17599. * Convert viewport coordinates to pixel coordinates relative to the window.
  17600. * @param {OpenSeadragon.Point} point
  17601. * @returns {OpenSeadragon.Point}
  17602. */
  17603. viewportToWindowCoordinates: function(point) {
  17604. $.console.assert(this.viewer,
  17605. "[Viewport.viewportToWindowCoordinates] the viewport must have a viewer.");
  17606. var viewerCoordinates = this.viewportToViewerElementCoordinates(point);
  17607. return viewerCoordinates.plus(
  17608. $.getElementPosition(this.viewer.element));
  17609. },
  17610. /**
  17611. * Convert a viewport zoom to an image zoom.
  17612. * Image zoom: ratio of the original image size to displayed image size.
  17613. * 1 means original image size, 0.5 half size...
  17614. * Viewport zoom: ratio of the displayed image's width to viewport's width.
  17615. * 1 means identical width, 2 means image's width is twice the viewport's width...
  17616. * Note: not accurate with multi-image.
  17617. * @function
  17618. * @param {Number} viewportZoom The viewport zoom
  17619. * target zoom.
  17620. * @returns {Number} imageZoom The image zoom
  17621. */
  17622. viewportToImageZoom: function(viewportZoom) {
  17623. if (this.viewer) {
  17624. var count = this.viewer.world.getItemCount();
  17625. if (count > 1) {
  17626. $.console.error('[Viewport.viewportToImageZoom] is not ' +
  17627. 'accurate with multi-image.');
  17628. } else if (count === 1) {
  17629. // It is better to use TiledImage.viewportToImageZoom
  17630. // because this._contentBoundsNoRotate can not be relied on
  17631. // with clipping.
  17632. var item = this.viewer.world.getItemAt(0);
  17633. return item.viewportToImageZoom(viewportZoom);
  17634. }
  17635. }
  17636. var imageWidth = this._contentSizeNoRotate.x;
  17637. var containerWidth = this._containerInnerSize.x;
  17638. var scale = this._contentBoundsNoRotate.width;
  17639. var viewportToImageZoomRatio = (containerWidth / imageWidth) * scale;
  17640. return viewportZoom * viewportToImageZoomRatio;
  17641. },
  17642. /**
  17643. * Convert an image zoom to a viewport zoom.
  17644. * Image zoom: ratio of the original image size to displayed image size.
  17645. * 1 means original image size, 0.5 half size...
  17646. * Viewport zoom: ratio of the displayed image's width to viewport's width.
  17647. * 1 means identical width, 2 means image's width is twice the viewport's width...
  17648. * Note: not accurate with multi-image.
  17649. * @function
  17650. * @param {Number} imageZoom The image zoom
  17651. * target zoom.
  17652. * @returns {Number} viewportZoom The viewport zoom
  17653. */
  17654. imageToViewportZoom: function(imageZoom) {
  17655. if (this.viewer) {
  17656. var count = this.viewer.world.getItemCount();
  17657. if (count > 1) {
  17658. $.console.error('[Viewport.imageToViewportZoom] is not accurate ' +
  17659. 'with multi-image.');
  17660. } else if (count === 1) {
  17661. // It is better to use TiledImage.imageToViewportZoom
  17662. // because this._contentBoundsNoRotate can not be relied on
  17663. // with clipping.
  17664. var item = this.viewer.world.getItemAt(0);
  17665. return item.imageToViewportZoom(imageZoom);
  17666. }
  17667. }
  17668. var imageWidth = this._contentSizeNoRotate.x;
  17669. var containerWidth = this._containerInnerSize.x;
  17670. var scale = this._contentBoundsNoRotate.width;
  17671. var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale;
  17672. return imageZoom * viewportToImageZoomRatio;
  17673. },
  17674. /**
  17675. * Toggles flip state and demands a new drawing on navigator and viewer objects.
  17676. * @function
  17677. * @return {OpenSeadragon.Viewport} Chainable.
  17678. */
  17679. toggleFlip: function() {
  17680. this.setFlip(!this.getFlip());
  17681. return this;
  17682. },
  17683. /**
  17684. * Get flip state stored on viewport.
  17685. * @function
  17686. * @return {Boolean} Flip state.
  17687. */
  17688. getFlip: function() {
  17689. return this.flipped;
  17690. },
  17691. /**
  17692. * Sets flip state according to the state input argument.
  17693. * @function
  17694. * @param {Boolean} state - Flip state to set.
  17695. * @return {OpenSeadragon.Viewport} Chainable.
  17696. */
  17697. setFlip: function( state ) {
  17698. if ( this.flipped === state ) {
  17699. return this;
  17700. }
  17701. this.flipped = state;
  17702. if(this.viewer.navigator){
  17703. this.viewer.navigator.setFlip(this.getFlip());
  17704. }
  17705. this.viewer.forceRedraw();
  17706. /**
  17707. * Raised when flip state has been changed.
  17708. *
  17709. * @event flip
  17710. * @memberof OpenSeadragon.Viewer
  17711. * @type {object}
  17712. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  17713. * @property {Number} flipped - The flip state after this change.
  17714. * @property {?Object} userData - Arbitrary subscriber-defined object.
  17715. */
  17716. this.viewer.raiseEvent('flip', {"flipped": state});
  17717. return this;
  17718. }
  17719. };
  17720. }( OpenSeadragon ));
  17721. /*
  17722. * OpenSeadragon - TiledImage
  17723. *
  17724. * Copyright (C) 2009 CodePlex Foundation
  17725. * Copyright (C) 2010-2013 OpenSeadragon contributors
  17726. *
  17727. * Redistribution and use in source and binary forms, with or without
  17728. * modification, are permitted provided that the following conditions are
  17729. * met:
  17730. *
  17731. * - Redistributions of source code must retain the above copyright notice,
  17732. * this list of conditions and the following disclaimer.
  17733. *
  17734. * - Redistributions in binary form must reproduce the above copyright
  17735. * notice, this list of conditions and the following disclaimer in the
  17736. * documentation and/or other materials provided with the distribution.
  17737. *
  17738. * - Neither the name of CodePlex Foundation nor the names of its
  17739. * contributors may be used to endorse or promote products derived from
  17740. * this software without specific prior written permission.
  17741. *
  17742. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  17743. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  17744. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  17745. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  17746. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  17747. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  17748. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  17749. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  17750. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  17751. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  17752. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  17753. */
  17754. (function( $ ){
  17755. /**
  17756. * You shouldn't have to create a TiledImage instance directly; get it asynchronously by
  17757. * using {@link OpenSeadragon.Viewer#open} or {@link OpenSeadragon.Viewer#addTiledImage} instead.
  17758. * @class TiledImage
  17759. * @memberof OpenSeadragon
  17760. * @extends OpenSeadragon.EventSource
  17761. * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
  17762. * A new instance is created for each TileSource opened.
  17763. * @param {Object} options - Configuration for this TiledImage.
  17764. * @param {OpenSeadragon.TileSource} options.source - The TileSource that defines this TiledImage.
  17765. * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this TiledImage.
  17766. * @param {OpenSeadragon.TileCache} options.tileCache - The TileCache for this TiledImage to use.
  17767. * @param {OpenSeadragon.Drawer} options.drawer - The Drawer for this TiledImage to draw onto.
  17768. * @param {OpenSeadragon.ImageLoader} options.imageLoader - The ImageLoader for this TiledImage to use.
  17769. * @param {Number} [options.x=0] - Left position, in viewport coordinates.
  17770. * @param {Number} [options.y=0] - Top position, in viewport coordinates.
  17771. * @param {Number} [options.width=1] - Width, in viewport coordinates.
  17772. * @param {Number} [options.height] - Height, in viewport coordinates.
  17773. * @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates
  17774. * to fit the image into. If specified, x, y, width and height get ignored.
  17775. * @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER]
  17776. * How to anchor the image in the bounds if options.fitBounds is set.
  17777. * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to
  17778. * (portions of the image outside of this area will not be visible). Only works on
  17779. * browsers that support the HTML5 canvas.
  17780. * @param {Number} [options.springStiffness] - See {@link OpenSeadragon.Options}.
  17781. * @param {Boolean} [options.animationTime] - See {@link OpenSeadragon.Options}.
  17782. * @param {Number} [options.minZoomImageRatio] - See {@link OpenSeadragon.Options}.
  17783. * @param {Boolean} [options.wrapHorizontal] - See {@link OpenSeadragon.Options}.
  17784. * @param {Boolean} [options.wrapVertical] - See {@link OpenSeadragon.Options}.
  17785. * @param {Boolean} [options.immediateRender] - See {@link OpenSeadragon.Options}.
  17786. * @param {Number} [options.blendTime] - See {@link OpenSeadragon.Options}.
  17787. * @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}.
  17788. * @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}.
  17789. * @param {Number} [options.smoothTileEdgesMinZoom] - See {@link OpenSeadragon.Options}.
  17790. * @param {Boolean} [options.iOSDevice] - See {@link OpenSeadragon.Options}.
  17791. * @param {Number} [options.opacity=1] - Set to draw at proportional opacity. If zero, images will not draw.
  17792. * @param {Boolean} [options.preload=false] - Set true to load even when the image is hidden by zero opacity.
  17793. * @param {String} [options.compositeOperation] - How the image is composited onto other images; see compositeOperation in {@link OpenSeadragon.Options} for possible values.
  17794. * @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.
  17795. * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
  17796. * @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}.
  17797. * @param {Boolean} [options.ajaxWithCredentials] - See {@link OpenSeadragon.Options}.
  17798. * @param {Boolean} [options.loadTilesWithAjax]
  17799. * Whether to load tile data using AJAX requests.
  17800. * Defaults to the setting in {@link OpenSeadragon.Options}.
  17801. * @param {Object} [options.ajaxHeaders={}]
  17802. * A set of headers to include when making tile AJAX requests.
  17803. */
  17804. $.TiledImage = function( options ) {
  17805. var _this = this;
  17806. /**
  17807. * The {@link OpenSeadragon.TileSource} that defines this TiledImage.
  17808. * @member {OpenSeadragon.TileSource} source
  17809. * @memberof OpenSeadragon.TiledImage#
  17810. */
  17811. $.console.assert( options.tileCache, "[TiledImage] options.tileCache is required" );
  17812. $.console.assert( options.drawer, "[TiledImage] options.drawer is required" );
  17813. $.console.assert( options.viewer, "[TiledImage] options.viewer is required" );
  17814. $.console.assert( options.imageLoader, "[TiledImage] options.imageLoader is required" );
  17815. $.console.assert( options.source, "[TiledImage] options.source is required" );
  17816. $.console.assert(!options.clip || options.clip instanceof $.Rect,
  17817. "[TiledImage] options.clip must be an OpenSeadragon.Rect if present");
  17818. $.EventSource.call( this );
  17819. this._tileCache = options.tileCache;
  17820. delete options.tileCache;
  17821. this._drawer = options.drawer;
  17822. delete options.drawer;
  17823. this._imageLoader = options.imageLoader;
  17824. delete options.imageLoader;
  17825. if (options.clip instanceof $.Rect) {
  17826. this._clip = options.clip.clone();
  17827. }
  17828. delete options.clip;
  17829. var x = options.x || 0;
  17830. delete options.x;
  17831. var y = options.y || 0;
  17832. delete options.y;
  17833. // Ratio of zoomable image height to width.
  17834. this.normHeight = options.source.dimensions.y / options.source.dimensions.x;
  17835. this.contentAspectX = options.source.dimensions.x / options.source.dimensions.y;
  17836. var scale = 1;
  17837. if ( options.width ) {
  17838. scale = options.width;
  17839. delete options.width;
  17840. if ( options.height ) {
  17841. $.console.error( "specifying both width and height to a tiledImage is not supported" );
  17842. delete options.height;
  17843. }
  17844. } else if ( options.height ) {
  17845. scale = options.height / this.normHeight;
  17846. delete options.height;
  17847. }
  17848. var fitBounds = options.fitBounds;
  17849. delete options.fitBounds;
  17850. var fitBoundsPlacement = options.fitBoundsPlacement || OpenSeadragon.Placement.CENTER;
  17851. delete options.fitBoundsPlacement;
  17852. var degrees = options.degrees || 0;
  17853. delete options.degrees;
  17854. $.extend( true, this, {
  17855. //internal state properties
  17856. viewer: null,
  17857. tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile.
  17858. coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas have been drawn.
  17859. loadingCoverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas are loaded or are being loaded/blended.
  17860. lastDrawn: [], // An unordered list of Tiles drawn last frame.
  17861. lastResetTime: 0, // Last time for which the tiledImage was reset.
  17862. _midDraw: false, // Is the tiledImage currently updating the viewport?
  17863. _needsDraw: true, // Does the tiledImage need to update the viewport again?
  17864. _hasOpaqueTile: false, // Do we have even one fully opaque tile?
  17865. _tilesLoading: 0, // The number of pending tile requests.
  17866. //configurable settings
  17867. springStiffness: $.DEFAULT_SETTINGS.springStiffness,
  17868. animationTime: $.DEFAULT_SETTINGS.animationTime,
  17869. minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
  17870. wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
  17871. wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
  17872. immediateRender: $.DEFAULT_SETTINGS.immediateRender,
  17873. blendTime: $.DEFAULT_SETTINGS.blendTime,
  17874. alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,
  17875. minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,
  17876. smoothTileEdgesMinZoom: $.DEFAULT_SETTINGS.smoothTileEdgesMinZoom,
  17877. iOSDevice: $.DEFAULT_SETTINGS.iOSDevice,
  17878. debugMode: $.DEFAULT_SETTINGS.debugMode,
  17879. crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
  17880. ajaxWithCredentials: $.DEFAULT_SETTINGS.ajaxWithCredentials,
  17881. placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
  17882. opacity: $.DEFAULT_SETTINGS.opacity,
  17883. preload: $.DEFAULT_SETTINGS.preload,
  17884. compositeOperation: $.DEFAULT_SETTINGS.compositeOperation
  17885. }, options );
  17886. this._preload = this.preload;
  17887. delete this.preload;
  17888. this._fullyLoaded = false;
  17889. this._xSpring = new $.Spring({
  17890. initial: x,
  17891. springStiffness: this.springStiffness,
  17892. animationTime: this.animationTime
  17893. });
  17894. this._ySpring = new $.Spring({
  17895. initial: y,
  17896. springStiffness: this.springStiffness,
  17897. animationTime: this.animationTime
  17898. });
  17899. this._scaleSpring = new $.Spring({
  17900. initial: scale,
  17901. springStiffness: this.springStiffness,
  17902. animationTime: this.animationTime
  17903. });
  17904. this._degreesSpring = new $.Spring({
  17905. initial: degrees,
  17906. springStiffness: this.springStiffness,
  17907. animationTime: this.animationTime
  17908. });
  17909. this._updateForScale();
  17910. if (fitBounds) {
  17911. this.fitBounds(fitBounds, fitBoundsPlacement, true);
  17912. }
  17913. // We need a callback to give image manipulation a chance to happen
  17914. this._drawingHandler = function(args) {
  17915. /**
  17916. * This event is fired just before the tile is drawn giving the application a chance to alter the image.
  17917. *
  17918. * NOTE: This event is only fired when the drawer is using a &lt;canvas&gt;.
  17919. *
  17920. * @event tile-drawing
  17921. * @memberof OpenSeadragon.Viewer
  17922. * @type {object}
  17923. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  17924. * @property {OpenSeadragon.Tile} tile - The Tile being drawn.
  17925. * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
  17926. * @property {OpenSeadragon.Tile} context - The HTML canvas context being drawn into.
  17927. * @property {OpenSeadragon.Tile} rendered - The HTML canvas context containing the tile imagery.
  17928. * @property {?Object} userData - Arbitrary subscriber-defined object.
  17929. */
  17930. _this.viewer.raiseEvent('tile-drawing', $.extend({
  17931. tiledImage: _this
  17932. }, args));
  17933. };
  17934. };
  17935. $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.TiledImage.prototype */{
  17936. /**
  17937. * @returns {Boolean} Whether the TiledImage needs to be drawn.
  17938. */
  17939. needsDraw: function() {
  17940. return this._needsDraw;
  17941. },
  17942. /**
  17943. * @returns {Boolean} Whether all tiles necessary for this TiledImage to draw at the current view have been loaded.
  17944. */
  17945. getFullyLoaded: function() {
  17946. return this._fullyLoaded;
  17947. },
  17948. // private
  17949. _setFullyLoaded: function(flag) {
  17950. if (flag === this._fullyLoaded) {
  17951. return;
  17952. }
  17953. this._fullyLoaded = flag;
  17954. /**
  17955. * Fired when the TiledImage's "fully loaded" flag (whether all tiles necessary for this TiledImage
  17956. * to draw at the current view have been loaded) changes.
  17957. *
  17958. * @event fully-loaded-change
  17959. * @memberof OpenSeadragon.TiledImage
  17960. * @type {object}
  17961. * @property {Boolean} fullyLoaded - The new "fully loaded" value.
  17962. * @property {OpenSeadragon.TiledImage} eventSource - A reference to the TiledImage which raised the event.
  17963. * @property {?Object} userData - Arbitrary subscriber-defined object.
  17964. */
  17965. this.raiseEvent('fully-loaded-change', {
  17966. fullyLoaded: this._fullyLoaded
  17967. });
  17968. },
  17969. /**
  17970. * Clears all tiles and triggers an update on the next call to
  17971. * {@link OpenSeadragon.TiledImage#update}.
  17972. */
  17973. reset: function() {
  17974. this._tileCache.clearTilesFor(this);
  17975. this.lastResetTime = $.now();
  17976. this._needsDraw = true;
  17977. },
  17978. /**
  17979. * Updates the TiledImage's bounds, animating if needed.
  17980. * @returns {Boolean} Whether the TiledImage animated.
  17981. */
  17982. update: function() {
  17983. var xUpdated = this._xSpring.update();
  17984. var yUpdated = this._ySpring.update();
  17985. var scaleUpdated = this._scaleSpring.update();
  17986. var degreesUpdated = this._degreesSpring.update();
  17987. if (xUpdated || yUpdated || scaleUpdated || degreesUpdated) {
  17988. this._updateForScale();
  17989. this._needsDraw = true;
  17990. return true;
  17991. }
  17992. return false;
  17993. },
  17994. /**
  17995. * Draws the TiledImage to its Drawer.
  17996. */
  17997. draw: function() {
  17998. if (this.opacity !== 0 || this._preload) {
  17999. this._midDraw = true;
  18000. this._updateViewport();
  18001. this._midDraw = false;
  18002. }
  18003. // Images with opacity 0 should not need to be drawn in future. this._needsDraw = false is set in this._updateViewport() for other images.
  18004. else {
  18005. this._needsDraw = false;
  18006. }
  18007. },
  18008. /**
  18009. * Destroy the TiledImage (unload current loaded tiles).
  18010. */
  18011. destroy: function() {
  18012. this.reset();
  18013. },
  18014. /**
  18015. * Get this TiledImage's bounds in viewport coordinates.
  18016. * @param {Boolean} [current=false] - Pass true for the current location;
  18017. * false for target location.
  18018. * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates.
  18019. */
  18020. getBounds: function(current) {
  18021. return this.getBoundsNoRotate(current)
  18022. .rotate(this.getRotation(current), this._getRotationPoint(current));
  18023. },
  18024. /**
  18025. * Get this TiledImage's bounds in viewport coordinates without taking
  18026. * rotation into account.
  18027. * @param {Boolean} [current=false] - Pass true for the current location;
  18028. * false for target location.
  18029. * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates.
  18030. */
  18031. getBoundsNoRotate: function(current) {
  18032. return current ?
  18033. new $.Rect(
  18034. this._xSpring.current.value,
  18035. this._ySpring.current.value,
  18036. this._worldWidthCurrent,
  18037. this._worldHeightCurrent) :
  18038. new $.Rect(
  18039. this._xSpring.target.value,
  18040. this._ySpring.target.value,
  18041. this._worldWidthTarget,
  18042. this._worldHeightTarget);
  18043. },
  18044. // deprecated
  18045. getWorldBounds: function() {
  18046. $.console.error('[TiledImage.getWorldBounds] is deprecated; use TiledImage.getBounds instead');
  18047. return this.getBounds();
  18048. },
  18049. /**
  18050. * Get the bounds of the displayed part of the tiled image.
  18051. * @param {Boolean} [current=false] Pass true for the current location,
  18052. * false for the target location.
  18053. * @returns {$.Rect} The clipped bounds in viewport coordinates.
  18054. */
  18055. getClippedBounds: function(current) {
  18056. var bounds = this.getBoundsNoRotate(current);
  18057. if (this._clip) {
  18058. var worldWidth = current ?
  18059. this._worldWidthCurrent : this._worldWidthTarget;
  18060. var ratio = worldWidth / this.source.dimensions.x;
  18061. var clip = this._clip.times(ratio);
  18062. bounds = new $.Rect(
  18063. bounds.x + clip.x,
  18064. bounds.y + clip.y,
  18065. clip.width,
  18066. clip.height);
  18067. }
  18068. return bounds.rotate(this.getRotation(current), this._getRotationPoint(current));
  18069. },
  18070. /**
  18071. * @returns {OpenSeadragon.Point} This TiledImage's content size, in original pixels.
  18072. */
  18073. getContentSize: function() {
  18074. return new $.Point(this.source.dimensions.x, this.source.dimensions.y);
  18075. },
  18076. // private
  18077. _viewportToImageDelta: function( viewerX, viewerY, current ) {
  18078. var scale = (current ? this._scaleSpring.current.value : this._scaleSpring.target.value);
  18079. return new $.Point(viewerX * (this.source.dimensions.x / scale),
  18080. viewerY * ((this.source.dimensions.y * this.contentAspectX) / scale));
  18081. },
  18082. /**
  18083. * Translates from OpenSeadragon viewer coordinate system to image coordinate system.
  18084. * This method can be called either by passing X,Y coordinates or an {@link OpenSeadragon.Point}.
  18085. * @param {Number|OpenSeadragon.Point} viewerX - The X coordinate or point in viewport coordinate system.
  18086. * @param {Number} [viewerY] - The Y coordinate in viewport coordinate system.
  18087. * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
  18088. * @return {OpenSeadragon.Point} A point representing the coordinates in the image.
  18089. */
  18090. viewportToImageCoordinates: function(viewerX, viewerY, current) {
  18091. var point;
  18092. if (viewerX instanceof $.Point) {
  18093. //they passed a point instead of individual components
  18094. current = viewerY;
  18095. point = viewerX;
  18096. } else {
  18097. point = new $.Point(viewerX, viewerY);
  18098. }
  18099. point = point.rotate(-this.getRotation(current), this._getRotationPoint(current));
  18100. return current ?
  18101. this._viewportToImageDelta(
  18102. point.x - this._xSpring.current.value,
  18103. point.y - this._ySpring.current.value) :
  18104. this._viewportToImageDelta(
  18105. point.x - this._xSpring.target.value,
  18106. point.y - this._ySpring.target.value);
  18107. },
  18108. // private
  18109. _imageToViewportDelta: function( imageX, imageY, current ) {
  18110. var scale = (current ? this._scaleSpring.current.value : this._scaleSpring.target.value);
  18111. return new $.Point((imageX / this.source.dimensions.x) * scale,
  18112. (imageY / this.source.dimensions.y / this.contentAspectX) * scale);
  18113. },
  18114. /**
  18115. * Translates from image coordinate system to OpenSeadragon viewer coordinate system
  18116. * This method can be called either by passing X,Y coordinates or an {@link OpenSeadragon.Point}.
  18117. * @param {Number|OpenSeadragon.Point} imageX - The X coordinate or point in image coordinate system.
  18118. * @param {Number} [imageY] - The Y coordinate in image coordinate system.
  18119. * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
  18120. * @return {OpenSeadragon.Point} A point representing the coordinates in the viewport.
  18121. */
  18122. imageToViewportCoordinates: function(imageX, imageY, current) {
  18123. if (imageX instanceof $.Point) {
  18124. //they passed a point instead of individual components
  18125. current = imageY;
  18126. imageY = imageX.y;
  18127. imageX = imageX.x;
  18128. }
  18129. var point = this._imageToViewportDelta(imageX, imageY);
  18130. if (current) {
  18131. point.x += this._xSpring.current.value;
  18132. point.y += this._ySpring.current.value;
  18133. } else {
  18134. point.x += this._xSpring.target.value;
  18135. point.y += this._ySpring.target.value;
  18136. }
  18137. return point.rotate(this.getRotation(current), this._getRotationPoint(current));
  18138. },
  18139. /**
  18140. * Translates from a rectangle which describes a portion of the image in
  18141. * pixel coordinates to OpenSeadragon viewport rectangle coordinates.
  18142. * This method can be called either by passing X,Y,width,height or an {@link OpenSeadragon.Rect}.
  18143. * @param {Number|OpenSeadragon.Rect} imageX - The left coordinate or rectangle in image coordinate system.
  18144. * @param {Number} [imageY] - The top coordinate in image coordinate system.
  18145. * @param {Number} [pixelWidth] - The width in pixel of the rectangle.
  18146. * @param {Number} [pixelHeight] - The height in pixel of the rectangle.
  18147. * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
  18148. * @return {OpenSeadragon.Rect} A rect representing the coordinates in the viewport.
  18149. */
  18150. imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight, current) {
  18151. var rect = imageX;
  18152. if (rect instanceof $.Rect) {
  18153. //they passed a rect instead of individual components
  18154. current = imageY;
  18155. } else {
  18156. rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight);
  18157. }
  18158. var coordA = this.imageToViewportCoordinates(rect.getTopLeft(), current);
  18159. var coordB = this._imageToViewportDelta(rect.width, rect.height, current);
  18160. return new $.Rect(
  18161. coordA.x,
  18162. coordA.y,
  18163. coordB.x,
  18164. coordB.y,
  18165. rect.degrees + this.getRotation(current)
  18166. );
  18167. },
  18168. /**
  18169. * Translates from a rectangle which describes a portion of
  18170. * the viewport in point coordinates to image rectangle coordinates.
  18171. * This method can be called either by passing X,Y,width,height or an {@link OpenSeadragon.Rect}.
  18172. * @param {Number|OpenSeadragon.Rect} viewerX - The left coordinate or rectangle in viewport coordinate system.
  18173. * @param {Number} [viewerY] - The top coordinate in viewport coordinate system.
  18174. * @param {Number} [pointWidth] - The width in viewport coordinate system.
  18175. * @param {Number} [pointHeight] - The height in viewport coordinate system.
  18176. * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
  18177. * @return {OpenSeadragon.Rect} A rect representing the coordinates in the image.
  18178. */
  18179. viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight, current ) {
  18180. var rect = viewerX;
  18181. if (viewerX instanceof $.Rect) {
  18182. //they passed a rect instead of individual components
  18183. current = viewerY;
  18184. } else {
  18185. rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight);
  18186. }
  18187. var coordA = this.viewportToImageCoordinates(rect.getTopLeft(), current);
  18188. var coordB = this._viewportToImageDelta(rect.width, rect.height, current);
  18189. return new $.Rect(
  18190. coordA.x,
  18191. coordA.y,
  18192. coordB.x,
  18193. coordB.y,
  18194. rect.degrees - this.getRotation(current)
  18195. );
  18196. },
  18197. /**
  18198. * Convert pixel coordinates relative to the viewer element to image
  18199. * coordinates.
  18200. * @param {OpenSeadragon.Point} pixel
  18201. * @returns {OpenSeadragon.Point}
  18202. */
  18203. viewerElementToImageCoordinates: function( pixel ) {
  18204. var point = this.viewport.pointFromPixel( pixel, true );
  18205. return this.viewportToImageCoordinates( point );
  18206. },
  18207. /**
  18208. * Convert pixel coordinates relative to the image to
  18209. * viewer element coordinates.
  18210. * @param {OpenSeadragon.Point} pixel
  18211. * @returns {OpenSeadragon.Point}
  18212. */
  18213. imageToViewerElementCoordinates: function( pixel ) {
  18214. var point = this.imageToViewportCoordinates( pixel );
  18215. return this.viewport.pixelFromPoint( point, true );
  18216. },
  18217. /**
  18218. * Convert pixel coordinates relative to the window to image coordinates.
  18219. * @param {OpenSeadragon.Point} pixel
  18220. * @returns {OpenSeadragon.Point}
  18221. */
  18222. windowToImageCoordinates: function( pixel ) {
  18223. var viewerCoordinates = pixel.minus(
  18224. OpenSeadragon.getElementPosition( this.viewer.element ));
  18225. return this.viewerElementToImageCoordinates( viewerCoordinates );
  18226. },
  18227. /**
  18228. * Convert image coordinates to pixel coordinates relative to the window.
  18229. * @param {OpenSeadragon.Point} pixel
  18230. * @returns {OpenSeadragon.Point}
  18231. */
  18232. imageToWindowCoordinates: function( pixel ) {
  18233. var viewerCoordinates = this.imageToViewerElementCoordinates( pixel );
  18234. return viewerCoordinates.plus(
  18235. OpenSeadragon.getElementPosition( this.viewer.element ));
  18236. },
  18237. // private
  18238. // Convert rectangle in viewport coordinates to this tiled image point
  18239. // coordinates (x in [0, 1] and y in [0, aspectRatio])
  18240. _viewportToTiledImageRectangle: function(rect) {
  18241. var scale = this._scaleSpring.current.value;
  18242. rect = rect.rotate(-this.getRotation(true), this._getRotationPoint(true));
  18243. return new $.Rect(
  18244. (rect.x - this._xSpring.current.value) / scale,
  18245. (rect.y - this._ySpring.current.value) / scale,
  18246. rect.width / scale,
  18247. rect.height / scale,
  18248. rect.degrees);
  18249. },
  18250. /**
  18251. * Convert a viewport zoom to an image zoom.
  18252. * Image zoom: ratio of the original image size to displayed image size.
  18253. * 1 means original image size, 0.5 half size...
  18254. * Viewport zoom: ratio of the displayed image's width to viewport's width.
  18255. * 1 means identical width, 2 means image's width is twice the viewport's width...
  18256. * @function
  18257. * @param {Number} viewportZoom The viewport zoom
  18258. * @returns {Number} imageZoom The image zoom
  18259. */
  18260. viewportToImageZoom: function( viewportZoom ) {
  18261. var ratio = this._scaleSpring.current.value *
  18262. this.viewport._containerInnerSize.x / this.source.dimensions.x;
  18263. return ratio * viewportZoom;
  18264. },
  18265. /**
  18266. * Convert an image zoom to a viewport zoom.
  18267. * Image zoom: ratio of the original image size to displayed image size.
  18268. * 1 means original image size, 0.5 half size...
  18269. * Viewport zoom: ratio of the displayed image's width to viewport's width.
  18270. * 1 means identical width, 2 means image's width is twice the viewport's width...
  18271. * Note: not accurate with multi-image.
  18272. * @function
  18273. * @param {Number} imageZoom The image zoom
  18274. * @returns {Number} viewportZoom The viewport zoom
  18275. */
  18276. imageToViewportZoom: function( imageZoom ) {
  18277. var ratio = this._scaleSpring.current.value *
  18278. this.viewport._containerInnerSize.x / this.source.dimensions.x;
  18279. return imageZoom / ratio;
  18280. },
  18281. /**
  18282. * Sets the TiledImage's position in the world.
  18283. * @param {OpenSeadragon.Point} position - The new position, in viewport coordinates.
  18284. * @param {Boolean} [immediately=false] - Whether to animate to the new position or snap immediately.
  18285. * @fires OpenSeadragon.TiledImage.event:bounds-change
  18286. */
  18287. setPosition: function(position, immediately) {
  18288. var sameTarget = (this._xSpring.target.value === position.x &&
  18289. this._ySpring.target.value === position.y);
  18290. if (immediately) {
  18291. if (sameTarget && this._xSpring.current.value === position.x &&
  18292. this._ySpring.current.value === position.y) {
  18293. return;
  18294. }
  18295. this._xSpring.resetTo(position.x);
  18296. this._ySpring.resetTo(position.y);
  18297. this._needsDraw = true;
  18298. } else {
  18299. if (sameTarget) {
  18300. return;
  18301. }
  18302. this._xSpring.springTo(position.x);
  18303. this._ySpring.springTo(position.y);
  18304. this._needsDraw = true;
  18305. }
  18306. if (!sameTarget) {
  18307. this._raiseBoundsChange();
  18308. }
  18309. },
  18310. /**
  18311. * Sets the TiledImage's width in the world, adjusting the height to match based on aspect ratio.
  18312. * @param {Number} width - The new width, in viewport coordinates.
  18313. * @param {Boolean} [immediately=false] - Whether to animate to the new size or snap immediately.
  18314. * @fires OpenSeadragon.TiledImage.event:bounds-change
  18315. */
  18316. setWidth: function(width, immediately) {
  18317. this._setScale(width, immediately);
  18318. },
  18319. /**
  18320. * Sets the TiledImage's height in the world, adjusting the width to match based on aspect ratio.
  18321. * @param {Number} height - The new height, in viewport coordinates.
  18322. * @param {Boolean} [immediately=false] - Whether to animate to the new size or snap immediately.
  18323. * @fires OpenSeadragon.TiledImage.event:bounds-change
  18324. */
  18325. setHeight: function(height, immediately) {
  18326. this._setScale(height / this.normHeight, immediately);
  18327. },
  18328. /**
  18329. * Positions and scales the TiledImage to fit in the specified bounds.
  18330. * Note: this method fires OpenSeadragon.TiledImage.event:bounds-change
  18331. * twice
  18332. * @param {OpenSeadragon.Rect} bounds The bounds to fit the image into.
  18333. * @param {OpenSeadragon.Placement} [anchor=OpenSeadragon.Placement.CENTER]
  18334. * How to anchor the image in the bounds.
  18335. * @param {Boolean} [immediately=false] Whether to animate to the new size
  18336. * or snap immediately.
  18337. * @fires OpenSeadragon.TiledImage.event:bounds-change
  18338. */
  18339. fitBounds: function(bounds, anchor, immediately) {
  18340. anchor = anchor || $.Placement.CENTER;
  18341. var anchorProperties = $.Placement.properties[anchor];
  18342. var aspectRatio = this.contentAspectX;
  18343. var xOffset = 0;
  18344. var yOffset = 0;
  18345. var displayedWidthRatio = 1;
  18346. var displayedHeightRatio = 1;
  18347. if (this._clip) {
  18348. aspectRatio = this._clip.getAspectRatio();
  18349. displayedWidthRatio = this._clip.width / this.source.dimensions.x;
  18350. displayedHeightRatio = this._clip.height / this.source.dimensions.y;
  18351. if (bounds.getAspectRatio() > aspectRatio) {
  18352. xOffset = this._clip.x / this._clip.height * bounds.height;
  18353. yOffset = this._clip.y / this._clip.height * bounds.height;
  18354. } else {
  18355. xOffset = this._clip.x / this._clip.width * bounds.width;
  18356. yOffset = this._clip.y / this._clip.width * bounds.width;
  18357. }
  18358. }
  18359. if (bounds.getAspectRatio() > aspectRatio) {
  18360. // We will have margins on the X axis
  18361. var height = bounds.height / displayedHeightRatio;
  18362. var marginLeft = 0;
  18363. if (anchorProperties.isHorizontallyCentered) {
  18364. marginLeft = (bounds.width - bounds.height * aspectRatio) / 2;
  18365. } else if (anchorProperties.isRight) {
  18366. marginLeft = bounds.width - bounds.height * aspectRatio;
  18367. }
  18368. this.setPosition(
  18369. new $.Point(bounds.x - xOffset + marginLeft, bounds.y - yOffset),
  18370. immediately);
  18371. this.setHeight(height, immediately);
  18372. } else {
  18373. // We will have margins on the Y axis
  18374. var width = bounds.width / displayedWidthRatio;
  18375. var marginTop = 0;
  18376. if (anchorProperties.isVerticallyCentered) {
  18377. marginTop = (bounds.height - bounds.width / aspectRatio) / 2;
  18378. } else if (anchorProperties.isBottom) {
  18379. marginTop = bounds.height - bounds.width / aspectRatio;
  18380. }
  18381. this.setPosition(
  18382. new $.Point(bounds.x - xOffset, bounds.y - yOffset + marginTop),
  18383. immediately);
  18384. this.setWidth(width, immediately);
  18385. }
  18386. },
  18387. /**
  18388. * @returns {OpenSeadragon.Rect|null} The TiledImage's current clip rectangle,
  18389. * in image pixels, or null if none.
  18390. */
  18391. getClip: function() {
  18392. if (this._clip) {
  18393. return this._clip.clone();
  18394. }
  18395. return null;
  18396. },
  18397. /**
  18398. * @param {OpenSeadragon.Rect|null} newClip - An area, in image pixels, to clip to
  18399. * (portions of the image outside of this area will not be visible). Only works on
  18400. * browsers that support the HTML5 canvas.
  18401. * @fires OpenSeadragon.TiledImage.event:clip-change
  18402. */
  18403. setClip: function(newClip) {
  18404. $.console.assert(!newClip || newClip instanceof $.Rect,
  18405. "[TiledImage.setClip] newClip must be an OpenSeadragon.Rect or null");
  18406. if (newClip instanceof $.Rect) {
  18407. this._clip = newClip.clone();
  18408. } else {
  18409. this._clip = null;
  18410. }
  18411. this._needsDraw = true;
  18412. /**
  18413. * Raised when the TiledImage's clip is changed.
  18414. * @event clip-change
  18415. * @memberOf OpenSeadragon.TiledImage
  18416. * @type {object}
  18417. * @property {OpenSeadragon.TiledImage} eventSource - A reference to the
  18418. * TiledImage which raised the event.
  18419. * @property {?Object} userData - Arbitrary subscriber-defined object.
  18420. */
  18421. this.raiseEvent('clip-change');
  18422. },
  18423. /**
  18424. * @returns {Number} The TiledImage's current opacity.
  18425. */
  18426. getOpacity: function() {
  18427. return this.opacity;
  18428. },
  18429. /**
  18430. * @param {Number} opacity Opacity the tiled image should be drawn at.
  18431. * @fires OpenSeadragon.TiledImage.event:opacity-change
  18432. */
  18433. setOpacity: function(opacity) {
  18434. if (opacity === this.opacity) {
  18435. return;
  18436. }
  18437. this.opacity = opacity;
  18438. this._needsDraw = true;
  18439. /**
  18440. * Raised when the TiledImage's opacity is changed.
  18441. * @event opacity-change
  18442. * @memberOf OpenSeadragon.TiledImage
  18443. * @type {object}
  18444. * @property {Number} opacity - The new opacity value.
  18445. * @property {OpenSeadragon.TiledImage} eventSource - A reference to the
  18446. * TiledImage which raised the event.
  18447. * @property {?Object} userData - Arbitrary subscriber-defined object.
  18448. */
  18449. this.raiseEvent('opacity-change', {
  18450. opacity: this.opacity
  18451. });
  18452. },
  18453. /**
  18454. * @returns {Boolean} whether the tiledImage can load its tiles even when it has zero opacity.
  18455. */
  18456. getPreload: function() {
  18457. return this._preload;
  18458. },
  18459. /**
  18460. * Set true to load even when hidden. Set false to block loading when hidden.
  18461. */
  18462. setPreload: function(preload) {
  18463. this._preload = !!preload;
  18464. this._needsDraw = true;
  18465. },
  18466. /**
  18467. * Get the rotation of this tiled image in degrees.
  18468. * @param {Boolean} [current=false] True for current rotation, false for target.
  18469. * @returns {Number} the rotation of this tiled image in degrees.
  18470. */
  18471. getRotation: function(current) {
  18472. return current ?
  18473. this._degreesSpring.current.value :
  18474. this._degreesSpring.target.value;
  18475. },
  18476. /**
  18477. * Set the current rotation of this tiled image in degrees.
  18478. * @param {Number} degrees the rotation in degrees.
  18479. * @param {Boolean} [immediately=false] Whether to animate to the new angle
  18480. * or rotate immediately.
  18481. * @fires OpenSeadragon.TiledImage.event:bounds-change
  18482. */
  18483. setRotation: function(degrees, immediately) {
  18484. if (this._degreesSpring.target.value === degrees &&
  18485. this._degreesSpring.isAtTargetValue()) {
  18486. return;
  18487. }
  18488. if (immediately) {
  18489. this._degreesSpring.resetTo(degrees);
  18490. } else {
  18491. this._degreesSpring.springTo(degrees);
  18492. }
  18493. this._needsDraw = true;
  18494. this._raiseBoundsChange();
  18495. },
  18496. /**
  18497. * Get the point around which this tiled image is rotated
  18498. * @private
  18499. * @param {Boolean} current True for current rotation point, false for target.
  18500. * @returns {OpenSeadragon.Point}
  18501. */
  18502. _getRotationPoint: function(current) {
  18503. return this.getBoundsNoRotate(current).getCenter();
  18504. },
  18505. /**
  18506. * @returns {String} The TiledImage's current compositeOperation.
  18507. */
  18508. getCompositeOperation: function() {
  18509. return this.compositeOperation;
  18510. },
  18511. /**
  18512. * @param {String} compositeOperation the tiled image should be drawn with this globalCompositeOperation.
  18513. * @fires OpenSeadragon.TiledImage.event:composite-operation-change
  18514. */
  18515. setCompositeOperation: function(compositeOperation) {
  18516. if (compositeOperation === this.compositeOperation) {
  18517. return;
  18518. }
  18519. this.compositeOperation = compositeOperation;
  18520. this._needsDraw = true;
  18521. /**
  18522. * Raised when the TiledImage's opacity is changed.
  18523. * @event composite-operation-change
  18524. * @memberOf OpenSeadragon.TiledImage
  18525. * @type {object}
  18526. * @property {String} compositeOperation - The new compositeOperation value.
  18527. * @property {OpenSeadragon.TiledImage} eventSource - A reference to the
  18528. * TiledImage which raised the event.
  18529. * @property {?Object} userData - Arbitrary subscriber-defined object.
  18530. */
  18531. this.raiseEvent('composite-operation-change', {
  18532. compositeOperation: this.compositeOperation
  18533. });
  18534. },
  18535. // private
  18536. _setScale: function(scale, immediately) {
  18537. var sameTarget = (this._scaleSpring.target.value === scale);
  18538. if (immediately) {
  18539. if (sameTarget && this._scaleSpring.current.value === scale) {
  18540. return;
  18541. }
  18542. this._scaleSpring.resetTo(scale);
  18543. this._updateForScale();
  18544. this._needsDraw = true;
  18545. } else {
  18546. if (sameTarget) {
  18547. return;
  18548. }
  18549. this._scaleSpring.springTo(scale);
  18550. this._updateForScale();
  18551. this._needsDraw = true;
  18552. }
  18553. if (!sameTarget) {
  18554. this._raiseBoundsChange();
  18555. }
  18556. },
  18557. // private
  18558. _updateForScale: function() {
  18559. this._worldWidthTarget = this._scaleSpring.target.value;
  18560. this._worldHeightTarget = this.normHeight * this._scaleSpring.target.value;
  18561. this._worldWidthCurrent = this._scaleSpring.current.value;
  18562. this._worldHeightCurrent = this.normHeight * this._scaleSpring.current.value;
  18563. },
  18564. // private
  18565. _raiseBoundsChange: function() {
  18566. /**
  18567. * Raised when the TiledImage's bounds are changed.
  18568. * Note that this event is triggered only when the animation target is changed;
  18569. * not for every frame of animation.
  18570. * @event bounds-change
  18571. * @memberOf OpenSeadragon.TiledImage
  18572. * @type {object}
  18573. * @property {OpenSeadragon.TiledImage} eventSource - A reference to the
  18574. * TiledImage which raised the event.
  18575. * @property {?Object} userData - Arbitrary subscriber-defined object.
  18576. */
  18577. this.raiseEvent('bounds-change');
  18578. },
  18579. // private
  18580. _isBottomItem: function() {
  18581. return this.viewer.world.getItemAt(0) === this;
  18582. },
  18583. // private
  18584. _getLevelsInterval: function() {
  18585. var lowestLevel = Math.max(
  18586. this.source.minLevel,
  18587. Math.floor(Math.log(this.minZoomImageRatio) / Math.log(2))
  18588. );
  18589. var currentZeroRatio = this.viewport.deltaPixelsFromPointsNoRotate(
  18590. this.source.getPixelRatio(0), true).x *
  18591. this._scaleSpring.current.value;
  18592. var highestLevel = Math.min(
  18593. Math.abs(this.source.maxLevel),
  18594. Math.abs(Math.floor(
  18595. Math.log(currentZeroRatio / this.minPixelRatio) / Math.log(2)
  18596. ))
  18597. );
  18598. // Calculations for the interval of levels to draw
  18599. // can return invalid intervals; fix that here if necessary
  18600. highestLevel = Math.max(highestLevel, this.source.minLevel || 0);
  18601. lowestLevel = Math.min(lowestLevel, highestLevel);
  18602. return {
  18603. lowestLevel: lowestLevel,
  18604. highestLevel: highestLevel
  18605. };
  18606. },
  18607. /**
  18608. * @private
  18609. * @inner
  18610. * Pretty much every other line in this needs to be documented so it's clear
  18611. * how each piece of this routine contributes to the drawing process. That's
  18612. * why there are so many TODO's inside this function.
  18613. */
  18614. _updateViewport: function() {
  18615. this._needsDraw = false;
  18616. this._tilesLoading = 0;
  18617. this.loadingCoverage = {};
  18618. // Reset tile's internal drawn state
  18619. while (this.lastDrawn.length > 0) {
  18620. var tile = this.lastDrawn.pop();
  18621. tile.beingDrawn = false;
  18622. }
  18623. var viewport = this.viewport;
  18624. var drawArea = this._viewportToTiledImageRectangle(
  18625. viewport.getBoundsWithMargins(true));
  18626. if (!this.wrapHorizontal && !this.wrapVertical) {
  18627. var tiledImageBounds = this._viewportToTiledImageRectangle(
  18628. this.getClippedBounds(true));
  18629. drawArea = drawArea.intersection(tiledImageBounds);
  18630. if (drawArea === null) {
  18631. return;
  18632. }
  18633. }
  18634. var levelsInterval = this._getLevelsInterval();
  18635. var lowestLevel = levelsInterval.lowestLevel;
  18636. var highestLevel = levelsInterval.highestLevel;
  18637. var bestTile = null;
  18638. var haveDrawn = false;
  18639. var currentTime = $.now();
  18640. // Update any level that will be drawn
  18641. for (var level = highestLevel; level >= lowestLevel; level--) {
  18642. var drawLevel = false;
  18643. //Avoid calculations for draw if we have already drawn this
  18644. var currentRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate(
  18645. this.source.getPixelRatio(level),
  18646. true
  18647. ).x * this._scaleSpring.current.value;
  18648. if (level === lowestLevel ||
  18649. (!haveDrawn && currentRenderPixelRatio >= this.minPixelRatio)) {
  18650. drawLevel = true;
  18651. haveDrawn = true;
  18652. } else if (!haveDrawn) {
  18653. continue;
  18654. }
  18655. //Perform calculations for draw if we haven't drawn this
  18656. var targetRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate(
  18657. this.source.getPixelRatio(level),
  18658. false
  18659. ).x * this._scaleSpring.current.value;
  18660. var targetZeroRatio = viewport.deltaPixelsFromPointsNoRotate(
  18661. this.source.getPixelRatio(
  18662. Math.max(
  18663. this.source.getClosestLevel(),
  18664. 0
  18665. )
  18666. ),
  18667. false
  18668. ).x * this._scaleSpring.current.value;
  18669. var optimalRatio = this.immediateRender ? 1 : targetZeroRatio;
  18670. var levelOpacity = Math.min(1, (currentRenderPixelRatio - 0.5) / 0.5);
  18671. var levelVisibility = optimalRatio / Math.abs(
  18672. optimalRatio - targetRenderPixelRatio
  18673. );
  18674. // Update the level and keep track of 'best' tile to load
  18675. bestTile = updateLevel(
  18676. this,
  18677. haveDrawn,
  18678. drawLevel,
  18679. level,
  18680. levelOpacity,
  18681. levelVisibility,
  18682. drawArea,
  18683. currentTime,
  18684. bestTile
  18685. );
  18686. // Stop the loop if lower-res tiles would all be covered by
  18687. // already drawn tiles
  18688. if (providesCoverage(this.coverage, level)) {
  18689. break;
  18690. }
  18691. }
  18692. // Perform the actual drawing
  18693. drawTiles(this, this.lastDrawn);
  18694. // Load the new 'best' tile
  18695. if (bestTile && !bestTile.context2D) {
  18696. loadTile(this, bestTile, currentTime);
  18697. this._needsDraw = true;
  18698. this._setFullyLoaded(false);
  18699. } else {
  18700. this._setFullyLoaded(this._tilesLoading === 0);
  18701. }
  18702. },
  18703. // private
  18704. _getCornerTiles: function(level, topLeftBound, bottomRightBound) {
  18705. var leftX;
  18706. var rightX;
  18707. if (this.wrapHorizontal) {
  18708. leftX = $.positiveModulo(topLeftBound.x, 1);
  18709. rightX = $.positiveModulo(bottomRightBound.x, 1);
  18710. } else {
  18711. leftX = Math.max(0, topLeftBound.x);
  18712. rightX = Math.min(1, bottomRightBound.x);
  18713. }
  18714. var topY;
  18715. var bottomY;
  18716. var aspectRatio = 1 / this.source.aspectRatio;
  18717. if (this.wrapVertical) {
  18718. topY = $.positiveModulo(topLeftBound.y, aspectRatio);
  18719. bottomY = $.positiveModulo(bottomRightBound.y, aspectRatio);
  18720. } else {
  18721. topY = Math.max(0, topLeftBound.y);
  18722. bottomY = Math.min(aspectRatio, bottomRightBound.y);
  18723. }
  18724. var topLeftTile = this.source.getTileAtPoint(level, new $.Point(leftX, topY));
  18725. var bottomRightTile = this.source.getTileAtPoint(level, new $.Point(rightX, bottomY));
  18726. var numTiles = this.source.getNumTiles(level);
  18727. if (this.wrapHorizontal) {
  18728. topLeftTile.x += numTiles.x * Math.floor(topLeftBound.x);
  18729. bottomRightTile.x += numTiles.x * Math.floor(bottomRightBound.x);
  18730. }
  18731. if (this.wrapVertical) {
  18732. topLeftTile.y += numTiles.y * Math.floor(topLeftBound.y / aspectRatio);
  18733. bottomRightTile.y += numTiles.y * Math.floor(bottomRightBound.y / aspectRatio);
  18734. }
  18735. return {
  18736. topLeft: topLeftTile,
  18737. bottomRight: bottomRightTile,
  18738. };
  18739. }
  18740. });
  18741. /**
  18742. * @private
  18743. * @inner
  18744. * Updates all tiles at a given resolution level.
  18745. * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
  18746. * @param {Boolean} haveDrawn
  18747. * @param {Boolean} drawLevel
  18748. * @param {Number} level
  18749. * @param {Number} levelOpacity
  18750. * @param {Number} levelVisibility
  18751. * @param {OpenSeadragon.Point} viewportTL - The index of the most top-left visible tile.
  18752. * @param {OpenSeadragon.Point} viewportBR - The index of the most bottom-right visible tile.
  18753. * @param {Number} currentTime
  18754. * @param {OpenSeadragon.Tile} best - The current "best" tile to draw.
  18755. */
  18756. function updateLevel(tiledImage, haveDrawn, drawLevel, level, levelOpacity,
  18757. levelVisibility, drawArea, currentTime, best) {
  18758. var topLeftBound = drawArea.getBoundingBox().getTopLeft();
  18759. var bottomRightBound = drawArea.getBoundingBox().getBottomRight();
  18760. if (tiledImage.viewer) {
  18761. /**
  18762. * <em>- Needs documentation -</em>
  18763. *
  18764. * @event update-level
  18765. * @memberof OpenSeadragon.Viewer
  18766. * @type {object}
  18767. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  18768. * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
  18769. * @property {Object} havedrawn
  18770. * @property {Object} level
  18771. * @property {Object} opacity
  18772. * @property {Object} visibility
  18773. * @property {OpenSeadragon.Rect} drawArea
  18774. * @property {Object} topleft deprecated, use drawArea instead
  18775. * @property {Object} bottomright deprecated, use drawArea instead
  18776. * @property {Object} currenttime
  18777. * @property {Object} best
  18778. * @property {?Object} userData - Arbitrary subscriber-defined object.
  18779. */
  18780. tiledImage.viewer.raiseEvent('update-level', {
  18781. tiledImage: tiledImage,
  18782. havedrawn: haveDrawn,
  18783. level: level,
  18784. opacity: levelOpacity,
  18785. visibility: levelVisibility,
  18786. drawArea: drawArea,
  18787. topleft: topLeftBound,
  18788. bottomright: bottomRightBound,
  18789. currenttime: currentTime,
  18790. best: best
  18791. });
  18792. }
  18793. resetCoverage(tiledImage.coverage, level);
  18794. resetCoverage(tiledImage.loadingCoverage, level);
  18795. //OK, a new drawing so do your calculations
  18796. var cornerTiles = tiledImage._getCornerTiles(level, topLeftBound, bottomRightBound);
  18797. var topLeftTile = cornerTiles.topLeft;
  18798. var bottomRightTile = cornerTiles.bottomRight;
  18799. var numberOfTiles = tiledImage.source.getNumTiles(level);
  18800. var viewportCenter = tiledImage.viewport.pixelFromPoint(
  18801. tiledImage.viewport.getCenter());
  18802. for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) {
  18803. for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) {
  18804. // Optimisation disabled with wrapping because getTileBounds does not
  18805. // work correctly with x and y outside of the number of tiles
  18806. if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) {
  18807. var tileBounds = tiledImage.source.getTileBounds(level, x, y);
  18808. if (drawArea.intersection(tileBounds) === null) {
  18809. // This tile is outside of the viewport, no need to draw it
  18810. continue;
  18811. }
  18812. }
  18813. best = updateTile(
  18814. tiledImage,
  18815. drawLevel,
  18816. haveDrawn,
  18817. x, y,
  18818. level,
  18819. levelOpacity,
  18820. levelVisibility,
  18821. viewportCenter,
  18822. numberOfTiles,
  18823. currentTime,
  18824. best
  18825. );
  18826. }
  18827. }
  18828. return best;
  18829. }
  18830. /**
  18831. * @private
  18832. * @inner
  18833. * Update a single tile at a particular resolution level.
  18834. * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
  18835. * @param {Boolean} haveDrawn
  18836. * @param {Boolean} drawLevel
  18837. * @param {Number} x
  18838. * @param {Number} y
  18839. * @param {Number} level
  18840. * @param {Number} levelOpacity
  18841. * @param {Number} levelVisibility
  18842. * @param {OpenSeadragon.Point} viewportCenter
  18843. * @param {Number} numberOfTiles
  18844. * @param {Number} currentTime
  18845. * @param {OpenSeadragon.Tile} best - The current "best" tile to draw.
  18846. */
  18847. function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){
  18848. var tile = getTile(
  18849. x, y,
  18850. level,
  18851. tiledImage,
  18852. tiledImage.source,
  18853. tiledImage.tilesMatrix,
  18854. currentTime,
  18855. numberOfTiles,
  18856. tiledImage._worldWidthCurrent,
  18857. tiledImage._worldHeightCurrent
  18858. ),
  18859. drawTile = drawLevel;
  18860. if( tiledImage.viewer ){
  18861. /**
  18862. * <em>- Needs documentation -</em>
  18863. *
  18864. * @event update-tile
  18865. * @memberof OpenSeadragon.Viewer
  18866. * @type {object}
  18867. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  18868. * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
  18869. * @property {OpenSeadragon.Tile} tile
  18870. * @property {?Object} userData - Arbitrary subscriber-defined object.
  18871. */
  18872. tiledImage.viewer.raiseEvent( 'update-tile', {
  18873. tiledImage: tiledImage,
  18874. tile: tile
  18875. });
  18876. }
  18877. setCoverage( tiledImage.coverage, level, x, y, false );
  18878. var loadingCoverage = tile.loaded || tile.loading || isCovered(tiledImage.loadingCoverage, level, x, y);
  18879. setCoverage(tiledImage.loadingCoverage, level, x, y, loadingCoverage);
  18880. if ( !tile.exists ) {
  18881. return best;
  18882. }
  18883. if ( haveDrawn && !drawTile ) {
  18884. if ( isCovered( tiledImage.coverage, level, x, y ) ) {
  18885. setCoverage( tiledImage.coverage, level, x, y, true );
  18886. } else {
  18887. drawTile = true;
  18888. }
  18889. }
  18890. if ( !drawTile ) {
  18891. return best;
  18892. }
  18893. positionTile(
  18894. tile,
  18895. tiledImage.source.tileOverlap,
  18896. tiledImage.viewport,
  18897. viewportCenter,
  18898. levelVisibility,
  18899. tiledImage
  18900. );
  18901. if (!tile.loaded) {
  18902. if (tile.context2D) {
  18903. setTileLoaded(tiledImage, tile);
  18904. } else {
  18905. var imageRecord = tiledImage._tileCache.getImageRecord(tile.cacheKey);
  18906. if (imageRecord) {
  18907. var image = imageRecord.getImage();
  18908. setTileLoaded(tiledImage, tile, image);
  18909. }
  18910. }
  18911. }
  18912. if ( tile.loaded ) {
  18913. var needsDraw = blendTile(
  18914. tiledImage,
  18915. tile,
  18916. x, y,
  18917. level,
  18918. levelOpacity,
  18919. currentTime
  18920. );
  18921. if ( needsDraw ) {
  18922. tiledImage._needsDraw = true;
  18923. }
  18924. } else if ( tile.loading ) {
  18925. // the tile is already in the download queue
  18926. tiledImage._tilesLoading++;
  18927. } else if (!loadingCoverage) {
  18928. best = compareTiles( best, tile );
  18929. }
  18930. return best;
  18931. }
  18932. /**
  18933. * @private
  18934. * @inner
  18935. * Obtains a tile at the given location.
  18936. * @param {Number} x
  18937. * @param {Number} y
  18938. * @param {Number} level
  18939. * @param {OpenSeadragon.TiledImage} tiledImage
  18940. * @param {OpenSeadragon.TileSource} tileSource
  18941. * @param {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile.
  18942. * @param {Number} time
  18943. * @param {Number} numTiles
  18944. * @param {Number} worldWidth
  18945. * @param {Number} worldHeight
  18946. * @returns {OpenSeadragon.Tile}
  18947. */
  18948. function getTile(
  18949. x, y,
  18950. level,
  18951. tiledImage,
  18952. tileSource,
  18953. tilesMatrix,
  18954. time,
  18955. numTiles,
  18956. worldWidth,
  18957. worldHeight
  18958. ) {
  18959. var xMod,
  18960. yMod,
  18961. bounds,
  18962. sourceBounds,
  18963. exists,
  18964. url,
  18965. ajaxHeaders,
  18966. context2D,
  18967. tile;
  18968. if ( !tilesMatrix[ level ] ) {
  18969. tilesMatrix[ level ] = {};
  18970. }
  18971. if ( !tilesMatrix[ level ][ x ] ) {
  18972. tilesMatrix[ level ][ x ] = {};
  18973. }
  18974. if ( !tilesMatrix[ level ][ x ][ y ] ) {
  18975. xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x;
  18976. yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y;
  18977. bounds = tileSource.getTileBounds( level, xMod, yMod );
  18978. sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true );
  18979. exists = tileSource.tileExists( level, xMod, yMod );
  18980. url = tileSource.getTileUrl( level, xMod, yMod );
  18981. // Headers are only applicable if loadTilesWithAjax is set
  18982. if (tiledImage.loadTilesWithAjax) {
  18983. ajaxHeaders = tileSource.getTileAjaxHeaders( level, xMod, yMod );
  18984. // Combine tile AJAX headers with tiled image AJAX headers (if applicable)
  18985. if ($.isPlainObject(tiledImage.ajaxHeaders)) {
  18986. ajaxHeaders = $.extend({}, tiledImage.ajaxHeaders, ajaxHeaders);
  18987. }
  18988. } else {
  18989. ajaxHeaders = null;
  18990. }
  18991. context2D = tileSource.getContext2D ?
  18992. tileSource.getContext2D(level, xMod, yMod) : undefined;
  18993. bounds.x += ( x - xMod ) / numTiles.x;
  18994. bounds.y += (worldHeight / worldWidth) * (( y - yMod ) / numTiles.y);
  18995. tile = new $.Tile(
  18996. level,
  18997. x,
  18998. y,
  18999. bounds,
  19000. exists,
  19001. url,
  19002. context2D,
  19003. tiledImage.loadTilesWithAjax,
  19004. ajaxHeaders,
  19005. sourceBounds
  19006. );
  19007. if (xMod === numTiles.x - 1) {
  19008. tile.isRightMost = true;
  19009. }
  19010. if (yMod === numTiles.y - 1) {
  19011. tile.isBottomMost = true;
  19012. }
  19013. tilesMatrix[ level ][ x ][ y ] = tile;
  19014. }
  19015. tile = tilesMatrix[ level ][ x ][ y ];
  19016. tile.lastTouchTime = time;
  19017. return tile;
  19018. }
  19019. /**
  19020. * @private
  19021. * @inner
  19022. * Dispatch a job to the ImageLoader to load the Image for a Tile.
  19023. * @param {OpenSeadragon.TiledImage} tiledImage
  19024. * @param {OpenSeadragon.Tile} tile
  19025. * @param {Number} time
  19026. */
  19027. function loadTile( tiledImage, tile, time ) {
  19028. tile.loading = true;
  19029. tiledImage._imageLoader.addJob({
  19030. src: tile.url,
  19031. loadWithAjax: tile.loadWithAjax,
  19032. ajaxHeaders: tile.ajaxHeaders,
  19033. crossOriginPolicy: tiledImage.crossOriginPolicy,
  19034. ajaxWithCredentials: tiledImage.ajaxWithCredentials,
  19035. callback: function( image, errorMsg, tileRequest ){
  19036. onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest );
  19037. },
  19038. abort: function() {
  19039. tile.loading = false;
  19040. }
  19041. });
  19042. }
  19043. /**
  19044. * @private
  19045. * @inner
  19046. * Callback fired when a Tile's Image finished downloading.
  19047. * @param {OpenSeadragon.TiledImage} tiledImage
  19048. * @param {OpenSeadragon.Tile} tile
  19049. * @param {Number} time
  19050. * @param {Image} image
  19051. * @param {String} errorMsg
  19052. * @param {XMLHttpRequest} tileRequest
  19053. */
  19054. function onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest ) {
  19055. if ( !image ) {
  19056. $.console.log( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg );
  19057. /**
  19058. * Triggered when a tile fails to load.
  19059. *
  19060. * @event tile-load-failed
  19061. * @memberof OpenSeadragon.Viewer
  19062. * @type {object}
  19063. * @property {OpenSeadragon.Tile} tile - The tile that failed to load.
  19064. * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image the tile belongs to.
  19065. * @property {number} time - The time in milliseconds when the tile load began.
  19066. * @property {string} message - The error message.
  19067. * @property {XMLHttpRequest} tileRequest - The XMLHttpRequest used to load the tile if available.
  19068. */
  19069. tiledImage.viewer.raiseEvent("tile-load-failed", {
  19070. tile: tile,
  19071. tiledImage: tiledImage,
  19072. time: time,
  19073. message: errorMsg,
  19074. tileRequest: tileRequest
  19075. });
  19076. tile.loading = false;
  19077. tile.exists = false;
  19078. return;
  19079. }
  19080. if ( time < tiledImage.lastResetTime ) {
  19081. $.console.log( "Ignoring tile %s loaded before reset: %s", tile, tile.url );
  19082. tile.loading = false;
  19083. return;
  19084. }
  19085. var finish = function() {
  19086. var cutoff = tiledImage.source.getClosestLevel();
  19087. setTileLoaded(tiledImage, tile, image, cutoff, tileRequest);
  19088. };
  19089. // Check if we're mid-update; this can happen on IE8 because image load events for
  19090. // cached images happen immediately there
  19091. if ( !tiledImage._midDraw ) {
  19092. finish();
  19093. } else {
  19094. // Wait until after the update, in case caching unloads any tiles
  19095. window.setTimeout( finish, 1);
  19096. }
  19097. }
  19098. /**
  19099. * @private
  19100. * @inner
  19101. * @param {OpenSeadragon.TiledImage} tiledImage
  19102. * @param {OpenSeadragon.Tile} tile
  19103. * @param {Image} image
  19104. * @param {Number} cutoff
  19105. */
  19106. function setTileLoaded(tiledImage, tile, image, cutoff, tileRequest) {
  19107. var increment = 0;
  19108. function getCompletionCallback() {
  19109. increment++;
  19110. return completionCallback;
  19111. }
  19112. function completionCallback() {
  19113. increment--;
  19114. if (increment === 0) {
  19115. tile.loading = false;
  19116. tile.loaded = true;
  19117. if (!tile.context2D) {
  19118. tiledImage._tileCache.cacheTile({
  19119. image: image,
  19120. tile: tile,
  19121. cutoff: cutoff,
  19122. tiledImage: tiledImage
  19123. });
  19124. }
  19125. tiledImage._needsDraw = true;
  19126. }
  19127. }
  19128. /**
  19129. * Triggered when a tile has just been loaded in memory. That means that the
  19130. * image has been downloaded and can be modified before being drawn to the canvas.
  19131. *
  19132. * @event tile-loaded
  19133. * @memberof OpenSeadragon.Viewer
  19134. * @type {object}
  19135. * @property {Image} image - The image of the tile.
  19136. * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.
  19137. * @property {OpenSeadragon.Tile} tile - The tile which has been loaded.
  19138. * @property {XMLHttpRequest} tiledImage - The AJAX request that loaded this tile (if applicable).
  19139. * @property {function} getCompletionCallback - A function giving a callback to call
  19140. * when the asynchronous processing of the image is done. The image will be
  19141. * marked as entirely loaded when the callback has been called once for each
  19142. * call to getCompletionCallback.
  19143. */
  19144. tiledImage.viewer.raiseEvent("tile-loaded", {
  19145. tile: tile,
  19146. tiledImage: tiledImage,
  19147. tileRequest: tileRequest,
  19148. image: image,
  19149. getCompletionCallback: getCompletionCallback
  19150. });
  19151. // In case the completion callback is never called, we at least force it once.
  19152. getCompletionCallback()();
  19153. }
  19154. /**
  19155. * @private
  19156. * @inner
  19157. * @param {OpenSeadragon.Tile} tile
  19158. * @param {Boolean} overlap
  19159. * @param {OpenSeadragon.Viewport} viewport
  19160. * @param {OpenSeadragon.Point} viewportCenter
  19161. * @param {Number} levelVisibility
  19162. * @param {OpenSeadragon.TiledImage} tiledImage
  19163. */
  19164. function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){
  19165. var boundsTL = tile.bounds.getTopLeft();
  19166. boundsTL.x *= tiledImage._scaleSpring.current.value;
  19167. boundsTL.y *= tiledImage._scaleSpring.current.value;
  19168. boundsTL.x += tiledImage._xSpring.current.value;
  19169. boundsTL.y += tiledImage._ySpring.current.value;
  19170. var boundsSize = tile.bounds.getSize();
  19171. boundsSize.x *= tiledImage._scaleSpring.current.value;
  19172. boundsSize.y *= tiledImage._scaleSpring.current.value;
  19173. var positionC = viewport.pixelFromPointNoRotate(boundsTL, true),
  19174. positionT = viewport.pixelFromPointNoRotate(boundsTL, false),
  19175. sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true),
  19176. sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false),
  19177. tileCenter = positionT.plus( sizeT.divide( 2 ) ),
  19178. tileSquaredDistance = viewportCenter.squaredDistanceTo( tileCenter );
  19179. if ( !overlap ) {
  19180. sizeC = sizeC.plus( new $.Point( 1, 1 ) );
  19181. }
  19182. if (tile.isRightMost && tiledImage.wrapHorizontal) {
  19183. sizeC.x += 0.75; // Otherwise Firefox and Safari show seams
  19184. }
  19185. if (tile.isBottomMost && tiledImage.wrapVertical) {
  19186. sizeC.y += 0.75; // Otherwise Firefox and Safari show seams
  19187. }
  19188. tile.position = positionC;
  19189. tile.size = sizeC;
  19190. tile.squaredDistance = tileSquaredDistance;
  19191. tile.visibility = levelVisibility;
  19192. }
  19193. /**
  19194. * @private
  19195. * @inner
  19196. * Updates the opacity of a tile according to the time it has been on screen
  19197. * to perform a fade-in.
  19198. * Updates coverage once a tile is fully opaque.
  19199. * Returns whether the fade-in has completed.
  19200. *
  19201. * @param {OpenSeadragon.TiledImage} tiledImage
  19202. * @param {OpenSeadragon.Tile} tile
  19203. * @param {Number} x
  19204. * @param {Number} y
  19205. * @param {Number} level
  19206. * @param {Number} levelOpacity
  19207. * @param {Number} currentTime
  19208. * @returns {Boolean}
  19209. */
  19210. function blendTile( tiledImage, tile, x, y, level, levelOpacity, currentTime ){
  19211. var blendTimeMillis = 1000 * tiledImage.blendTime,
  19212. deltaTime,
  19213. opacity;
  19214. if ( !tile.blendStart ) {
  19215. tile.blendStart = currentTime;
  19216. }
  19217. deltaTime = currentTime - tile.blendStart;
  19218. opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;
  19219. if ( tiledImage.alwaysBlend ) {
  19220. opacity *= levelOpacity;
  19221. }
  19222. tile.opacity = opacity;
  19223. tiledImage.lastDrawn.push( tile );
  19224. if ( opacity === 1 ) {
  19225. setCoverage( tiledImage.coverage, level, x, y, true );
  19226. tiledImage._hasOpaqueTile = true;
  19227. } else if ( deltaTime < blendTimeMillis ) {
  19228. return true;
  19229. }
  19230. return false;
  19231. }
  19232. /**
  19233. * @private
  19234. * @inner
  19235. * Returns true if the given tile provides coverage to lower-level tiles of
  19236. * lower resolution representing the same content. If neither x nor y is
  19237. * given, returns true if the entire visible level provides coverage.
  19238. *
  19239. * Note that out-of-bounds tiles provide coverage in this sense, since
  19240. * there's no content that they would need to cover. Tiles at non-existent
  19241. * levels that are within the image bounds, however, do not.
  19242. *
  19243. * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
  19244. * @param {Number} level - The resolution level of the tile.
  19245. * @param {Number} x - The X position of the tile.
  19246. * @param {Number} y - The Y position of the tile.
  19247. * @returns {Boolean}
  19248. */
  19249. function providesCoverage( coverage, level, x, y ) {
  19250. var rows,
  19251. cols,
  19252. i, j;
  19253. if ( !coverage[ level ] ) {
  19254. return false;
  19255. }
  19256. if ( x === undefined || y === undefined ) {
  19257. rows = coverage[ level ];
  19258. for ( i in rows ) {
  19259. if ( rows.hasOwnProperty( i ) ) {
  19260. cols = rows[ i ];
  19261. for ( j in cols ) {
  19262. if ( cols.hasOwnProperty( j ) && !cols[ j ] ) {
  19263. return false;
  19264. }
  19265. }
  19266. }
  19267. }
  19268. return true;
  19269. }
  19270. return (
  19271. coverage[ level ][ x] === undefined ||
  19272. coverage[ level ][ x ][ y ] === undefined ||
  19273. coverage[ level ][ x ][ y ] === true
  19274. );
  19275. }
  19276. /**
  19277. * @private
  19278. * @inner
  19279. * Returns true if the given tile is completely covered by higher-level
  19280. * tiles of higher resolution representing the same content. If neither x
  19281. * nor y is given, returns true if the entire visible level is covered.
  19282. *
  19283. * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
  19284. * @param {Number} level - The resolution level of the tile.
  19285. * @param {Number} x - The X position of the tile.
  19286. * @param {Number} y - The Y position of the tile.
  19287. * @returns {Boolean}
  19288. */
  19289. function isCovered( coverage, level, x, y ) {
  19290. if ( x === undefined || y === undefined ) {
  19291. return providesCoverage( coverage, level + 1 );
  19292. } else {
  19293. return (
  19294. providesCoverage( coverage, level + 1, 2 * x, 2 * y ) &&
  19295. providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) &&
  19296. providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) &&
  19297. providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 )
  19298. );
  19299. }
  19300. }
  19301. /**
  19302. * @private
  19303. * @inner
  19304. * Sets whether the given tile provides coverage or not.
  19305. *
  19306. * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
  19307. * @param {Number} level - The resolution level of the tile.
  19308. * @param {Number} x - The X position of the tile.
  19309. * @param {Number} y - The Y position of the tile.
  19310. * @param {Boolean} covers - Whether the tile provides coverage.
  19311. */
  19312. function setCoverage( coverage, level, x, y, covers ) {
  19313. if ( !coverage[ level ] ) {
  19314. $.console.warn(
  19315. "Setting coverage for a tile before its level's coverage has been reset: %s",
  19316. level
  19317. );
  19318. return;
  19319. }
  19320. if ( !coverage[ level ][ x ] ) {
  19321. coverage[ level ][ x ] = {};
  19322. }
  19323. coverage[ level ][ x ][ y ] = covers;
  19324. }
  19325. /**
  19326. * @private
  19327. * @inner
  19328. * Resets coverage information for the given level. This should be called
  19329. * after every draw routine. Note that at the beginning of the next draw
  19330. * routine, coverage for every visible tile should be explicitly set.
  19331. *
  19332. * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
  19333. * @param {Number} level - The resolution level of tiles to completely reset.
  19334. */
  19335. function resetCoverage( coverage, level ) {
  19336. coverage[ level ] = {};
  19337. }
  19338. /**
  19339. * @private
  19340. * @inner
  19341. * Determines whether the 'last best' tile for the area is better than the
  19342. * tile in question.
  19343. *
  19344. * @param {OpenSeadragon.Tile} previousBest
  19345. * @param {OpenSeadragon.Tile} tile
  19346. * @returns {OpenSeadragon.Tile} The new best tile.
  19347. */
  19348. function compareTiles( previousBest, tile ) {
  19349. if ( !previousBest ) {
  19350. return tile;
  19351. }
  19352. if ( tile.visibility > previousBest.visibility ) {
  19353. return tile;
  19354. } else if ( tile.visibility == previousBest.visibility ) {
  19355. if ( tile.squaredDistance < previousBest.squaredDistance ) {
  19356. return tile;
  19357. }
  19358. }
  19359. return previousBest;
  19360. }
  19361. /**
  19362. * @private
  19363. * @inner
  19364. * Draws a TiledImage.
  19365. * @param {OpenSeadragon.TiledImage} tiledImage
  19366. * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.
  19367. */
  19368. function drawTiles( tiledImage, lastDrawn ) {
  19369. if (tiledImage.opacity === 0 || (lastDrawn.length === 0 && !tiledImage.placeholderFillStyle)) {
  19370. return;
  19371. }
  19372. var tile = lastDrawn[0];
  19373. var useSketch;
  19374. if (tile) {
  19375. useSketch = tiledImage.opacity < 1 ||
  19376. (tiledImage.compositeOperation &&
  19377. tiledImage.compositeOperation !== 'source-over') ||
  19378. (!tiledImage._isBottomItem() && tile._hasTransparencyChannel());
  19379. }
  19380. var sketchScale;
  19381. var sketchTranslate;
  19382. var zoom = tiledImage.viewport.getZoom(true);
  19383. var imageZoom = tiledImage.viewportToImageZoom(zoom);
  19384. if (lastDrawn.length > 1 &&
  19385. imageZoom > tiledImage.smoothTileEdgesMinZoom &&
  19386. !tiledImage.iOSDevice &&
  19387. tiledImage.getRotation(true) % 360 === 0 && // TODO: support tile edge smoothing with tiled image rotation.
  19388. $.supportsCanvas) {
  19389. // When zoomed in a lot (>100%) the tile edges are visible.
  19390. // So we have to composite them at ~100% and scale them up together.
  19391. // Note: Disabled on iOS devices per default as it causes a native crash
  19392. useSketch = true;
  19393. sketchScale = tile.getScaleForEdgeSmoothing();
  19394. sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale,
  19395. tiledImage._drawer.getCanvasSize(false),
  19396. tiledImage._drawer.getCanvasSize(true));
  19397. }
  19398. var bounds;
  19399. if (useSketch) {
  19400. if (!sketchScale) {
  19401. // Except when edge smoothing, we only clean the part of the
  19402. // sketch canvas we are going to use for performance reasons.
  19403. bounds = tiledImage.viewport.viewportToViewerElementRectangle(
  19404. tiledImage.getClippedBounds(true))
  19405. .getIntegerBoundingBox()
  19406. .times($.pixelDensityRatio);
  19407. if(tiledImage._drawer.viewer.viewport.getFlip()) {
  19408. if (tiledImage.viewport.degrees !== 0 || tiledImage.getRotation(true) % 360 !== 0){
  19409. bounds.x = tiledImage._drawer.viewer.container.clientWidth - (bounds.x + bounds.width);
  19410. }
  19411. }
  19412. }
  19413. tiledImage._drawer._clear(true, bounds);
  19414. }
  19415. // When scaling, we must rotate only when blending the sketch canvas to
  19416. // avoid interpolation
  19417. if (!sketchScale) {
  19418. if (tiledImage.viewport.degrees !== 0) {
  19419. tiledImage._drawer._offsetForRotation({
  19420. degrees: tiledImage.viewport.degrees,
  19421. useSketch: useSketch
  19422. });
  19423. }
  19424. if (tiledImage.getRotation(true) % 360 !== 0) {
  19425. tiledImage._drawer._offsetForRotation({
  19426. degrees: tiledImage.getRotation(true),
  19427. point: tiledImage.viewport.pixelFromPointNoRotate(
  19428. tiledImage._getRotationPoint(true), true),
  19429. useSketch: useSketch
  19430. });
  19431. }
  19432. if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){
  19433. if(tiledImage._drawer.viewer.viewport.getFlip()) {
  19434. tiledImage._drawer._flip();
  19435. }
  19436. }
  19437. }
  19438. var usedClip = false;
  19439. if ( tiledImage._clip ) {
  19440. tiledImage._drawer.saveContext(useSketch);
  19441. var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
  19442. box = box.rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true));
  19443. var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);
  19444. if (sketchScale) {
  19445. clipRect = clipRect.times(sketchScale);
  19446. }
  19447. if (sketchTranslate) {
  19448. clipRect = clipRect.translate(sketchTranslate);
  19449. }
  19450. tiledImage._drawer.setClip(clipRect, useSketch);
  19451. usedClip = true;
  19452. }
  19453. if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
  19454. var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true));
  19455. if (sketchScale) {
  19456. placeholderRect = placeholderRect.times(sketchScale);
  19457. }
  19458. if (sketchTranslate) {
  19459. placeholderRect = placeholderRect.translate(sketchTranslate);
  19460. }
  19461. var fillStyle = null;
  19462. if ( typeof tiledImage.placeholderFillStyle === "function" ) {
  19463. fillStyle = tiledImage.placeholderFillStyle(tiledImage, tiledImage._drawer.context);
  19464. }
  19465. else {
  19466. fillStyle = tiledImage.placeholderFillStyle;
  19467. }
  19468. tiledImage._drawer.drawRectangle(placeholderRect, fillStyle, useSketch);
  19469. }
  19470. for (var i = lastDrawn.length - 1; i >= 0; i--) {
  19471. tile = lastDrawn[ i ];
  19472. tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale, sketchTranslate );
  19473. tile.beingDrawn = true;
  19474. if( tiledImage.viewer ){
  19475. /**
  19476. * <em>- Needs documentation -</em>
  19477. *
  19478. * @event tile-drawn
  19479. * @memberof OpenSeadragon.Viewer
  19480. * @type {object}
  19481. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
  19482. * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
  19483. * @property {OpenSeadragon.Tile} tile
  19484. * @property {?Object} userData - Arbitrary subscriber-defined object.
  19485. */
  19486. tiledImage.viewer.raiseEvent( 'tile-drawn', {
  19487. tiledImage: tiledImage,
  19488. tile: tile
  19489. });
  19490. }
  19491. }
  19492. if ( usedClip ) {
  19493. tiledImage._drawer.restoreContext( useSketch );
  19494. }
  19495. if (!sketchScale) {
  19496. if (tiledImage.getRotation(true) % 360 !== 0) {
  19497. tiledImage._drawer._restoreRotationChanges(useSketch);
  19498. }
  19499. if (tiledImage.viewport.degrees !== 0) {
  19500. tiledImage._drawer._restoreRotationChanges(useSketch);
  19501. }
  19502. }
  19503. if (useSketch) {
  19504. if (sketchScale) {
  19505. if (tiledImage.viewport.degrees !== 0) {
  19506. tiledImage._drawer._offsetForRotation({
  19507. degrees: tiledImage.viewport.degrees,
  19508. useSketch: false
  19509. });
  19510. }
  19511. if (tiledImage.getRotation(true) % 360 !== 0) {
  19512. tiledImage._drawer._offsetForRotation({
  19513. degrees: tiledImage.getRotation(true),
  19514. point: tiledImage.viewport.pixelFromPointNoRotate(
  19515. tiledImage._getRotationPoint(true), true),
  19516. useSketch: false
  19517. });
  19518. }
  19519. }
  19520. tiledImage._drawer.blendSketch({
  19521. opacity: tiledImage.opacity,
  19522. scale: sketchScale,
  19523. translate: sketchTranslate,
  19524. compositeOperation: tiledImage.compositeOperation,
  19525. bounds: bounds
  19526. });
  19527. if (sketchScale) {
  19528. if (tiledImage.getRotation(true) % 360 !== 0) {
  19529. tiledImage._drawer._restoreRotationChanges(false);
  19530. }
  19531. if (tiledImage.viewport.degrees !== 0) {
  19532. tiledImage._drawer._restoreRotationChanges(false);
  19533. }
  19534. }
  19535. }
  19536. if (!sketchScale) {
  19537. if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){
  19538. if(tiledImage._drawer.viewer.viewport.getFlip()) {
  19539. tiledImage._drawer._flip();
  19540. }
  19541. }
  19542. }
  19543. drawDebugInfo( tiledImage, lastDrawn );
  19544. }
  19545. /**
  19546. * @private
  19547. * @inner
  19548. * Draws special debug information for a TiledImage if in debug mode.
  19549. * @param {OpenSeadragon.TiledImage} tiledImage
  19550. * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.
  19551. */
  19552. function drawDebugInfo( tiledImage, lastDrawn ) {
  19553. if( tiledImage.debugMode ) {
  19554. for ( var i = lastDrawn.length - 1; i >= 0; i-- ) {
  19555. var tile = lastDrawn[ i ];
  19556. try {
  19557. tiledImage._drawer.drawDebugInfo(
  19558. tile, lastDrawn.length, i, tiledImage);
  19559. } catch(e) {
  19560. $.console.error(e);
  19561. }
  19562. }
  19563. }
  19564. }
  19565. }( OpenSeadragon ));
  19566. /*
  19567. * OpenSeadragon - TileCache
  19568. *
  19569. * Copyright (C) 2009 CodePlex Foundation
  19570. * Copyright (C) 2010-2013 OpenSeadragon contributors
  19571. *
  19572. * Redistribution and use in source and binary forms, with or without
  19573. * modification, are permitted provided that the following conditions are
  19574. * met:
  19575. *
  19576. * - Redistributions of source code must retain the above copyright notice,
  19577. * this list of conditions and the following disclaimer.
  19578. *
  19579. * - Redistributions in binary form must reproduce the above copyright
  19580. * notice, this list of conditions and the following disclaimer in the
  19581. * documentation and/or other materials provided with the distribution.
  19582. *
  19583. * - Neither the name of CodePlex Foundation nor the names of its
  19584. * contributors may be used to endorse or promote products derived from
  19585. * this software without specific prior written permission.
  19586. *
  19587. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19588. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  19589. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  19590. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  19591. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  19592. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  19593. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  19594. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  19595. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  19596. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  19597. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  19598. */
  19599. (function( $ ){
  19600. // private class
  19601. var TileRecord = function( options ) {
  19602. $.console.assert( options, "[TileCache.cacheTile] options is required" );
  19603. $.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" );
  19604. $.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
  19605. this.tile = options.tile;
  19606. this.tiledImage = options.tiledImage;
  19607. };
  19608. // private class
  19609. var ImageRecord = function(options) {
  19610. $.console.assert( options, "[ImageRecord] options is required" );
  19611. $.console.assert( options.image, "[ImageRecord] options.image is required" );
  19612. this._image = options.image;
  19613. this._tiles = [];
  19614. };
  19615. ImageRecord.prototype = {
  19616. destroy: function() {
  19617. this._image = null;
  19618. this._renderedContext = null;
  19619. this._tiles = null;
  19620. },
  19621. getImage: function() {
  19622. return this._image;
  19623. },
  19624. getRenderedContext: function() {
  19625. if (!this._renderedContext) {
  19626. var canvas = document.createElement( 'canvas' );
  19627. canvas.width = this._image.width;
  19628. canvas.height = this._image.height;
  19629. this._renderedContext = canvas.getContext('2d');
  19630. this._renderedContext.drawImage( this._image, 0, 0 );
  19631. //since we are caching the prerendered image on a canvas
  19632. //allow the image to not be held in memory
  19633. this._image = null;
  19634. }
  19635. return this._renderedContext;
  19636. },
  19637. setRenderedContext: function(renderedContext) {
  19638. $.console.error("ImageRecord.setRenderedContext is deprecated. " +
  19639. "The rendered context should be created by the ImageRecord " +
  19640. "itself when calling ImageRecord.getRenderedContext.");
  19641. this._renderedContext = renderedContext;
  19642. },
  19643. addTile: function(tile) {
  19644. $.console.assert(tile, '[ImageRecord.addTile] tile is required');
  19645. this._tiles.push(tile);
  19646. },
  19647. removeTile: function(tile) {
  19648. for (var i = 0; i < this._tiles.length; i++) {
  19649. if (this._tiles[i] === tile) {
  19650. this._tiles.splice(i, 1);
  19651. return;
  19652. }
  19653. }
  19654. $.console.warn('[ImageRecord.removeTile] trying to remove unknown tile', tile);
  19655. },
  19656. getTileCount: function() {
  19657. return this._tiles.length;
  19658. }
  19659. };
  19660. /**
  19661. * @class TileCache
  19662. * @memberof OpenSeadragon
  19663. * @classdesc Stores all the tiles displayed in a {@link OpenSeadragon.Viewer}.
  19664. * You generally won't have to interact with the TileCache directly.
  19665. * @param {Object} options - Configuration for this TileCache.
  19666. * @param {Number} [options.maxImageCacheCount] - See maxImageCacheCount in
  19667. * {@link OpenSeadragon.Options} for details.
  19668. */
  19669. $.TileCache = function( options ) {
  19670. options = options || {};
  19671. this._maxImageCacheCount = options.maxImageCacheCount || $.DEFAULT_SETTINGS.maxImageCacheCount;
  19672. this._tilesLoaded = [];
  19673. this._imagesLoaded = [];
  19674. this._imagesLoadedCount = 0;
  19675. };
  19676. /** @lends OpenSeadragon.TileCache.prototype */
  19677. $.TileCache.prototype = {
  19678. /**
  19679. * @returns {Number} The total number of tiles that have been loaded by
  19680. * this TileCache.
  19681. */
  19682. numTilesLoaded: function() {
  19683. return this._tilesLoaded.length;
  19684. },
  19685. /**
  19686. * Caches the specified tile, removing an old tile if necessary to stay under the
  19687. * maxImageCacheCount specified on construction. Note that if multiple tiles reference
  19688. * the same image, there may be more tiles than maxImageCacheCount; the goal is to keep
  19689. * the number of images below that number. Note, as well, that even the number of images
  19690. * may temporarily surpass that number, but should eventually come back down to the max specified.
  19691. * @param {Object} options - Tile info.
  19692. * @param {OpenSeadragon.Tile} options.tile - The tile to cache.
  19693. * @param {String} options.tile.cacheKey - The unique key used to identify this tile in the cache.
  19694. * @param {Image} options.image - The image of the tile to cache.
  19695. * @param {OpenSeadragon.TiledImage} options.tiledImage - The TiledImage that owns that tile.
  19696. * @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this
  19697. * function will release an old tile. The cutoff option specifies a tile level at or below which
  19698. * tiles will not be released.
  19699. */
  19700. cacheTile: function( options ) {
  19701. $.console.assert( options, "[TileCache.cacheTile] options is required" );
  19702. $.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" );
  19703. $.console.assert( options.tile.cacheKey, "[TileCache.cacheTile] options.tile.cacheKey is required" );
  19704. $.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
  19705. var cutoff = options.cutoff || 0;
  19706. var insertionIndex = this._tilesLoaded.length;
  19707. var imageRecord = this._imagesLoaded[options.tile.cacheKey];
  19708. if (!imageRecord) {
  19709. $.console.assert( options.image, "[TileCache.cacheTile] options.image is required to create an ImageRecord" );
  19710. imageRecord = this._imagesLoaded[options.tile.cacheKey] = new ImageRecord({
  19711. image: options.image
  19712. });
  19713. this._imagesLoadedCount++;
  19714. }
  19715. imageRecord.addTile(options.tile);
  19716. options.tile.cacheImageRecord = imageRecord;
  19717. // Note that just because we're unloading a tile doesn't necessarily mean
  19718. // we're unloading an image. With repeated calls it should sort itself out, though.
  19719. if ( this._imagesLoadedCount > this._maxImageCacheCount ) {
  19720. var worstTile = null;
  19721. var worstTileIndex = -1;
  19722. var worstTileRecord = null;
  19723. var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord;
  19724. for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) {
  19725. prevTileRecord = this._tilesLoaded[ i ];
  19726. prevTile = prevTileRecord.tile;
  19727. if ( prevTile.level <= cutoff || prevTile.beingDrawn ) {
  19728. continue;
  19729. } else if ( !worstTile ) {
  19730. worstTile = prevTile;
  19731. worstTileIndex = i;
  19732. worstTileRecord = prevTileRecord;
  19733. continue;
  19734. }
  19735. prevTime = prevTile.lastTouchTime;
  19736. worstTime = worstTile.lastTouchTime;
  19737. prevLevel = prevTile.level;
  19738. worstLevel = worstTile.level;
  19739. if ( prevTime < worstTime ||
  19740. ( prevTime == worstTime && prevLevel > worstLevel ) ) {
  19741. worstTile = prevTile;
  19742. worstTileIndex = i;
  19743. worstTileRecord = prevTileRecord;
  19744. }
  19745. }
  19746. if ( worstTile && worstTileIndex >= 0 ) {
  19747. this._unloadTile(worstTileRecord);
  19748. insertionIndex = worstTileIndex;
  19749. }
  19750. }
  19751. this._tilesLoaded[ insertionIndex ] = new TileRecord({
  19752. tile: options.tile,
  19753. tiledImage: options.tiledImage
  19754. });
  19755. },
  19756. /**
  19757. * Clears all tiles associated with the specified tiledImage.
  19758. * @param {OpenSeadragon.TiledImage} tiledImage
  19759. */
  19760. clearTilesFor: function( tiledImage ) {
  19761. $.console.assert(tiledImage, '[TileCache.clearTilesFor] tiledImage is required');
  19762. var tileRecord;
  19763. for ( var i = 0; i < this._tilesLoaded.length; ++i ) {
  19764. tileRecord = this._tilesLoaded[ i ];
  19765. if ( tileRecord.tiledImage === tiledImage ) {
  19766. this._unloadTile(tileRecord);
  19767. this._tilesLoaded.splice( i, 1 );
  19768. i--;
  19769. }
  19770. }
  19771. },
  19772. // private
  19773. getImageRecord: function(cacheKey) {
  19774. $.console.assert(cacheKey, '[TileCache.getImageRecord] cacheKey is required');
  19775. return this._imagesLoaded[cacheKey];
  19776. },
  19777. // private
  19778. _unloadTile: function(tileRecord) {
  19779. $.console.assert(tileRecord, '[TileCache._unloadTile] tileRecord is required');
  19780. var tile = tileRecord.tile;
  19781. var tiledImage = tileRecord.tiledImage;
  19782. tile.unload();
  19783. tile.cacheImageRecord = null;
  19784. var imageRecord = this._imagesLoaded[tile.cacheKey];
  19785. imageRecord.removeTile(tile);
  19786. if (!imageRecord.getTileCount()) {
  19787. imageRecord.destroy();
  19788. delete this._imagesLoaded[tile.cacheKey];
  19789. this._imagesLoadedCount--;
  19790. }
  19791. /**
  19792. * Triggered when a tile has just been unloaded from memory.
  19793. *
  19794. * @event tile-unloaded
  19795. * @memberof OpenSeadragon.Viewer
  19796. * @type {object}
  19797. * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the unloaded tile.
  19798. * @property {OpenSeadragon.Tile} tile - The tile which has been unloaded.
  19799. */
  19800. tiledImage.viewer.raiseEvent("tile-unloaded", {
  19801. tile: tile,
  19802. tiledImage: tiledImage
  19803. });
  19804. }
  19805. };
  19806. }( OpenSeadragon ));
  19807. /*
  19808. * OpenSeadragon - World
  19809. *
  19810. * Copyright (C) 2009 CodePlex Foundation
  19811. * Copyright (C) 2010-2013 OpenSeadragon contributors
  19812. *
  19813. * Redistribution and use in source and binary forms, with or without
  19814. * modification, are permitted provided that the following conditions are
  19815. * met:
  19816. *
  19817. * - Redistributions of source code must retain the above copyright notice,
  19818. * this list of conditions and the following disclaimer.
  19819. *
  19820. * - Redistributions in binary form must reproduce the above copyright
  19821. * notice, this list of conditions and the following disclaimer in the
  19822. * documentation and/or other materials provided with the distribution.
  19823. *
  19824. * - Neither the name of CodePlex Foundation nor the names of its
  19825. * contributors may be used to endorse or promote products derived from
  19826. * this software without specific prior written permission.
  19827. *
  19828. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19829. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  19830. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  19831. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  19832. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  19833. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  19834. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  19835. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  19836. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  19837. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  19838. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  19839. */
  19840. (function( $ ){
  19841. /**
  19842. * @class World
  19843. * @memberof OpenSeadragon
  19844. * @extends OpenSeadragon.EventSource
  19845. * @classdesc Keeps track of all of the tiled images in the scene.
  19846. * @param {Object} options - World options.
  19847. * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this World.
  19848. **/
  19849. $.World = function( options ) {
  19850. var _this = this;
  19851. $.console.assert( options.viewer, "[World] options.viewer is required" );
  19852. $.EventSource.call( this );
  19853. this.viewer = options.viewer;
  19854. this._items = [];
  19855. this._needsDraw = false;
  19856. this._autoRefigureSizes = true;
  19857. this._needsSizesFigured = false;
  19858. this._delegatedFigureSizes = function(event) {
  19859. if (_this._autoRefigureSizes) {
  19860. _this._figureSizes();
  19861. } else {
  19862. _this._needsSizesFigured = true;
  19863. }
  19864. };
  19865. this._figureSizes();
  19866. };
  19867. $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.World.prototype */{
  19868. /**
  19869. * Add the specified item.
  19870. * @param {OpenSeadragon.TiledImage} item - The item to add.
  19871. * @param {Number} [options.index] - Index for the item. If not specified, goes at the top.
  19872. * @fires OpenSeadragon.World.event:add-item
  19873. * @fires OpenSeadragon.World.event:metrics-change
  19874. */
  19875. addItem: function( item, options ) {
  19876. $.console.assert(item, "[World.addItem] item is required");
  19877. $.console.assert(item instanceof $.TiledImage, "[World.addItem] only TiledImages supported at this time");
  19878. options = options || {};
  19879. if (options.index !== undefined) {
  19880. var index = Math.max(0, Math.min(this._items.length, options.index));
  19881. this._items.splice(index, 0, item);
  19882. } else {
  19883. this._items.push( item );
  19884. }
  19885. if (this._autoRefigureSizes) {
  19886. this._figureSizes();
  19887. } else {
  19888. this._needsSizesFigured = true;
  19889. }
  19890. this._needsDraw = true;
  19891. item.addHandler('bounds-change', this._delegatedFigureSizes);
  19892. item.addHandler('clip-change', this._delegatedFigureSizes);
  19893. /**
  19894. * Raised when an item is added to the World.
  19895. * @event add-item
  19896. * @memberOf OpenSeadragon.World
  19897. * @type {object}
  19898. * @property {OpenSeadragon.Viewer} eventSource - A reference to the World which raised the event.
  19899. * @property {OpenSeadragon.TiledImage} item - The item that has been added.
  19900. * @property {?Object} userData - Arbitrary subscriber-defined object.
  19901. */
  19902. this.raiseEvent( 'add-item', {
  19903. item: item
  19904. } );
  19905. },
  19906. /**
  19907. * Get the item at the specified index.
  19908. * @param {Number} index - The item's index.
  19909. * @returns {OpenSeadragon.TiledImage} The item at the specified index.
  19910. */
  19911. getItemAt: function( index ) {
  19912. $.console.assert(index !== undefined, "[World.getItemAt] index is required");
  19913. return this._items[ index ];
  19914. },
  19915. /**
  19916. * Get the index of the given item or -1 if not present.
  19917. * @param {OpenSeadragon.TiledImage} item - The item.
  19918. * @returns {Number} The index of the item or -1 if not present.
  19919. */
  19920. getIndexOfItem: function( item ) {
  19921. $.console.assert(item, "[World.getIndexOfItem] item is required");
  19922. return $.indexOf( this._items, item );
  19923. },
  19924. /**
  19925. * @returns {Number} The number of items used.
  19926. */
  19927. getItemCount: function() {
  19928. return this._items.length;
  19929. },
  19930. /**
  19931. * Change the index of a item so that it appears over or under others.
  19932. * @param {OpenSeadragon.TiledImage} item - The item to move.
  19933. * @param {Number} index - The new index.
  19934. * @fires OpenSeadragon.World.event:item-index-change
  19935. */
  19936. setItemIndex: function( item, index ) {
  19937. $.console.assert(item, "[World.setItemIndex] item is required");
  19938. $.console.assert(index !== undefined, "[World.setItemIndex] index is required");
  19939. var oldIndex = this.getIndexOfItem( item );
  19940. if ( index >= this._items.length ) {
  19941. throw new Error( "Index bigger than number of layers." );
  19942. }
  19943. if ( index === oldIndex || oldIndex === -1 ) {
  19944. return;
  19945. }
  19946. this._items.splice( oldIndex, 1 );
  19947. this._items.splice( index, 0, item );
  19948. this._needsDraw = true;
  19949. /**
  19950. * Raised when the order of the indexes has been changed.
  19951. * @event item-index-change
  19952. * @memberOf OpenSeadragon.World
  19953. * @type {object}
  19954. * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.
  19955. * @property {OpenSeadragon.TiledImage} item - The item whose index has
  19956. * been changed
  19957. * @property {Number} previousIndex - The previous index of the item
  19958. * @property {Number} newIndex - The new index of the item
  19959. * @property {?Object} userData - Arbitrary subscriber-defined object.
  19960. */
  19961. this.raiseEvent( 'item-index-change', {
  19962. item: item,
  19963. previousIndex: oldIndex,
  19964. newIndex: index
  19965. } );
  19966. },
  19967. /**
  19968. * Remove an item.
  19969. * @param {OpenSeadragon.TiledImage} item - The item to remove.
  19970. * @fires OpenSeadragon.World.event:remove-item
  19971. * @fires OpenSeadragon.World.event:metrics-change
  19972. */
  19973. removeItem: function( item ) {
  19974. $.console.assert(item, "[World.removeItem] item is required");
  19975. var index = $.indexOf(this._items, item );
  19976. if ( index === -1 ) {
  19977. return;
  19978. }
  19979. item.removeHandler('bounds-change', this._delegatedFigureSizes);
  19980. item.removeHandler('clip-change', this._delegatedFigureSizes);
  19981. item.destroy();
  19982. this._items.splice( index, 1 );
  19983. this._figureSizes();
  19984. this._needsDraw = true;
  19985. this._raiseRemoveItem(item);
  19986. },
  19987. /**
  19988. * Remove all items.
  19989. * @fires OpenSeadragon.World.event:remove-item
  19990. * @fires OpenSeadragon.World.event:metrics-change
  19991. */
  19992. removeAll: function() {
  19993. // We need to make sure any pending images are canceled so the world items don't get messed up
  19994. this.viewer._cancelPendingImages();
  19995. var item;
  19996. var i;
  19997. for (i = 0; i < this._items.length; i++) {
  19998. item = this._items[i];
  19999. item.removeHandler('bounds-change', this._delegatedFigureSizes);
  20000. item.removeHandler('clip-change', this._delegatedFigureSizes);
  20001. item.destroy();
  20002. }
  20003. var removedItems = this._items;
  20004. this._items = [];
  20005. this._figureSizes();
  20006. this._needsDraw = true;
  20007. for (i = 0; i < removedItems.length; i++) {
  20008. item = removedItems[i];
  20009. this._raiseRemoveItem(item);
  20010. }
  20011. },
  20012. /**
  20013. * Clears all tiles and triggers updates for all items.
  20014. */
  20015. resetItems: function() {
  20016. for ( var i = 0; i < this._items.length; i++ ) {
  20017. this._items[i].reset();
  20018. }
  20019. },
  20020. /**
  20021. * Updates (i.e. animates bounds of) all items.
  20022. */
  20023. update: function() {
  20024. var animated = false;
  20025. for ( var i = 0; i < this._items.length; i++ ) {
  20026. animated = this._items[i].update() || animated;
  20027. }
  20028. return animated;
  20029. },
  20030. /**
  20031. * Draws all items.
  20032. */
  20033. draw: function() {
  20034. for ( var i = 0; i < this._items.length; i++ ) {
  20035. this._items[i].draw();
  20036. }
  20037. this._needsDraw = false;
  20038. },
  20039. /**
  20040. * @returns {Boolean} true if any items need updating.
  20041. */
  20042. needsDraw: function() {
  20043. for ( var i = 0; i < this._items.length; i++ ) {
  20044. if ( this._items[i].needsDraw() ) {
  20045. return true;
  20046. }
  20047. }
  20048. return this._needsDraw;
  20049. },
  20050. /**
  20051. * @returns {OpenSeadragon.Rect} The smallest rectangle that encloses all items, in viewport coordinates.
  20052. */
  20053. getHomeBounds: function() {
  20054. return this._homeBounds.clone();
  20055. },
  20056. /**
  20057. * To facilitate zoom constraints, we keep track of the pixel density of the
  20058. * densest item in the World (i.e. the item whose content size to viewport size
  20059. * ratio is the highest) and save it as this "content factor".
  20060. * @returns {Number} the number of content units per viewport unit.
  20061. */
  20062. getContentFactor: function() {
  20063. return this._contentFactor;
  20064. },
  20065. /**
  20066. * As a performance optimization, setting this flag to false allows the bounds-change event handler
  20067. * on tiledImages to skip calculations on the world bounds. If a lot of images are going to be positioned in
  20068. * rapid succession, this is a good idea. When finished, setAutoRefigureSizes should be called with true
  20069. * or the system may behave oddly.
  20070. * @param {Boolean} [value] The value to which to set the flag.
  20071. */
  20072. setAutoRefigureSizes: function(value) {
  20073. this._autoRefigureSizes = value;
  20074. if (value & this._needsSizesFigured) {
  20075. this._figureSizes();
  20076. this._needsSizesFigured = false;
  20077. }
  20078. },
  20079. /**
  20080. * Arranges all of the TiledImages with the specified settings.
  20081. * @param {Object} options - Specifies how to arrange.
  20082. * @param {Boolean} [options.immediately=false] - Whether to animate to the new arrangement.
  20083. * @param {String} [options.layout] - See collectionLayout in {@link OpenSeadragon.Options}.
  20084. * @param {Number} [options.rows] - See collectionRows in {@link OpenSeadragon.Options}.
  20085. * @param {Number} [options.columns] - See collectionColumns in {@link OpenSeadragon.Options}.
  20086. * @param {Number} [options.tileSize] - See collectionTileSize in {@link OpenSeadragon.Options}.
  20087. * @param {Number} [options.tileMargin] - See collectionTileMargin in {@link OpenSeadragon.Options}.
  20088. * @fires OpenSeadragon.World.event:metrics-change
  20089. */
  20090. arrange: function(options) {
  20091. options = options || {};
  20092. var immediately = options.immediately || false;
  20093. var layout = options.layout || $.DEFAULT_SETTINGS.collectionLayout;
  20094. var rows = options.rows || $.DEFAULT_SETTINGS.collectionRows;
  20095. var columns = options.columns || $.DEFAULT_SETTINGS.collectionColumns;
  20096. var tileSize = options.tileSize || $.DEFAULT_SETTINGS.collectionTileSize;
  20097. var tileMargin = options.tileMargin || $.DEFAULT_SETTINGS.collectionTileMargin;
  20098. var increment = tileSize + tileMargin;
  20099. var wrap;
  20100. if (!options.rows && columns) {
  20101. wrap = columns;
  20102. } else {
  20103. wrap = Math.ceil(this._items.length / rows);
  20104. }
  20105. var x = 0;
  20106. var y = 0;
  20107. var item, box, width, height, position;
  20108. this.setAutoRefigureSizes(false);
  20109. for (var i = 0; i < this._items.length; i++) {
  20110. if (i && (i % wrap) === 0) {
  20111. if (layout === 'horizontal') {
  20112. y += increment;
  20113. x = 0;
  20114. } else {
  20115. x += increment;
  20116. y = 0;
  20117. }
  20118. }
  20119. item = this._items[i];
  20120. box = item.getBounds();
  20121. if (box.width > box.height) {
  20122. width = tileSize;
  20123. } else {
  20124. width = tileSize * (box.width / box.height);
  20125. }
  20126. height = width * (box.height / box.width);
  20127. position = new $.Point(x + ((tileSize - width) / 2),
  20128. y + ((tileSize - height) / 2));
  20129. item.setPosition(position, immediately);
  20130. item.setWidth(width, immediately);
  20131. if (layout === 'horizontal') {
  20132. x += increment;
  20133. } else {
  20134. y += increment;
  20135. }
  20136. }
  20137. this.setAutoRefigureSizes(true);
  20138. },
  20139. // private
  20140. _figureSizes: function() {
  20141. var oldHomeBounds = this._homeBounds ? this._homeBounds.clone() : null;
  20142. var oldContentSize = this._contentSize ? this._contentSize.clone() : null;
  20143. var oldContentFactor = this._contentFactor || 0;
  20144. if (!this._items.length) {
  20145. this._homeBounds = new $.Rect(0, 0, 1, 1);
  20146. this._contentSize = new $.Point(1, 1);
  20147. this._contentFactor = 1;
  20148. } else {
  20149. var item = this._items[0];
  20150. var bounds = item.getBounds();
  20151. this._contentFactor = item.getContentSize().x / bounds.width;
  20152. var clippedBounds = item.getClippedBounds().getBoundingBox();
  20153. var left = clippedBounds.x;
  20154. var top = clippedBounds.y;
  20155. var right = clippedBounds.x + clippedBounds.width;
  20156. var bottom = clippedBounds.y + clippedBounds.height;
  20157. for (var i = 1; i < this._items.length; i++) {
  20158. item = this._items[i];
  20159. bounds = item.getBounds();
  20160. this._contentFactor = Math.max(this._contentFactor,
  20161. item.getContentSize().x / bounds.width);
  20162. clippedBounds = item.getClippedBounds().getBoundingBox();
  20163. left = Math.min(left, clippedBounds.x);
  20164. top = Math.min(top, clippedBounds.y);
  20165. right = Math.max(right, clippedBounds.x + clippedBounds.width);
  20166. bottom = Math.max(bottom, clippedBounds.y + clippedBounds.height);
  20167. }
  20168. this._homeBounds = new $.Rect(left, top, right - left, bottom - top);
  20169. this._contentSize = new $.Point(
  20170. this._homeBounds.width * this._contentFactor,
  20171. this._homeBounds.height * this._contentFactor);
  20172. }
  20173. if (this._contentFactor !== oldContentFactor ||
  20174. !this._homeBounds.equals(oldHomeBounds) ||
  20175. !this._contentSize.equals(oldContentSize)) {
  20176. /**
  20177. * Raised when the home bounds or content factor change.
  20178. * @event metrics-change
  20179. * @memberOf OpenSeadragon.World
  20180. * @type {object}
  20181. * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.
  20182. * @property {?Object} userData - Arbitrary subscriber-defined object.
  20183. */
  20184. this.raiseEvent('metrics-change', {});
  20185. }
  20186. },
  20187. // private
  20188. _raiseRemoveItem: function(item) {
  20189. /**
  20190. * Raised when an item is removed.
  20191. * @event remove-item
  20192. * @memberOf OpenSeadragon.World
  20193. * @type {object}
  20194. * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.
  20195. * @property {OpenSeadragon.TiledImage} item - The item's underlying item.
  20196. * @property {?Object} userData - Arbitrary subscriber-defined object.
  20197. */
  20198. this.raiseEvent( 'remove-item', { item: item } );
  20199. }
  20200. });
  20201. }( OpenSeadragon ));
  20202. //# sourceMappingURL=openseadragon.js.map
  20203. ;/*!
  20204. * Bootstrap v4.4.1 (https://getbootstrap.com/)
  20205. * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
  20206. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
  20207. */
  20208. (function (global, factory) {
  20209. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('jquery'), require('popper.js')) :
  20210. typeof define === 'function' && define.amd ? define(['exports', 'jquery', 'popper.js'], factory) :
  20211. (global = global || self, factory(global.bootstrap = {}, global.jQuery, global.Popper));
  20212. }(this, (function (exports, $, Popper) { 'use strict';
  20213. $ = $ && $.hasOwnProperty('default') ? $['default'] : $;
  20214. Popper = Popper && Popper.hasOwnProperty('default') ? Popper['default'] : Popper;
  20215. function _defineProperties(target, props) {
  20216. for (var i = 0; i < props.length; i++) {
  20217. var descriptor = props[i];
  20218. descriptor.enumerable = descriptor.enumerable || false;
  20219. descriptor.configurable = true;
  20220. if ("value" in descriptor) descriptor.writable = true;
  20221. Object.defineProperty(target, descriptor.key, descriptor);
  20222. }
  20223. }
  20224. function _createClass(Constructor, protoProps, staticProps) {
  20225. if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  20226. if (staticProps) _defineProperties(Constructor, staticProps);
  20227. return Constructor;
  20228. }
  20229. function _defineProperty(obj, key, value) {
  20230. if (key in obj) {
  20231. Object.defineProperty(obj, key, {
  20232. value: value,
  20233. enumerable: true,
  20234. configurable: true,
  20235. writable: true
  20236. });
  20237. } else {
  20238. obj[key] = value;
  20239. }
  20240. return obj;
  20241. }
  20242. function ownKeys(object, enumerableOnly) {
  20243. var keys = Object.keys(object);
  20244. if (Object.getOwnPropertySymbols) {
  20245. var symbols = Object.getOwnPropertySymbols(object);
  20246. if (enumerableOnly) symbols = symbols.filter(function (sym) {
  20247. return Object.getOwnPropertyDescriptor(object, sym).enumerable;
  20248. });
  20249. keys.push.apply(keys, symbols);
  20250. }
  20251. return keys;
  20252. }
  20253. function _objectSpread2(target) {
  20254. for (var i = 1; i < arguments.length; i++) {
  20255. var source = arguments[i] != null ? arguments[i] : {};
  20256. if (i % 2) {
  20257. ownKeys(Object(source), true).forEach(function (key) {
  20258. _defineProperty(target, key, source[key]);
  20259. });
  20260. } else if (Object.getOwnPropertyDescriptors) {
  20261. Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
  20262. } else {
  20263. ownKeys(Object(source)).forEach(function (key) {
  20264. Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
  20265. });
  20266. }
  20267. }
  20268. return target;
  20269. }
  20270. function _inheritsLoose(subClass, superClass) {
  20271. subClass.prototype = Object.create(superClass.prototype);
  20272. subClass.prototype.constructor = subClass;
  20273. subClass.__proto__ = superClass;
  20274. }
  20275. /**
  20276. * --------------------------------------------------------------------------
  20277. * Bootstrap (v4.4.1): util.js
  20278. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
  20279. * --------------------------------------------------------------------------
  20280. */
  20281. /**
  20282. * ------------------------------------------------------------------------
  20283. * Private TransitionEnd Helpers
  20284. * ------------------------------------------------------------------------
  20285. */
  20286. var TRANSITION_END = 'transitionend';
  20287. var MAX_UID = 1000000;
  20288. var MILLISECONDS_MULTIPLIER = 1000; // Shoutout AngusCroll (https://goo.gl/pxwQGp)
  20289. function toType(obj) {
  20290. return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase();
  20291. }
  20292. function getSpecialTransitionEndEvent() {
  20293. return {
  20294. bindType: TRANSITION_END,
  20295. delegateType: TRANSITION_END,
  20296. handle: function handle(event) {
  20297. if ($(event.target).is(this)) {
  20298. return event.handleObj.handler.apply(this, arguments); // eslint-disable-line prefer-rest-params
  20299. }
  20300. return undefined; // eslint-disable-line no-undefined
  20301. }
  20302. };
  20303. }
  20304. function transitionEndEmulator(duration) {
  20305. var _this = this;
  20306. var called = false;
  20307. $(this).one(Util.TRANSITION_END, function () {
  20308. called = true;
  20309. });
  20310. setTimeout(function () {
  20311. if (!called) {
  20312. Util.triggerTransitionEnd(_this);
  20313. }
  20314. }, duration);
  20315. return this;
  20316. }
  20317. function setTransitionEndSupport() {
  20318. $.fn.emulateTransitionEnd = transitionEndEmulator;
  20319. $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent();
  20320. }
  20321. /**
  20322. * --------------------------------------------------------------------------
  20323. * Public Util Api
  20324. * --------------------------------------------------------------------------
  20325. */
  20326. var Util = {
  20327. TRANSITION_END: 'bsTransitionEnd',
  20328. getUID: function getUID(prefix) {
  20329. do {
  20330. // eslint-disable-next-line no-bitwise
  20331. prefix += ~~(Math.random() * MAX_UID); // "~~" acts like a faster Math.floor() here
  20332. } while (document.getElementById(prefix));
  20333. return prefix;
  20334. },
  20335. getSelectorFromElement: function getSelectorFromElement(element) {
  20336. var selector = element.getAttribute('data-target');
  20337. if (!selector || selector === '#') {
  20338. var hrefAttr = element.getAttribute('href');
  20339. selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : '';
  20340. }
  20341. try {
  20342. return document.querySelector(selector) ? selector : null;
  20343. } catch (err) {
  20344. return null;
  20345. }
  20346. },
  20347. getTransitionDurationFromElement: function getTransitionDurationFromElement(element) {
  20348. if (!element) {
  20349. return 0;
  20350. } // Get transition-duration of the element
  20351. var transitionDuration = $(element).css('transition-duration');
  20352. var transitionDelay = $(element).css('transition-delay');
  20353. var floatTransitionDuration = parseFloat(transitionDuration);
  20354. var floatTransitionDelay = parseFloat(transitionDelay); // Return 0 if element or transition duration is not found
  20355. if (!floatTransitionDuration && !floatTransitionDelay) {
  20356. return 0;
  20357. } // If multiple durations are defined, take the first
  20358. transitionDuration = transitionDuration.split(',')[0];
  20359. transitionDelay = transitionDelay.split(',')[0];
  20360. return (parseFloat(transitionDuration) + parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;
  20361. },
  20362. reflow: function reflow(element) {
  20363. return element.offsetHeight;
  20364. },
  20365. triggerTransitionEnd: function triggerTransitionEnd(element) {
  20366. $(element).trigger(TRANSITION_END);
  20367. },
  20368. // TODO: Remove in v5
  20369. supportsTransitionEnd: function supportsTransitionEnd() {
  20370. return Boolean(TRANSITION_END);
  20371. },
  20372. isElement: function isElement(obj) {
  20373. return (obj[0] || obj).nodeType;
  20374. },
  20375. typeCheckConfig: function typeCheckConfig(componentName, config, configTypes) {
  20376. for (var property in configTypes) {
  20377. if (Object.prototype.hasOwnProperty.call(configTypes, property)) {
  20378. var expectedTypes = configTypes[property];
  20379. var value = config[property];
  20380. var valueType = value && Util.isElement(value) ? 'element' : toType(value);
  20381. if (!new RegExp(expectedTypes).test(valueType)) {
  20382. throw new Error(componentName.toUpperCase() + ": " + ("Option \"" + property + "\" provided type \"" + valueType + "\" ") + ("but expected type \"" + expectedTypes + "\"."));
  20383. }
  20384. }
  20385. }
  20386. },
  20387. findShadowRoot: function findShadowRoot(element) {
  20388. if (!document.documentElement.attachShadow) {
  20389. return null;
  20390. } // Can find the shadow root otherwise it'll return the document
  20391. if (typeof element.getRootNode === 'function') {
  20392. var root = element.getRootNode();
  20393. return root instanceof ShadowRoot ? root : null;
  20394. }
  20395. if (element instanceof ShadowRoot) {
  20396. return element;
  20397. } // when we don't find a shadow root
  20398. if (!element.parentNode) {
  20399. return null;
  20400. }
  20401. return Util.findShadowRoot(element.parentNode);
  20402. },
  20403. jQueryDetection: function jQueryDetection() {
  20404. if (typeof $ === 'undefined') {
  20405. throw new TypeError('Bootstrap\'s JavaScript requires jQuery. jQuery must be included before Bootstrap\'s JavaScript.');
  20406. }
  20407. var version = $.fn.jquery.split(' ')[0].split('.');
  20408. var minMajor = 1;
  20409. var ltMajor = 2;
  20410. var minMinor = 9;
  20411. var minPatch = 1;
  20412. var maxMajor = 4;
  20413. if (version[0] < ltMajor && version[1] < minMinor || version[0] === minMajor && version[1] === minMinor && version[2] < minPatch || version[0] >= maxMajor) {
  20414. throw new Error('Bootstrap\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0');
  20415. }
  20416. }
  20417. };
  20418. Util.jQueryDetection();
  20419. setTransitionEndSupport();
  20420. /**
  20421. * ------------------------------------------------------------------------
  20422. * Constants
  20423. * ------------------------------------------------------------------------
  20424. */
  20425. var NAME = 'alert';
  20426. var VERSION = '4.4.1';
  20427. var DATA_KEY = 'bs.alert';
  20428. var EVENT_KEY = "." + DATA_KEY;
  20429. var DATA_API_KEY = '.data-api';
  20430. var JQUERY_NO_CONFLICT = $.fn[NAME];
  20431. var Selector = {
  20432. DISMISS: '[data-dismiss="alert"]'
  20433. };
  20434. var Event = {
  20435. CLOSE: "close" + EVENT_KEY,
  20436. CLOSED: "closed" + EVENT_KEY,
  20437. CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY
  20438. };
  20439. var ClassName = {
  20440. ALERT: 'alert',
  20441. FADE: 'fade',
  20442. SHOW: 'show'
  20443. };
  20444. /**
  20445. * ------------------------------------------------------------------------
  20446. * Class Definition
  20447. * ------------------------------------------------------------------------
  20448. */
  20449. var Alert =
  20450. /*#__PURE__*/
  20451. function () {
  20452. function Alert(element) {
  20453. this._element = element;
  20454. } // Getters
  20455. var _proto = Alert.prototype;
  20456. // Public
  20457. _proto.close = function close(element) {
  20458. var rootElement = this._element;
  20459. if (element) {
  20460. rootElement = this._getRootElement(element);
  20461. }
  20462. var customEvent = this._triggerCloseEvent(rootElement);
  20463. if (customEvent.isDefaultPrevented()) {
  20464. return;
  20465. }
  20466. this._removeElement(rootElement);
  20467. };
  20468. _proto.dispose = function dispose() {
  20469. $.removeData(this._element, DATA_KEY);
  20470. this._element = null;
  20471. } // Private
  20472. ;
  20473. _proto._getRootElement = function _getRootElement(element) {
  20474. var selector = Util.getSelectorFromElement(element);
  20475. var parent = false;
  20476. if (selector) {
  20477. parent = document.querySelector(selector);
  20478. }
  20479. if (!parent) {
  20480. parent = $(element).closest("." + ClassName.ALERT)[0];
  20481. }
  20482. return parent;
  20483. };
  20484. _proto._triggerCloseEvent = function _triggerCloseEvent(element) {
  20485. var closeEvent = $.Event(Event.CLOSE);
  20486. $(element).trigger(closeEvent);
  20487. return closeEvent;
  20488. };
  20489. _proto._removeElement = function _removeElement(element) {
  20490. var _this = this;
  20491. $(element).removeClass(ClassName.SHOW);
  20492. if (!$(element).hasClass(ClassName.FADE)) {
  20493. this._destroyElement(element);
  20494. return;
  20495. }
  20496. var transitionDuration = Util.getTransitionDurationFromElement(element);
  20497. $(element).one(Util.TRANSITION_END, function (event) {
  20498. return _this._destroyElement(element, event);
  20499. }).emulateTransitionEnd(transitionDuration);
  20500. };
  20501. _proto._destroyElement = function _destroyElement(element) {
  20502. $(element).detach().trigger(Event.CLOSED).remove();
  20503. } // Static
  20504. ;
  20505. Alert._jQueryInterface = function _jQueryInterface(config) {
  20506. return this.each(function () {
  20507. var $element = $(this);
  20508. var data = $element.data(DATA_KEY);
  20509. if (!data) {
  20510. data = new Alert(this);
  20511. $element.data(DATA_KEY, data);
  20512. }
  20513. if (config === 'close') {
  20514. data[config](this);
  20515. }
  20516. });
  20517. };
  20518. Alert._handleDismiss = function _handleDismiss(alertInstance) {
  20519. return function (event) {
  20520. if (event) {
  20521. event.preventDefault();
  20522. }
  20523. alertInstance.close(this);
  20524. };
  20525. };
  20526. _createClass(Alert, null, [{
  20527. key: "VERSION",
  20528. get: function get() {
  20529. return VERSION;
  20530. }
  20531. }]);
  20532. return Alert;
  20533. }();
  20534. /**
  20535. * ------------------------------------------------------------------------
  20536. * Data Api implementation
  20537. * ------------------------------------------------------------------------
  20538. */
  20539. $(document).on(Event.CLICK_DATA_API, Selector.DISMISS, Alert._handleDismiss(new Alert()));
  20540. /**
  20541. * ------------------------------------------------------------------------
  20542. * jQuery
  20543. * ------------------------------------------------------------------------
  20544. */
  20545. $.fn[NAME] = Alert._jQueryInterface;
  20546. $.fn[NAME].Constructor = Alert;
  20547. $.fn[NAME].noConflict = function () {
  20548. $.fn[NAME] = JQUERY_NO_CONFLICT;
  20549. return Alert._jQueryInterface;
  20550. };
  20551. /**
  20552. * ------------------------------------------------------------------------
  20553. * Constants
  20554. * ------------------------------------------------------------------------
  20555. */
  20556. var NAME$1 = 'button';
  20557. var VERSION$1 = '4.4.1';
  20558. var DATA_KEY$1 = 'bs.button';
  20559. var EVENT_KEY$1 = "." + DATA_KEY$1;
  20560. var DATA_API_KEY$1 = '.data-api';
  20561. var JQUERY_NO_CONFLICT$1 = $.fn[NAME$1];
  20562. var ClassName$1 = {
  20563. ACTIVE: 'active',
  20564. BUTTON: 'btn',
  20565. FOCUS: 'focus'
  20566. };
  20567. var Selector$1 = {
  20568. DATA_TOGGLE_CARROT: '[data-toggle^="button"]',
  20569. DATA_TOGGLES: '[data-toggle="buttons"]',
  20570. DATA_TOGGLE: '[data-toggle="button"]',
  20571. DATA_TOGGLES_BUTTONS: '[data-toggle="buttons"] .btn',
  20572. INPUT: 'input:not([type="hidden"])',
  20573. ACTIVE: '.active',
  20574. BUTTON: '.btn'
  20575. };
  20576. var Event$1 = {
  20577. CLICK_DATA_API: "click" + EVENT_KEY$1 + DATA_API_KEY$1,
  20578. FOCUS_BLUR_DATA_API: "focus" + EVENT_KEY$1 + DATA_API_KEY$1 + " " + ("blur" + EVENT_KEY$1 + DATA_API_KEY$1),
  20579. LOAD_DATA_API: "load" + EVENT_KEY$1 + DATA_API_KEY$1
  20580. };
  20581. /**
  20582. * ------------------------------------------------------------------------
  20583. * Class Definition
  20584. * ------------------------------------------------------------------------
  20585. */
  20586. var Button =
  20587. /*#__PURE__*/
  20588. function () {
  20589. function Button(element) {
  20590. this._element = element;
  20591. } // Getters
  20592. var _proto = Button.prototype;
  20593. // Public
  20594. _proto.toggle = function toggle() {
  20595. var triggerChangeEvent = true;
  20596. var addAriaPressed = true;
  20597. var rootElement = $(this._element).closest(Selector$1.DATA_TOGGLES)[0];
  20598. if (rootElement) {
  20599. var input = this._element.querySelector(Selector$1.INPUT);
  20600. if (input) {
  20601. if (input.type === 'radio') {
  20602. if (input.checked && this._element.classList.contains(ClassName$1.ACTIVE)) {
  20603. triggerChangeEvent = false;
  20604. } else {
  20605. var activeElement = rootElement.querySelector(Selector$1.ACTIVE);
  20606. if (activeElement) {
  20607. $(activeElement).removeClass(ClassName$1.ACTIVE);
  20608. }
  20609. }
  20610. } else if (input.type === 'checkbox') {
  20611. if (this._element.tagName === 'LABEL' && input.checked === this._element.classList.contains(ClassName$1.ACTIVE)) {
  20612. triggerChangeEvent = false;
  20613. }
  20614. } else {
  20615. // if it's not a radio button or checkbox don't add a pointless/invalid checked property to the input
  20616. triggerChangeEvent = false;
  20617. }
  20618. if (triggerChangeEvent) {
  20619. input.checked = !this._element.classList.contains(ClassName$1.ACTIVE);
  20620. $(input).trigger('change');
  20621. }
  20622. input.focus();
  20623. addAriaPressed = false;
  20624. }
  20625. }
  20626. if (!(this._element.hasAttribute('disabled') || this._element.classList.contains('disabled'))) {
  20627. if (addAriaPressed) {
  20628. this._element.setAttribute('aria-pressed', !this._element.classList.contains(ClassName$1.ACTIVE));
  20629. }
  20630. if (triggerChangeEvent) {
  20631. $(this._element).toggleClass(ClassName$1.ACTIVE);
  20632. }
  20633. }
  20634. };
  20635. _proto.dispose = function dispose() {
  20636. $.removeData(this._element, DATA_KEY$1);
  20637. this._element = null;
  20638. } // Static
  20639. ;
  20640. Button._jQueryInterface = function _jQueryInterface(config) {
  20641. return this.each(function () {
  20642. var data = $(this).data(DATA_KEY$1);
  20643. if (!data) {
  20644. data = new Button(this);
  20645. $(this).data(DATA_KEY$1, data);
  20646. }
  20647. if (config === 'toggle') {
  20648. data[config]();
  20649. }
  20650. });
  20651. };
  20652. _createClass(Button, null, [{
  20653. key: "VERSION",
  20654. get: function get() {
  20655. return VERSION$1;
  20656. }
  20657. }]);
  20658. return Button;
  20659. }();
  20660. /**
  20661. * ------------------------------------------------------------------------
  20662. * Data Api implementation
  20663. * ------------------------------------------------------------------------
  20664. */
  20665. $(document).on(Event$1.CLICK_DATA_API, Selector$1.DATA_TOGGLE_CARROT, function (event) {
  20666. var button = event.target;
  20667. if (!$(button).hasClass(ClassName$1.BUTTON)) {
  20668. button = $(button).closest(Selector$1.BUTTON)[0];
  20669. }
  20670. if (!button || button.hasAttribute('disabled') || button.classList.contains('disabled')) {
  20671. event.preventDefault(); // work around Firefox bug #1540995
  20672. } else {
  20673. var inputBtn = button.querySelector(Selector$1.INPUT);
  20674. if (inputBtn && (inputBtn.hasAttribute('disabled') || inputBtn.classList.contains('disabled'))) {
  20675. event.preventDefault(); // work around Firefox bug #1540995
  20676. return;
  20677. }
  20678. Button._jQueryInterface.call($(button), 'toggle');
  20679. }
  20680. }).on(Event$1.FOCUS_BLUR_DATA_API, Selector$1.DATA_TOGGLE_CARROT, function (event) {
  20681. var button = $(event.target).closest(Selector$1.BUTTON)[0];
  20682. $(button).toggleClass(ClassName$1.FOCUS, /^focus(in)?$/.test(event.type));
  20683. });
  20684. $(window).on(Event$1.LOAD_DATA_API, function () {
  20685. // ensure correct active class is set to match the controls' actual values/states
  20686. // find all checkboxes/readio buttons inside data-toggle groups
  20687. var buttons = [].slice.call(document.querySelectorAll(Selector$1.DATA_TOGGLES_BUTTONS));
  20688. for (var i = 0, len = buttons.length; i < len; i++) {
  20689. var button = buttons[i];
  20690. var input = button.querySelector(Selector$1.INPUT);
  20691. if (input.checked || input.hasAttribute('checked')) {
  20692. button.classList.add(ClassName$1.ACTIVE);
  20693. } else {
  20694. button.classList.remove(ClassName$1.ACTIVE);
  20695. }
  20696. } // find all button toggles
  20697. buttons = [].slice.call(document.querySelectorAll(Selector$1.DATA_TOGGLE));
  20698. for (var _i = 0, _len = buttons.length; _i < _len; _i++) {
  20699. var _button = buttons[_i];
  20700. if (_button.getAttribute('aria-pressed') === 'true') {
  20701. _button.classList.add(ClassName$1.ACTIVE);
  20702. } else {
  20703. _button.classList.remove(ClassName$1.ACTIVE);
  20704. }
  20705. }
  20706. });
  20707. /**
  20708. * ------------------------------------------------------------------------
  20709. * jQuery
  20710. * ------------------------------------------------------------------------
  20711. */
  20712. $.fn[NAME$1] = Button._jQueryInterface;
  20713. $.fn[NAME$1].Constructor = Button;
  20714. $.fn[NAME$1].noConflict = function () {
  20715. $.fn[NAME$1] = JQUERY_NO_CONFLICT$1;
  20716. return Button._jQueryInterface;
  20717. };
  20718. /**
  20719. * ------------------------------------------------------------------------
  20720. * Constants
  20721. * ------------------------------------------------------------------------
  20722. */
  20723. var NAME$2 = 'carousel';
  20724. var VERSION$2 = '4.4.1';
  20725. var DATA_KEY$2 = 'bs.carousel';
  20726. var EVENT_KEY$2 = "." + DATA_KEY$2;
  20727. var DATA_API_KEY$2 = '.data-api';
  20728. var JQUERY_NO_CONFLICT$2 = $.fn[NAME$2];
  20729. var ARROW_LEFT_KEYCODE = 37; // KeyboardEvent.which value for left arrow key
  20730. var ARROW_RIGHT_KEYCODE = 39; // KeyboardEvent.which value for right arrow key
  20731. var TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch
  20732. var SWIPE_THRESHOLD = 40;
  20733. var Default = {
  20734. interval: 5000,
  20735. keyboard: true,
  20736. slide: false,
  20737. pause: 'hover',
  20738. wrap: true,
  20739. touch: true
  20740. };
  20741. var DefaultType = {
  20742. interval: '(number|boolean)',
  20743. keyboard: 'boolean',
  20744. slide: '(boolean|string)',
  20745. pause: '(string|boolean)',
  20746. wrap: 'boolean',
  20747. touch: 'boolean'
  20748. };
  20749. var Direction = {
  20750. NEXT: 'next',
  20751. PREV: 'prev',
  20752. LEFT: 'left',
  20753. RIGHT: 'right'
  20754. };
  20755. var Event$2 = {
  20756. SLIDE: "slide" + EVENT_KEY$2,
  20757. SLID: "slid" + EVENT_KEY$2,
  20758. KEYDOWN: "keydown" + EVENT_KEY$2,
  20759. MOUSEENTER: "mouseenter" + EVENT_KEY$2,
  20760. MOUSELEAVE: "mouseleave" + EVENT_KEY$2,
  20761. TOUCHSTART: "touchstart" + EVENT_KEY$2,
  20762. TOUCHMOVE: "touchmove" + EVENT_KEY$2,
  20763. TOUCHEND: "touchend" + EVENT_KEY$2,
  20764. POINTERDOWN: "pointerdown" + EVENT_KEY$2,
  20765. POINTERUP: "pointerup" + EVENT_KEY$2,
  20766. DRAG_START: "dragstart" + EVENT_KEY$2,
  20767. LOAD_DATA_API: "load" + EVENT_KEY$2 + DATA_API_KEY$2,
  20768. CLICK_DATA_API: "click" + EVENT_KEY$2 + DATA_API_KEY$2
  20769. };
  20770. var ClassName$2 = {
  20771. CAROUSEL: 'carousel',
  20772. ACTIVE: 'active',
  20773. SLIDE: 'slide',
  20774. RIGHT: 'carousel-item-right',
  20775. LEFT: 'carousel-item-left',
  20776. NEXT: 'carousel-item-next',
  20777. PREV: 'carousel-item-prev',
  20778. ITEM: 'carousel-item',
  20779. POINTER_EVENT: 'pointer-event'
  20780. };
  20781. var Selector$2 = {
  20782. ACTIVE: '.active',
  20783. ACTIVE_ITEM: '.active.carousel-item',
  20784. ITEM: '.carousel-item',
  20785. ITEM_IMG: '.carousel-item img',
  20786. NEXT_PREV: '.carousel-item-next, .carousel-item-prev',
  20787. INDICATORS: '.carousel-indicators',
  20788. DATA_SLIDE: '[data-slide], [data-slide-to]',
  20789. DATA_RIDE: '[data-ride="carousel"]'
  20790. };
  20791. var PointerType = {
  20792. TOUCH: 'touch',
  20793. PEN: 'pen'
  20794. };
  20795. /**
  20796. * ------------------------------------------------------------------------
  20797. * Class Definition
  20798. * ------------------------------------------------------------------------
  20799. */
  20800. var Carousel =
  20801. /*#__PURE__*/
  20802. function () {
  20803. function Carousel(element, config) {
  20804. this._items = null;
  20805. this._interval = null;
  20806. this._activeElement = null;
  20807. this._isPaused = false;
  20808. this._isSliding = false;
  20809. this.touchTimeout = null;
  20810. this.touchStartX = 0;
  20811. this.touchDeltaX = 0;
  20812. this._config = this._getConfig(config);
  20813. this._element = element;
  20814. this._indicatorsElement = this._element.querySelector(Selector$2.INDICATORS);
  20815. this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;
  20816. this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent);
  20817. this._addEventListeners();
  20818. } // Getters
  20819. var _proto = Carousel.prototype;
  20820. // Public
  20821. _proto.next = function next() {
  20822. if (!this._isSliding) {
  20823. this._slide(Direction.NEXT);
  20824. }
  20825. };
  20826. _proto.nextWhenVisible = function nextWhenVisible() {
  20827. // Don't call next when the page isn't visible
  20828. // or the carousel or its parent isn't visible
  20829. if (!document.hidden && $(this._element).is(':visible') && $(this._element).css('visibility') !== 'hidden') {
  20830. this.next();
  20831. }
  20832. };
  20833. _proto.prev = function prev() {
  20834. if (!this._isSliding) {
  20835. this._slide(Direction.PREV);
  20836. }
  20837. };
  20838. _proto.pause = function pause(event) {
  20839. if (!event) {
  20840. this._isPaused = true;
  20841. }
  20842. if (this._element.querySelector(Selector$2.NEXT_PREV)) {
  20843. Util.triggerTransitionEnd(this._element);
  20844. this.cycle(true);
  20845. }
  20846. clearInterval(this._interval);
  20847. this._interval = null;
  20848. };
  20849. _proto.cycle = function cycle(event) {
  20850. if (!event) {
  20851. this._isPaused = false;
  20852. }
  20853. if (this._interval) {
  20854. clearInterval(this._interval);
  20855. this._interval = null;
  20856. }
  20857. if (this._config.interval && !this._isPaused) {
  20858. this._interval = setInterval((document.visibilityState ? this.nextWhenVisible : this.next).bind(this), this._config.interval);
  20859. }
  20860. };
  20861. _proto.to = function to(index) {
  20862. var _this = this;
  20863. this._activeElement = this._element.querySelector(Selector$2.ACTIVE_ITEM);
  20864. var activeIndex = this._getItemIndex(this._activeElement);
  20865. if (index > this._items.length - 1 || index < 0) {
  20866. return;
  20867. }
  20868. if (this._isSliding) {
  20869. $(this._element).one(Event$2.SLID, function () {
  20870. return _this.to(index);
  20871. });
  20872. return;
  20873. }
  20874. if (activeIndex === index) {
  20875. this.pause();
  20876. this.cycle();
  20877. return;
  20878. }
  20879. var direction = index > activeIndex ? Direction.NEXT : Direction.PREV;
  20880. this._slide(direction, this._items[index]);
  20881. };
  20882. _proto.dispose = function dispose() {
  20883. $(this._element).off(EVENT_KEY$2);
  20884. $.removeData(this._element, DATA_KEY$2);
  20885. this._items = null;
  20886. this._config = null;
  20887. this._element = null;
  20888. this._interval = null;
  20889. this._isPaused = null;
  20890. this._isSliding = null;
  20891. this._activeElement = null;
  20892. this._indicatorsElement = null;
  20893. } // Private
  20894. ;
  20895. _proto._getConfig = function _getConfig(config) {
  20896. config = _objectSpread2({}, Default, {}, config);
  20897. Util.typeCheckConfig(NAME$2, config, DefaultType);
  20898. return config;
  20899. };
  20900. _proto._handleSwipe = function _handleSwipe() {
  20901. var absDeltax = Math.abs(this.touchDeltaX);
  20902. if (absDeltax <= SWIPE_THRESHOLD) {
  20903. return;
  20904. }
  20905. var direction = absDeltax / this.touchDeltaX;
  20906. this.touchDeltaX = 0; // swipe left
  20907. if (direction > 0) {
  20908. this.prev();
  20909. } // swipe right
  20910. if (direction < 0) {
  20911. this.next();
  20912. }
  20913. };
  20914. _proto._addEventListeners = function _addEventListeners() {
  20915. var _this2 = this;
  20916. if (this._config.keyboard) {
  20917. $(this._element).on(Event$2.KEYDOWN, function (event) {
  20918. return _this2._keydown(event);
  20919. });
  20920. }
  20921. if (this._config.pause === 'hover') {
  20922. $(this._element).on(Event$2.MOUSEENTER, function (event) {
  20923. return _this2.pause(event);
  20924. }).on(Event$2.MOUSELEAVE, function (event) {
  20925. return _this2.cycle(event);
  20926. });
  20927. }
  20928. if (this._config.touch) {
  20929. this._addTouchEventListeners();
  20930. }
  20931. };
  20932. _proto._addTouchEventListeners = function _addTouchEventListeners() {
  20933. var _this3 = this;
  20934. if (!this._touchSupported) {
  20935. return;
  20936. }
  20937. var start = function start(event) {
  20938. if (_this3._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {
  20939. _this3.touchStartX = event.originalEvent.clientX;
  20940. } else if (!_this3._pointerEvent) {
  20941. _this3.touchStartX = event.originalEvent.touches[0].clientX;
  20942. }
  20943. };
  20944. var move = function move(event) {
  20945. // ensure swiping with one touch and not pinching
  20946. if (event.originalEvent.touches && event.originalEvent.touches.length > 1) {
  20947. _this3.touchDeltaX = 0;
  20948. } else {
  20949. _this3.touchDeltaX = event.originalEvent.touches[0].clientX - _this3.touchStartX;
  20950. }
  20951. };
  20952. var end = function end(event) {
  20953. if (_this3._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {
  20954. _this3.touchDeltaX = event.originalEvent.clientX - _this3.touchStartX;
  20955. }
  20956. _this3._handleSwipe();
  20957. if (_this3._config.pause === 'hover') {
  20958. // If it's a touch-enabled device, mouseenter/leave are fired as
  20959. // part of the mouse compatibility events on first tap - the carousel
  20960. // would stop cycling until user tapped out of it;
  20961. // here, we listen for touchend, explicitly pause the carousel
  20962. // (as if it's the second time we tap on it, mouseenter compat event
  20963. // is NOT fired) and after a timeout (to allow for mouse compatibility
  20964. // events to fire) we explicitly restart cycling
  20965. _this3.pause();
  20966. if (_this3.touchTimeout) {
  20967. clearTimeout(_this3.touchTimeout);
  20968. }
  20969. _this3.touchTimeout = setTimeout(function (event) {
  20970. return _this3.cycle(event);
  20971. }, TOUCHEVENT_COMPAT_WAIT + _this3._config.interval);
  20972. }
  20973. };
  20974. $(this._element.querySelectorAll(Selector$2.ITEM_IMG)).on(Event$2.DRAG_START, function (e) {
  20975. return e.preventDefault();
  20976. });
  20977. if (this._pointerEvent) {
  20978. $(this._element).on(Event$2.POINTERDOWN, function (event) {
  20979. return start(event);
  20980. });
  20981. $(this._element).on(Event$2.POINTERUP, function (event) {
  20982. return end(event);
  20983. });
  20984. this._element.classList.add(ClassName$2.POINTER_EVENT);
  20985. } else {
  20986. $(this._element).on(Event$2.TOUCHSTART, function (event) {
  20987. return start(event);
  20988. });
  20989. $(this._element).on(Event$2.TOUCHMOVE, function (event) {
  20990. return move(event);
  20991. });
  20992. $(this._element).on(Event$2.TOUCHEND, function (event) {
  20993. return end(event);
  20994. });
  20995. }
  20996. };
  20997. _proto._keydown = function _keydown(event) {
  20998. if (/input|textarea/i.test(event.target.tagName)) {
  20999. return;
  21000. }
  21001. switch (event.which) {
  21002. case ARROW_LEFT_KEYCODE:
  21003. event.preventDefault();
  21004. this.prev();
  21005. break;
  21006. case ARROW_RIGHT_KEYCODE:
  21007. event.preventDefault();
  21008. this.next();
  21009. break;
  21010. }
  21011. };
  21012. _proto._getItemIndex = function _getItemIndex(element) {
  21013. this._items = element && element.parentNode ? [].slice.call(element.parentNode.querySelectorAll(Selector$2.ITEM)) : [];
  21014. return this._items.indexOf(element);
  21015. };
  21016. _proto._getItemByDirection = function _getItemByDirection(direction, activeElement) {
  21017. var isNextDirection = direction === Direction.NEXT;
  21018. var isPrevDirection = direction === Direction.PREV;
  21019. var activeIndex = this._getItemIndex(activeElement);
  21020. var lastItemIndex = this._items.length - 1;
  21021. var isGoingToWrap = isPrevDirection && activeIndex === 0 || isNextDirection && activeIndex === lastItemIndex;
  21022. if (isGoingToWrap && !this._config.wrap) {
  21023. return activeElement;
  21024. }
  21025. var delta = direction === Direction.PREV ? -1 : 1;
  21026. var itemIndex = (activeIndex + delta) % this._items.length;
  21027. return itemIndex === -1 ? this._items[this._items.length - 1] : this._items[itemIndex];
  21028. };
  21029. _proto._triggerSlideEvent = function _triggerSlideEvent(relatedTarget, eventDirectionName) {
  21030. var targetIndex = this._getItemIndex(relatedTarget);
  21031. var fromIndex = this._getItemIndex(this._element.querySelector(Selector$2.ACTIVE_ITEM));
  21032. var slideEvent = $.Event(Event$2.SLIDE, {
  21033. relatedTarget: relatedTarget,
  21034. direction: eventDirectionName,
  21035. from: fromIndex,
  21036. to: targetIndex
  21037. });
  21038. $(this._element).trigger(slideEvent);
  21039. return slideEvent;
  21040. };
  21041. _proto._setActiveIndicatorElement = function _setActiveIndicatorElement(element) {
  21042. if (this._indicatorsElement) {
  21043. var indicators = [].slice.call(this._indicatorsElement.querySelectorAll(Selector$2.ACTIVE));
  21044. $(indicators).removeClass(ClassName$2.ACTIVE);
  21045. var nextIndicator = this._indicatorsElement.children[this._getItemIndex(element)];
  21046. if (nextIndicator) {
  21047. $(nextIndicator).addClass(ClassName$2.ACTIVE);
  21048. }
  21049. }
  21050. };
  21051. _proto._slide = function _slide(direction, element) {
  21052. var _this4 = this;
  21053. var activeElement = this._element.querySelector(Selector$2.ACTIVE_ITEM);
  21054. var activeElementIndex = this._getItemIndex(activeElement);
  21055. var nextElement = element || activeElement && this._getItemByDirection(direction, activeElement);
  21056. var nextElementIndex = this._getItemIndex(nextElement);
  21057. var isCycling = Boolean(this._interval);
  21058. var directionalClassName;
  21059. var orderClassName;
  21060. var eventDirectionName;
  21061. if (direction === Direction.NEXT) {
  21062. directionalClassName = ClassName$2.LEFT;
  21063. orderClassName = ClassName$2.NEXT;
  21064. eventDirectionName = Direction.LEFT;
  21065. } else {
  21066. directionalClassName = ClassName$2.RIGHT;
  21067. orderClassName = ClassName$2.PREV;
  21068. eventDirectionName = Direction.RIGHT;
  21069. }
  21070. if (nextElement && $(nextElement).hasClass(ClassName$2.ACTIVE)) {
  21071. this._isSliding = false;
  21072. return;
  21073. }
  21074. var slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName);
  21075. if (slideEvent.isDefaultPrevented()) {
  21076. return;
  21077. }
  21078. if (!activeElement || !nextElement) {
  21079. // Some weirdness is happening, so we bail
  21080. return;
  21081. }
  21082. this._isSliding = true;
  21083. if (isCycling) {
  21084. this.pause();
  21085. }
  21086. this._setActiveIndicatorElement(nextElement);
  21087. var slidEvent = $.Event(Event$2.SLID, {
  21088. relatedTarget: nextElement,
  21089. direction: eventDirectionName,
  21090. from: activeElementIndex,
  21091. to: nextElementIndex
  21092. });
  21093. if ($(this._element).hasClass(ClassName$2.SLIDE)) {
  21094. $(nextElement).addClass(orderClassName);
  21095. Util.reflow(nextElement);
  21096. $(activeElement).addClass(directionalClassName);
  21097. $(nextElement).addClass(directionalClassName);
  21098. var nextElementInterval = parseInt(nextElement.getAttribute('data-interval'), 10);
  21099. if (nextElementInterval) {
  21100. this._config.defaultInterval = this._config.defaultInterval || this._config.interval;
  21101. this._config.interval = nextElementInterval;
  21102. } else {
  21103. this._config.interval = this._config.defaultInterval || this._config.interval;
  21104. }
  21105. var transitionDuration = Util.getTransitionDurationFromElement(activeElement);
  21106. $(activeElement).one(Util.TRANSITION_END, function () {
  21107. $(nextElement).removeClass(directionalClassName + " " + orderClassName).addClass(ClassName$2.ACTIVE);
  21108. $(activeElement).removeClass(ClassName$2.ACTIVE + " " + orderClassName + " " + directionalClassName);
  21109. _this4._isSliding = false;
  21110. setTimeout(function () {
  21111. return $(_this4._element).trigger(slidEvent);
  21112. }, 0);
  21113. }).emulateTransitionEnd(transitionDuration);
  21114. } else {
  21115. $(activeElement).removeClass(ClassName$2.ACTIVE);
  21116. $(nextElement).addClass(ClassName$2.ACTIVE);
  21117. this._isSliding = false;
  21118. $(this._element).trigger(slidEvent);
  21119. }
  21120. if (isCycling) {
  21121. this.cycle();
  21122. }
  21123. } // Static
  21124. ;
  21125. Carousel._jQueryInterface = function _jQueryInterface(config) {
  21126. return this.each(function () {
  21127. var data = $(this).data(DATA_KEY$2);
  21128. var _config = _objectSpread2({}, Default, {}, $(this).data());
  21129. if (typeof config === 'object') {
  21130. _config = _objectSpread2({}, _config, {}, config);
  21131. }
  21132. var action = typeof config === 'string' ? config : _config.slide;
  21133. if (!data) {
  21134. data = new Carousel(this, _config);
  21135. $(this).data(DATA_KEY$2, data);
  21136. }
  21137. if (typeof config === 'number') {
  21138. data.to(config);
  21139. } else if (typeof action === 'string') {
  21140. if (typeof data[action] === 'undefined') {
  21141. throw new TypeError("No method named \"" + action + "\"");
  21142. }
  21143. data[action]();
  21144. } else if (_config.interval && _config.ride) {
  21145. data.pause();
  21146. data.cycle();
  21147. }
  21148. });
  21149. };
  21150. Carousel._dataApiClickHandler = function _dataApiClickHandler(event) {
  21151. var selector = Util.getSelectorFromElement(this);
  21152. if (!selector) {
  21153. return;
  21154. }
  21155. var target = $(selector)[0];
  21156. if (!target || !$(target).hasClass(ClassName$2.CAROUSEL)) {
  21157. return;
  21158. }
  21159. var config = _objectSpread2({}, $(target).data(), {}, $(this).data());
  21160. var slideIndex = this.getAttribute('data-slide-to');
  21161. if (slideIndex) {
  21162. config.interval = false;
  21163. }
  21164. Carousel._jQueryInterface.call($(target), config);
  21165. if (slideIndex) {
  21166. $(target).data(DATA_KEY$2).to(slideIndex);
  21167. }
  21168. event.preventDefault();
  21169. };
  21170. _createClass(Carousel, null, [{
  21171. key: "VERSION",
  21172. get: function get() {
  21173. return VERSION$2;
  21174. }
  21175. }, {
  21176. key: "Default",
  21177. get: function get() {
  21178. return Default;
  21179. }
  21180. }]);
  21181. return Carousel;
  21182. }();
  21183. /**
  21184. * ------------------------------------------------------------------------
  21185. * Data Api implementation
  21186. * ------------------------------------------------------------------------
  21187. */
  21188. $(document).on(Event$2.CLICK_DATA_API, Selector$2.DATA_SLIDE, Carousel._dataApiClickHandler);
  21189. $(window).on(Event$2.LOAD_DATA_API, function () {
  21190. var carousels = [].slice.call(document.querySelectorAll(Selector$2.DATA_RIDE));
  21191. for (var i = 0, len = carousels.length; i < len; i++) {
  21192. var $carousel = $(carousels[i]);
  21193. Carousel._jQueryInterface.call($carousel, $carousel.data());
  21194. }
  21195. });
  21196. /**
  21197. * ------------------------------------------------------------------------
  21198. * jQuery
  21199. * ------------------------------------------------------------------------
  21200. */
  21201. $.fn[NAME$2] = Carousel._jQueryInterface;
  21202. $.fn[NAME$2].Constructor = Carousel;
  21203. $.fn[NAME$2].noConflict = function () {
  21204. $.fn[NAME$2] = JQUERY_NO_CONFLICT$2;
  21205. return Carousel._jQueryInterface;
  21206. };
  21207. /**
  21208. * ------------------------------------------------------------------------
  21209. * Constants
  21210. * ------------------------------------------------------------------------
  21211. */
  21212. var NAME$3 = 'collapse';
  21213. var VERSION$3 = '4.4.1';
  21214. var DATA_KEY$3 = 'bs.collapse';
  21215. var EVENT_KEY$3 = "." + DATA_KEY$3;
  21216. var DATA_API_KEY$3 = '.data-api';
  21217. var JQUERY_NO_CONFLICT$3 = $.fn[NAME$3];
  21218. var Default$1 = {
  21219. toggle: true,
  21220. parent: ''
  21221. };
  21222. var DefaultType$1 = {
  21223. toggle: 'boolean',
  21224. parent: '(string|element)'
  21225. };
  21226. var Event$3 = {
  21227. SHOW: "show" + EVENT_KEY$3,
  21228. SHOWN: "shown" + EVENT_KEY$3,
  21229. HIDE: "hide" + EVENT_KEY$3,
  21230. HIDDEN: "hidden" + EVENT_KEY$3,
  21231. CLICK_DATA_API: "click" + EVENT_KEY$3 + DATA_API_KEY$3
  21232. };
  21233. var ClassName$3 = {
  21234. SHOW: 'show',
  21235. COLLAPSE: 'collapse',
  21236. COLLAPSING: 'collapsing',
  21237. COLLAPSED: 'collapsed'
  21238. };
  21239. var Dimension = {
  21240. WIDTH: 'width',
  21241. HEIGHT: 'height'
  21242. };
  21243. var Selector$3 = {
  21244. ACTIVES: '.show, .collapsing',
  21245. DATA_TOGGLE: '[data-toggle="collapse"]'
  21246. };
  21247. /**
  21248. * ------------------------------------------------------------------------
  21249. * Class Definition
  21250. * ------------------------------------------------------------------------
  21251. */
  21252. var Collapse =
  21253. /*#__PURE__*/
  21254. function () {
  21255. function Collapse(element, config) {
  21256. this._isTransitioning = false;
  21257. this._element = element;
  21258. this._config = this._getConfig(config);
  21259. this._triggerArray = [].slice.call(document.querySelectorAll("[data-toggle=\"collapse\"][href=\"#" + element.id + "\"]," + ("[data-toggle=\"collapse\"][data-target=\"#" + element.id + "\"]")));
  21260. var toggleList = [].slice.call(document.querySelectorAll(Selector$3.DATA_TOGGLE));
  21261. for (var i = 0, len = toggleList.length; i < len; i++) {
  21262. var elem = toggleList[i];
  21263. var selector = Util.getSelectorFromElement(elem);
  21264. var filterElement = [].slice.call(document.querySelectorAll(selector)).filter(function (foundElem) {
  21265. return foundElem === element;
  21266. });
  21267. if (selector !== null && filterElement.length > 0) {
  21268. this._selector = selector;
  21269. this._triggerArray.push(elem);
  21270. }
  21271. }
  21272. this._parent = this._config.parent ? this._getParent() : null;
  21273. if (!this._config.parent) {
  21274. this._addAriaAndCollapsedClass(this._element, this._triggerArray);
  21275. }
  21276. if (this._config.toggle) {
  21277. this.toggle();
  21278. }
  21279. } // Getters
  21280. var _proto = Collapse.prototype;
  21281. // Public
  21282. _proto.toggle = function toggle() {
  21283. if ($(this._element).hasClass(ClassName$3.SHOW)) {
  21284. this.hide();
  21285. } else {
  21286. this.show();
  21287. }
  21288. };
  21289. _proto.show = function show() {
  21290. var _this = this;
  21291. if (this._isTransitioning || $(this._element).hasClass(ClassName$3.SHOW)) {
  21292. return;
  21293. }
  21294. var actives;
  21295. var activesData;
  21296. if (this._parent) {
  21297. actives = [].slice.call(this._parent.querySelectorAll(Selector$3.ACTIVES)).filter(function (elem) {
  21298. if (typeof _this._config.parent === 'string') {
  21299. return elem.getAttribute('data-parent') === _this._config.parent;
  21300. }
  21301. return elem.classList.contains(ClassName$3.COLLAPSE);
  21302. });
  21303. if (actives.length === 0) {
  21304. actives = null;
  21305. }
  21306. }
  21307. if (actives) {
  21308. activesData = $(actives).not(this._selector).data(DATA_KEY$3);
  21309. if (activesData && activesData._isTransitioning) {
  21310. return;
  21311. }
  21312. }
  21313. var startEvent = $.Event(Event$3.SHOW);
  21314. $(this._element).trigger(startEvent);
  21315. if (startEvent.isDefaultPrevented()) {
  21316. return;
  21317. }
  21318. if (actives) {
  21319. Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide');
  21320. if (!activesData) {
  21321. $(actives).data(DATA_KEY$3, null);
  21322. }
  21323. }
  21324. var dimension = this._getDimension();
  21325. $(this._element).removeClass(ClassName$3.COLLAPSE).addClass(ClassName$3.COLLAPSING);
  21326. this._element.style[dimension] = 0;
  21327. if (this._triggerArray.length) {
  21328. $(this._triggerArray).removeClass(ClassName$3.COLLAPSED).attr('aria-expanded', true);
  21329. }
  21330. this.setTransitioning(true);
  21331. var complete = function complete() {
  21332. $(_this._element).removeClass(ClassName$3.COLLAPSING).addClass(ClassName$3.COLLAPSE).addClass(ClassName$3.SHOW);
  21333. _this._element.style[dimension] = '';
  21334. _this.setTransitioning(false);
  21335. $(_this._element).trigger(Event$3.SHOWN);
  21336. };
  21337. var capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);
  21338. var scrollSize = "scroll" + capitalizedDimension;
  21339. var transitionDuration = Util.getTransitionDurationFromElement(this._element);
  21340. $(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
  21341. this._element.style[dimension] = this._element[scrollSize] + "px";
  21342. };
  21343. _proto.hide = function hide() {
  21344. var _this2 = this;
  21345. if (this._isTransitioning || !$(this._element).hasClass(ClassName$3.SHOW)) {
  21346. return;
  21347. }
  21348. var startEvent = $.Event(Event$3.HIDE);
  21349. $(this._element).trigger(startEvent);
  21350. if (startEvent.isDefaultPrevented()) {
  21351. return;
  21352. }
  21353. var dimension = this._getDimension();
  21354. this._element.style[dimension] = this._element.getBoundingClientRect()[dimension] + "px";
  21355. Util.reflow(this._element);
  21356. $(this._element).addClass(ClassName$3.COLLAPSING).removeClass(ClassName$3.COLLAPSE).removeClass(ClassName$3.SHOW);
  21357. var triggerArrayLength = this._triggerArray.length;
  21358. if (triggerArrayLength > 0) {
  21359. for (var i = 0; i < triggerArrayLength; i++) {
  21360. var trigger = this._triggerArray[i];
  21361. var selector = Util.getSelectorFromElement(trigger);
  21362. if (selector !== null) {
  21363. var $elem = $([].slice.call(document.querySelectorAll(selector)));
  21364. if (!$elem.hasClass(ClassName$3.SHOW)) {
  21365. $(trigger).addClass(ClassName$3.COLLAPSED).attr('aria-expanded', false);
  21366. }
  21367. }
  21368. }
  21369. }
  21370. this.setTransitioning(true);
  21371. var complete = function complete() {
  21372. _this2.setTransitioning(false);
  21373. $(_this2._element).removeClass(ClassName$3.COLLAPSING).addClass(ClassName$3.COLLAPSE).trigger(Event$3.HIDDEN);
  21374. };
  21375. this._element.style[dimension] = '';
  21376. var transitionDuration = Util.getTransitionDurationFromElement(this._element);
  21377. $(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
  21378. };
  21379. _proto.setTransitioning = function setTransitioning(isTransitioning) {
  21380. this._isTransitioning = isTransitioning;
  21381. };
  21382. _proto.dispose = function dispose() {
  21383. $.removeData(this._element, DATA_KEY$3);
  21384. this._config = null;
  21385. this._parent = null;
  21386. this._element = null;
  21387. this._triggerArray = null;
  21388. this._isTransitioning = null;
  21389. } // Private
  21390. ;
  21391. _proto._getConfig = function _getConfig(config) {
  21392. config = _objectSpread2({}, Default$1, {}, config);
  21393. config.toggle = Boolean(config.toggle); // Coerce string values
  21394. Util.typeCheckConfig(NAME$3, config, DefaultType$1);
  21395. return config;
  21396. };
  21397. _proto._getDimension = function _getDimension() {
  21398. var hasWidth = $(this._element).hasClass(Dimension.WIDTH);
  21399. return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT;
  21400. };
  21401. _proto._getParent = function _getParent() {
  21402. var _this3 = this;
  21403. var parent;
  21404. if (Util.isElement(this._config.parent)) {
  21405. parent = this._config.parent; // It's a jQuery object
  21406. if (typeof this._config.parent.jquery !== 'undefined') {
  21407. parent = this._config.parent[0];
  21408. }
  21409. } else {
  21410. parent = document.querySelector(this._config.parent);
  21411. }
  21412. var selector = "[data-toggle=\"collapse\"][data-parent=\"" + this._config.parent + "\"]";
  21413. var children = [].slice.call(parent.querySelectorAll(selector));
  21414. $(children).each(function (i, element) {
  21415. _this3._addAriaAndCollapsedClass(Collapse._getTargetFromElement(element), [element]);
  21416. });
  21417. return parent;
  21418. };
  21419. _proto._addAriaAndCollapsedClass = function _addAriaAndCollapsedClass(element, triggerArray) {
  21420. var isOpen = $(element).hasClass(ClassName$3.SHOW);
  21421. if (triggerArray.length) {
  21422. $(triggerArray).toggleClass(ClassName$3.COLLAPSED, !isOpen).attr('aria-expanded', isOpen);
  21423. }
  21424. } // Static
  21425. ;
  21426. Collapse._getTargetFromElement = function _getTargetFromElement(element) {
  21427. var selector = Util.getSelectorFromElement(element);
  21428. return selector ? document.querySelector(selector) : null;
  21429. };
  21430. Collapse._jQueryInterface = function _jQueryInterface(config) {
  21431. return this.each(function () {
  21432. var $this = $(this);
  21433. var data = $this.data(DATA_KEY$3);
  21434. var _config = _objectSpread2({}, Default$1, {}, $this.data(), {}, typeof config === 'object' && config ? config : {});
  21435. if (!data && _config.toggle && /show|hide/.test(config)) {
  21436. _config.toggle = false;
  21437. }
  21438. if (!data) {
  21439. data = new Collapse(this, _config);
  21440. $this.data(DATA_KEY$3, data);
  21441. }
  21442. if (typeof config === 'string') {
  21443. if (typeof data[config] === 'undefined') {
  21444. throw new TypeError("No method named \"" + config + "\"");
  21445. }
  21446. data[config]();
  21447. }
  21448. });
  21449. };
  21450. _createClass(Collapse, null, [{
  21451. key: "VERSION",
  21452. get: function get() {
  21453. return VERSION$3;
  21454. }
  21455. }, {
  21456. key: "Default",
  21457. get: function get() {
  21458. return Default$1;
  21459. }
  21460. }]);
  21461. return Collapse;
  21462. }();
  21463. /**
  21464. * ------------------------------------------------------------------------
  21465. * Data Api implementation
  21466. * ------------------------------------------------------------------------
  21467. */
  21468. $(document).on(Event$3.CLICK_DATA_API, Selector$3.DATA_TOGGLE, function (event) {
  21469. // preventDefault only for <a> elements (which change the URL) not inside the collapsible element
  21470. if (event.currentTarget.tagName === 'A') {
  21471. event.preventDefault();
  21472. }
  21473. var $trigger = $(this);
  21474. var selector = Util.getSelectorFromElement(this);
  21475. var selectors = [].slice.call(document.querySelectorAll(selector));
  21476. $(selectors).each(function () {
  21477. var $target = $(this);
  21478. var data = $target.data(DATA_KEY$3);
  21479. var config = data ? 'toggle' : $trigger.data();
  21480. Collapse._jQueryInterface.call($target, config);
  21481. });
  21482. });
  21483. /**
  21484. * ------------------------------------------------------------------------
  21485. * jQuery
  21486. * ------------------------------------------------------------------------
  21487. */
  21488. $.fn[NAME$3] = Collapse._jQueryInterface;
  21489. $.fn[NAME$3].Constructor = Collapse;
  21490. $.fn[NAME$3].noConflict = function () {
  21491. $.fn[NAME$3] = JQUERY_NO_CONFLICT$3;
  21492. return Collapse._jQueryInterface;
  21493. };
  21494. /**
  21495. * ------------------------------------------------------------------------
  21496. * Constants
  21497. * ------------------------------------------------------------------------
  21498. */
  21499. var NAME$4 = 'dropdown';
  21500. var VERSION$4 = '4.4.1';
  21501. var DATA_KEY$4 = 'bs.dropdown';
  21502. var EVENT_KEY$4 = "." + DATA_KEY$4;
  21503. var DATA_API_KEY$4 = '.data-api';
  21504. var JQUERY_NO_CONFLICT$4 = $.fn[NAME$4];
  21505. var ESCAPE_KEYCODE = 27; // KeyboardEvent.which value for Escape (Esc) key
  21506. var SPACE_KEYCODE = 32; // KeyboardEvent.which value for space key
  21507. var TAB_KEYCODE = 9; // KeyboardEvent.which value for tab key
  21508. var ARROW_UP_KEYCODE = 38; // KeyboardEvent.which value for up arrow key
  21509. var ARROW_DOWN_KEYCODE = 40; // KeyboardEvent.which value for down arrow key
  21510. var RIGHT_MOUSE_BUTTON_WHICH = 3; // MouseEvent.which value for the right button (assuming a right-handed mouse)
  21511. var REGEXP_KEYDOWN = new RegExp(ARROW_UP_KEYCODE + "|" + ARROW_DOWN_KEYCODE + "|" + ESCAPE_KEYCODE);
  21512. var Event$4 = {
  21513. HIDE: "hide" + EVENT_KEY$4,
  21514. HIDDEN: "hidden" + EVENT_KEY$4,
  21515. SHOW: "show" + EVENT_KEY$4,
  21516. SHOWN: "shown" + EVENT_KEY$4,
  21517. CLICK: "click" + EVENT_KEY$4,
  21518. CLICK_DATA_API: "click" + EVENT_KEY$4 + DATA_API_KEY$4,
  21519. KEYDOWN_DATA_API: "keydown" + EVENT_KEY$4 + DATA_API_KEY$4,
  21520. KEYUP_DATA_API: "keyup" + EVENT_KEY$4 + DATA_API_KEY$4
  21521. };
  21522. var ClassName$4 = {
  21523. DISABLED: 'disabled',
  21524. SHOW: 'show',
  21525. DROPUP: 'dropup',
  21526. DROPRIGHT: 'dropright',
  21527. DROPLEFT: 'dropleft',
  21528. MENURIGHT: 'dropdown-menu-right',
  21529. MENULEFT: 'dropdown-menu-left',
  21530. POSITION_STATIC: 'position-static'
  21531. };
  21532. var Selector$4 = {
  21533. DATA_TOGGLE: '[data-toggle="dropdown"]',
  21534. FORM_CHILD: '.dropdown form',
  21535. MENU: '.dropdown-menu',
  21536. NAVBAR_NAV: '.navbar-nav',
  21537. VISIBLE_ITEMS: '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'
  21538. };
  21539. var AttachmentMap = {
  21540. TOP: 'top-start',
  21541. TOPEND: 'top-end',
  21542. BOTTOM: 'bottom-start',
  21543. BOTTOMEND: 'bottom-end',
  21544. RIGHT: 'right-start',
  21545. RIGHTEND: 'right-end',
  21546. LEFT: 'left-start',
  21547. LEFTEND: 'left-end'
  21548. };
  21549. var Default$2 = {
  21550. offset: 0,
  21551. flip: true,
  21552. boundary: 'scrollParent',
  21553. reference: 'toggle',
  21554. display: 'dynamic',
  21555. popperConfig: null
  21556. };
  21557. var DefaultType$2 = {
  21558. offset: '(number|string|function)',
  21559. flip: 'boolean',
  21560. boundary: '(string|element)',
  21561. reference: '(string|element)',
  21562. display: 'string',
  21563. popperConfig: '(null|object)'
  21564. };
  21565. /**
  21566. * ------------------------------------------------------------------------
  21567. * Class Definition
  21568. * ------------------------------------------------------------------------
  21569. */
  21570. var Dropdown =
  21571. /*#__PURE__*/
  21572. function () {
  21573. function Dropdown(element, config) {
  21574. this._element = element;
  21575. this._popper = null;
  21576. this._config = this._getConfig(config);
  21577. this._menu = this._getMenuElement();
  21578. this._inNavbar = this._detectNavbar();
  21579. this._addEventListeners();
  21580. } // Getters
  21581. var _proto = Dropdown.prototype;
  21582. // Public
  21583. _proto.toggle = function toggle() {
  21584. if (this._element.disabled || $(this._element).hasClass(ClassName$4.DISABLED)) {
  21585. return;
  21586. }
  21587. var isActive = $(this._menu).hasClass(ClassName$4.SHOW);
  21588. Dropdown._clearMenus();
  21589. if (isActive) {
  21590. return;
  21591. }
  21592. this.show(true);
  21593. };
  21594. _proto.show = function show(usePopper) {
  21595. if (usePopper === void 0) {
  21596. usePopper = false;
  21597. }
  21598. if (this._element.disabled || $(this._element).hasClass(ClassName$4.DISABLED) || $(this._menu).hasClass(ClassName$4.SHOW)) {
  21599. return;
  21600. }
  21601. var relatedTarget = {
  21602. relatedTarget: this._element
  21603. };
  21604. var showEvent = $.Event(Event$4.SHOW, relatedTarget);
  21605. var parent = Dropdown._getParentFromElement(this._element);
  21606. $(parent).trigger(showEvent);
  21607. if (showEvent.isDefaultPrevented()) {
  21608. return;
  21609. } // Disable totally Popper.js for Dropdown in Navbar
  21610. if (!this._inNavbar && usePopper) {
  21611. /**
  21612. * Check for Popper dependency
  21613. * Popper - https://popper.js.org
  21614. */
  21615. if (typeof Popper === 'undefined') {
  21616. throw new TypeError('Bootstrap\'s dropdowns require Popper.js (https://popper.js.org/)');
  21617. }
  21618. var referenceElement = this._element;
  21619. if (this._config.reference === 'parent') {
  21620. referenceElement = parent;
  21621. } else if (Util.isElement(this._config.reference)) {
  21622. referenceElement = this._config.reference; // Check if it's jQuery element
  21623. if (typeof this._config.reference.jquery !== 'undefined') {
  21624. referenceElement = this._config.reference[0];
  21625. }
  21626. } // If boundary is not `scrollParent`, then set position to `static`
  21627. // to allow the menu to "escape" the scroll parent's boundaries
  21628. // https://github.com/twbs/bootstrap/issues/24251
  21629. if (this._config.boundary !== 'scrollParent') {
  21630. $(parent).addClass(ClassName$4.POSITION_STATIC);
  21631. }
  21632. this._popper = new Popper(referenceElement, this._menu, this._getPopperConfig());
  21633. } // If this is a touch-enabled device we add extra
  21634. // empty mouseover listeners to the body's immediate children;
  21635. // only needed because of broken event delegation on iOS
  21636. // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
  21637. if ('ontouchstart' in document.documentElement && $(parent).closest(Selector$4.NAVBAR_NAV).length === 0) {
  21638. $(document.body).children().on('mouseover', null, $.noop);
  21639. }
  21640. this._element.focus();
  21641. this._element.setAttribute('aria-expanded', true);
  21642. $(this._menu).toggleClass(ClassName$4.SHOW);
  21643. $(parent).toggleClass(ClassName$4.SHOW).trigger($.Event(Event$4.SHOWN, relatedTarget));
  21644. };
  21645. _proto.hide = function hide() {
  21646. if (this._element.disabled || $(this._element).hasClass(ClassName$4.DISABLED) || !$(this._menu).hasClass(ClassName$4.SHOW)) {
  21647. return;
  21648. }
  21649. var relatedTarget = {
  21650. relatedTarget: this._element
  21651. };
  21652. var hideEvent = $.Event(Event$4.HIDE, relatedTarget);
  21653. var parent = Dropdown._getParentFromElement(this._element);
  21654. $(parent).trigger(hideEvent);
  21655. if (hideEvent.isDefaultPrevented()) {
  21656. return;
  21657. }
  21658. if (this._popper) {
  21659. this._popper.destroy();
  21660. }
  21661. $(this._menu).toggleClass(ClassName$4.SHOW);
  21662. $(parent).toggleClass(ClassName$4.SHOW).trigger($.Event(Event$4.HIDDEN, relatedTarget));
  21663. };
  21664. _proto.dispose = function dispose() {
  21665. $.removeData(this._element, DATA_KEY$4);
  21666. $(this._element).off(EVENT_KEY$4);
  21667. this._element = null;
  21668. this._menu = null;
  21669. if (this._popper !== null) {
  21670. this._popper.destroy();
  21671. this._popper = null;
  21672. }
  21673. };
  21674. _proto.update = function update() {
  21675. this._inNavbar = this._detectNavbar();
  21676. if (this._popper !== null) {
  21677. this._popper.scheduleUpdate();
  21678. }
  21679. } // Private
  21680. ;
  21681. _proto._addEventListeners = function _addEventListeners() {
  21682. var _this = this;
  21683. $(this._element).on(Event$4.CLICK, function (event) {
  21684. event.preventDefault();
  21685. event.stopPropagation();
  21686. _this.toggle();
  21687. });
  21688. };
  21689. _proto._getConfig = function _getConfig(config) {
  21690. config = _objectSpread2({}, this.constructor.Default, {}, $(this._element).data(), {}, config);
  21691. Util.typeCheckConfig(NAME$4, config, this.constructor.DefaultType);
  21692. return config;
  21693. };
  21694. _proto._getMenuElement = function _getMenuElement() {
  21695. if (!this._menu) {
  21696. var parent = Dropdown._getParentFromElement(this._element);
  21697. if (parent) {
  21698. this._menu = parent.querySelector(Selector$4.MENU);
  21699. }
  21700. }
  21701. return this._menu;
  21702. };
  21703. _proto._getPlacement = function _getPlacement() {
  21704. var $parentDropdown = $(this._element.parentNode);
  21705. var placement = AttachmentMap.BOTTOM; // Handle dropup
  21706. if ($parentDropdown.hasClass(ClassName$4.DROPUP)) {
  21707. placement = AttachmentMap.TOP;
  21708. if ($(this._menu).hasClass(ClassName$4.MENURIGHT)) {
  21709. placement = AttachmentMap.TOPEND;
  21710. }
  21711. } else if ($parentDropdown.hasClass(ClassName$4.DROPRIGHT)) {
  21712. placement = AttachmentMap.RIGHT;
  21713. } else if ($parentDropdown.hasClass(ClassName$4.DROPLEFT)) {
  21714. placement = AttachmentMap.LEFT;
  21715. } else if ($(this._menu).hasClass(ClassName$4.MENURIGHT)) {
  21716. placement = AttachmentMap.BOTTOMEND;
  21717. }
  21718. return placement;
  21719. };
  21720. _proto._detectNavbar = function _detectNavbar() {
  21721. return $(this._element).closest('.navbar').length > 0;
  21722. };
  21723. _proto._getOffset = function _getOffset() {
  21724. var _this2 = this;
  21725. var offset = {};
  21726. if (typeof this._config.offset === 'function') {
  21727. offset.fn = function (data) {
  21728. data.offsets = _objectSpread2({}, data.offsets, {}, _this2._config.offset(data.offsets, _this2._element) || {});
  21729. return data;
  21730. };
  21731. } else {
  21732. offset.offset = this._config.offset;
  21733. }
  21734. return offset;
  21735. };
  21736. _proto._getPopperConfig = function _getPopperConfig() {
  21737. var popperConfig = {
  21738. placement: this._getPlacement(),
  21739. modifiers: {
  21740. offset: this._getOffset(),
  21741. flip: {
  21742. enabled: this._config.flip
  21743. },
  21744. preventOverflow: {
  21745. boundariesElement: this._config.boundary
  21746. }
  21747. }
  21748. }; // Disable Popper.js if we have a static display
  21749. if (this._config.display === 'static') {
  21750. popperConfig.modifiers.applyStyle = {
  21751. enabled: false
  21752. };
  21753. }
  21754. return _objectSpread2({}, popperConfig, {}, this._config.popperConfig);
  21755. } // Static
  21756. ;
  21757. Dropdown._jQueryInterface = function _jQueryInterface(config) {
  21758. return this.each(function () {
  21759. var data = $(this).data(DATA_KEY$4);
  21760. var _config = typeof config === 'object' ? config : null;
  21761. if (!data) {
  21762. data = new Dropdown(this, _config);
  21763. $(this).data(DATA_KEY$4, data);
  21764. }
  21765. if (typeof config === 'string') {
  21766. if (typeof data[config] === 'undefined') {
  21767. throw new TypeError("No method named \"" + config + "\"");
  21768. }
  21769. data[config]();
  21770. }
  21771. });
  21772. };
  21773. Dropdown._clearMenus = function _clearMenus(event) {
  21774. if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH || event.type === 'keyup' && event.which !== TAB_KEYCODE)) {
  21775. return;
  21776. }
  21777. var toggles = [].slice.call(document.querySelectorAll(Selector$4.DATA_TOGGLE));
  21778. for (var i = 0, len = toggles.length; i < len; i++) {
  21779. var parent = Dropdown._getParentFromElement(toggles[i]);
  21780. var context = $(toggles[i]).data(DATA_KEY$4);
  21781. var relatedTarget = {
  21782. relatedTarget: toggles[i]
  21783. };
  21784. if (event && event.type === 'click') {
  21785. relatedTarget.clickEvent = event;
  21786. }
  21787. if (!context) {
  21788. continue;
  21789. }
  21790. var dropdownMenu = context._menu;
  21791. if (!$(parent).hasClass(ClassName$4.SHOW)) {
  21792. continue;
  21793. }
  21794. if (event && (event.type === 'click' && /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) && $.contains(parent, event.target)) {
  21795. continue;
  21796. }
  21797. var hideEvent = $.Event(Event$4.HIDE, relatedTarget);
  21798. $(parent).trigger(hideEvent);
  21799. if (hideEvent.isDefaultPrevented()) {
  21800. continue;
  21801. } // If this is a touch-enabled device we remove the extra
  21802. // empty mouseover listeners we added for iOS support
  21803. if ('ontouchstart' in document.documentElement) {
  21804. $(document.body).children().off('mouseover', null, $.noop);
  21805. }
  21806. toggles[i].setAttribute('aria-expanded', 'false');
  21807. if (context._popper) {
  21808. context._popper.destroy();
  21809. }
  21810. $(dropdownMenu).removeClass(ClassName$4.SHOW);
  21811. $(parent).removeClass(ClassName$4.SHOW).trigger($.Event(Event$4.HIDDEN, relatedTarget));
  21812. }
  21813. };
  21814. Dropdown._getParentFromElement = function _getParentFromElement(element) {
  21815. var parent;
  21816. var selector = Util.getSelectorFromElement(element);
  21817. if (selector) {
  21818. parent = document.querySelector(selector);
  21819. }
  21820. return parent || element.parentNode;
  21821. } // eslint-disable-next-line complexity
  21822. ;
  21823. Dropdown._dataApiKeydownHandler = function _dataApiKeydownHandler(event) {
  21824. // If not input/textarea:
  21825. // - And not a key in REGEXP_KEYDOWN => not a dropdown command
  21826. // If input/textarea:
  21827. // - If space key => not a dropdown command
  21828. // - If key is other than escape
  21829. // - If key is not up or down => not a dropdown command
  21830. // - If trigger inside the menu => not a dropdown command
  21831. if (/input|textarea/i.test(event.target.tagName) ? event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE && (event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE || $(event.target).closest(Selector$4.MENU).length) : !REGEXP_KEYDOWN.test(event.which)) {
  21832. return;
  21833. }
  21834. event.preventDefault();
  21835. event.stopPropagation();
  21836. if (this.disabled || $(this).hasClass(ClassName$4.DISABLED)) {
  21837. return;
  21838. }
  21839. var parent = Dropdown._getParentFromElement(this);
  21840. var isActive = $(parent).hasClass(ClassName$4.SHOW);
  21841. if (!isActive && event.which === ESCAPE_KEYCODE) {
  21842. return;
  21843. }
  21844. if (!isActive || isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) {
  21845. if (event.which === ESCAPE_KEYCODE) {
  21846. var toggle = parent.querySelector(Selector$4.DATA_TOGGLE);
  21847. $(toggle).trigger('focus');
  21848. }
  21849. $(this).trigger('click');
  21850. return;
  21851. }
  21852. var items = [].slice.call(parent.querySelectorAll(Selector$4.VISIBLE_ITEMS)).filter(function (item) {
  21853. return $(item).is(':visible');
  21854. });
  21855. if (items.length === 0) {
  21856. return;
  21857. }
  21858. var index = items.indexOf(event.target);
  21859. if (event.which === ARROW_UP_KEYCODE && index > 0) {
  21860. // Up
  21861. index--;
  21862. }
  21863. if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) {
  21864. // Down
  21865. index++;
  21866. }
  21867. if (index < 0) {
  21868. index = 0;
  21869. }
  21870. items[index].focus();
  21871. };
  21872. _createClass(Dropdown, null, [{
  21873. key: "VERSION",
  21874. get: function get() {
  21875. return VERSION$4;
  21876. }
  21877. }, {
  21878. key: "Default",
  21879. get: function get() {
  21880. return Default$2;
  21881. }
  21882. }, {
  21883. key: "DefaultType",
  21884. get: function get() {
  21885. return DefaultType$2;
  21886. }
  21887. }]);
  21888. return Dropdown;
  21889. }();
  21890. /**
  21891. * ------------------------------------------------------------------------
  21892. * Data Api implementation
  21893. * ------------------------------------------------------------------------
  21894. */
  21895. $(document).on(Event$4.KEYDOWN_DATA_API, Selector$4.DATA_TOGGLE, Dropdown._dataApiKeydownHandler).on(Event$4.KEYDOWN_DATA_API, Selector$4.MENU, Dropdown._dataApiKeydownHandler).on(Event$4.CLICK_DATA_API + " " + Event$4.KEYUP_DATA_API, Dropdown._clearMenus).on(Event$4.CLICK_DATA_API, Selector$4.DATA_TOGGLE, function (event) {
  21896. event.preventDefault();
  21897. event.stopPropagation();
  21898. Dropdown._jQueryInterface.call($(this), 'toggle');
  21899. }).on(Event$4.CLICK_DATA_API, Selector$4.FORM_CHILD, function (e) {
  21900. e.stopPropagation();
  21901. });
  21902. /**
  21903. * ------------------------------------------------------------------------
  21904. * jQuery
  21905. * ------------------------------------------------------------------------
  21906. */
  21907. $.fn[NAME$4] = Dropdown._jQueryInterface;
  21908. $.fn[NAME$4].Constructor = Dropdown;
  21909. $.fn[NAME$4].noConflict = function () {
  21910. $.fn[NAME$4] = JQUERY_NO_CONFLICT$4;
  21911. return Dropdown._jQueryInterface;
  21912. };
  21913. /**
  21914. * ------------------------------------------------------------------------
  21915. * Constants
  21916. * ------------------------------------------------------------------------
  21917. */
  21918. var NAME$5 = 'modal';
  21919. var VERSION$5 = '4.4.1';
  21920. var DATA_KEY$5 = 'bs.modal';
  21921. var EVENT_KEY$5 = "." + DATA_KEY$5;
  21922. var DATA_API_KEY$5 = '.data-api';
  21923. var JQUERY_NO_CONFLICT$5 = $.fn[NAME$5];
  21924. var ESCAPE_KEYCODE$1 = 27; // KeyboardEvent.which value for Escape (Esc) key
  21925. var Default$3 = {
  21926. backdrop: true,
  21927. keyboard: true,
  21928. focus: true,
  21929. show: true
  21930. };
  21931. var DefaultType$3 = {
  21932. backdrop: '(boolean|string)',
  21933. keyboard: 'boolean',
  21934. focus: 'boolean',
  21935. show: 'boolean'
  21936. };
  21937. var Event$5 = {
  21938. HIDE: "hide" + EVENT_KEY$5,
  21939. HIDE_PREVENTED: "hidePrevented" + EVENT_KEY$5,
  21940. HIDDEN: "hidden" + EVENT_KEY$5,
  21941. SHOW: "show" + EVENT_KEY$5,
  21942. SHOWN: "shown" + EVENT_KEY$5,
  21943. FOCUSIN: "focusin" + EVENT_KEY$5,
  21944. RESIZE: "resize" + EVENT_KEY$5,
  21945. CLICK_DISMISS: "click.dismiss" + EVENT_KEY$5,
  21946. KEYDOWN_DISMISS: "keydown.dismiss" + EVENT_KEY$5,
  21947. MOUSEUP_DISMISS: "mouseup.dismiss" + EVENT_KEY$5,
  21948. MOUSEDOWN_DISMISS: "mousedown.dismiss" + EVENT_KEY$5,
  21949. CLICK_DATA_API: "click" + EVENT_KEY$5 + DATA_API_KEY$5
  21950. };
  21951. var ClassName$5 = {
  21952. SCROLLABLE: 'modal-dialog-scrollable',
  21953. SCROLLBAR_MEASURER: 'modal-scrollbar-measure',
  21954. BACKDROP: 'modal-backdrop',
  21955. OPEN: 'modal-open',
  21956. FADE: 'fade',
  21957. SHOW: 'show',
  21958. STATIC: 'modal-static'
  21959. };
  21960. var Selector$5 = {
  21961. DIALOG: '.modal-dialog',
  21962. MODAL_BODY: '.modal-body',
  21963. DATA_TOGGLE: '[data-toggle="modal"]',
  21964. DATA_DISMISS: '[data-dismiss="modal"]',
  21965. FIXED_CONTENT: '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top',
  21966. STICKY_CONTENT: '.sticky-top'
  21967. };
  21968. /**
  21969. * ------------------------------------------------------------------------
  21970. * Class Definition
  21971. * ------------------------------------------------------------------------
  21972. */
  21973. var Modal =
  21974. /*#__PURE__*/
  21975. function () {
  21976. function Modal(element, config) {
  21977. this._config = this._getConfig(config);
  21978. this._element = element;
  21979. this._dialog = element.querySelector(Selector$5.DIALOG);
  21980. this._backdrop = null;
  21981. this._isShown = false;
  21982. this._isBodyOverflowing = false;
  21983. this._ignoreBackdropClick = false;
  21984. this._isTransitioning = false;
  21985. this._scrollbarWidth = 0;
  21986. } // Getters
  21987. var _proto = Modal.prototype;
  21988. // Public
  21989. _proto.toggle = function toggle(relatedTarget) {
  21990. return this._isShown ? this.hide() : this.show(relatedTarget);
  21991. };
  21992. _proto.show = function show(relatedTarget) {
  21993. var _this = this;
  21994. if (this._isShown || this._isTransitioning) {
  21995. return;
  21996. }
  21997. if ($(this._element).hasClass(ClassName$5.FADE)) {
  21998. this._isTransitioning = true;
  21999. }
  22000. var showEvent = $.Event(Event$5.SHOW, {
  22001. relatedTarget: relatedTarget
  22002. });
  22003. $(this._element).trigger(showEvent);
  22004. if (this._isShown || showEvent.isDefaultPrevented()) {
  22005. return;
  22006. }
  22007. this._isShown = true;
  22008. this._checkScrollbar();
  22009. this._setScrollbar();
  22010. this._adjustDialog();
  22011. this._setEscapeEvent();
  22012. this._setResizeEvent();
  22013. $(this._element).on(Event$5.CLICK_DISMISS, Selector$5.DATA_DISMISS, function (event) {
  22014. return _this.hide(event);
  22015. });
  22016. $(this._dialog).on(Event$5.MOUSEDOWN_DISMISS, function () {
  22017. $(_this._element).one(Event$5.MOUSEUP_DISMISS, function (event) {
  22018. if ($(event.target).is(_this._element)) {
  22019. _this._ignoreBackdropClick = true;
  22020. }
  22021. });
  22022. });
  22023. this._showBackdrop(function () {
  22024. return _this._showElement(relatedTarget);
  22025. });
  22026. };
  22027. _proto.hide = function hide(event) {
  22028. var _this2 = this;
  22029. if (event) {
  22030. event.preventDefault();
  22031. }
  22032. if (!this._isShown || this._isTransitioning) {
  22033. return;
  22034. }
  22035. var hideEvent = $.Event(Event$5.HIDE);
  22036. $(this._element).trigger(hideEvent);
  22037. if (!this._isShown || hideEvent.isDefaultPrevented()) {
  22038. return;
  22039. }
  22040. this._isShown = false;
  22041. var transition = $(this._element).hasClass(ClassName$5.FADE);
  22042. if (transition) {
  22043. this._isTransitioning = true;
  22044. }
  22045. this._setEscapeEvent();
  22046. this._setResizeEvent();
  22047. $(document).off(Event$5.FOCUSIN);
  22048. $(this._element).removeClass(ClassName$5.SHOW);
  22049. $(this._element).off(Event$5.CLICK_DISMISS);
  22050. $(this._dialog).off(Event$5.MOUSEDOWN_DISMISS);
  22051. if (transition) {
  22052. var transitionDuration = Util.getTransitionDurationFromElement(this._element);
  22053. $(this._element).one(Util.TRANSITION_END, function (event) {
  22054. return _this2._hideModal(event);
  22055. }).emulateTransitionEnd(transitionDuration);
  22056. } else {
  22057. this._hideModal();
  22058. }
  22059. };
  22060. _proto.dispose = function dispose() {
  22061. [window, this._element, this._dialog].forEach(function (htmlElement) {
  22062. return $(htmlElement).off(EVENT_KEY$5);
  22063. });
  22064. /**
  22065. * `document` has 2 events `Event.FOCUSIN` and `Event.CLICK_DATA_API`
  22066. * Do not move `document` in `htmlElements` array
  22067. * It will remove `Event.CLICK_DATA_API` event that should remain
  22068. */
  22069. $(document).off(Event$5.FOCUSIN);
  22070. $.removeData(this._element, DATA_KEY$5);
  22071. this._config = null;
  22072. this._element = null;
  22073. this._dialog = null;
  22074. this._backdrop = null;
  22075. this._isShown = null;
  22076. this._isBodyOverflowing = null;
  22077. this._ignoreBackdropClick = null;
  22078. this._isTransitioning = null;
  22079. this._scrollbarWidth = null;
  22080. };
  22081. _proto.handleUpdate = function handleUpdate() {
  22082. this._adjustDialog();
  22083. } // Private
  22084. ;
  22085. _proto._getConfig = function _getConfig(config) {
  22086. config = _objectSpread2({}, Default$3, {}, config);
  22087. Util.typeCheckConfig(NAME$5, config, DefaultType$3);
  22088. return config;
  22089. };
  22090. _proto._triggerBackdropTransition = function _triggerBackdropTransition() {
  22091. var _this3 = this;
  22092. if (this._config.backdrop === 'static') {
  22093. var hideEventPrevented = $.Event(Event$5.HIDE_PREVENTED);
  22094. $(this._element).trigger(hideEventPrevented);
  22095. if (hideEventPrevented.defaultPrevented) {
  22096. return;
  22097. }
  22098. this._element.classList.add(ClassName$5.STATIC);
  22099. var modalTransitionDuration = Util.getTransitionDurationFromElement(this._element);
  22100. $(this._element).one(Util.TRANSITION_END, function () {
  22101. _this3._element.classList.remove(ClassName$5.STATIC);
  22102. }).emulateTransitionEnd(modalTransitionDuration);
  22103. this._element.focus();
  22104. } else {
  22105. this.hide();
  22106. }
  22107. };
  22108. _proto._showElement = function _showElement(relatedTarget) {
  22109. var _this4 = this;
  22110. var transition = $(this._element).hasClass(ClassName$5.FADE);
  22111. var modalBody = this._dialog ? this._dialog.querySelector(Selector$5.MODAL_BODY) : null;
  22112. if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) {
  22113. // Don't move modal's DOM position
  22114. document.body.appendChild(this._element);
  22115. }
  22116. this._element.style.display = 'block';
  22117. this._element.removeAttribute('aria-hidden');
  22118. this._element.setAttribute('aria-modal', true);
  22119. if ($(this._dialog).hasClass(ClassName$5.SCROLLABLE) && modalBody) {
  22120. modalBody.scrollTop = 0;
  22121. } else {
  22122. this._element.scrollTop = 0;
  22123. }
  22124. if (transition) {
  22125. Util.reflow(this._element);
  22126. }
  22127. $(this._element).addClass(ClassName$5.SHOW);
  22128. if (this._config.focus) {
  22129. this._enforceFocus();
  22130. }
  22131. var shownEvent = $.Event(Event$5.SHOWN, {
  22132. relatedTarget: relatedTarget
  22133. });
  22134. var transitionComplete = function transitionComplete() {
  22135. if (_this4._config.focus) {
  22136. _this4._element.focus();
  22137. }
  22138. _this4._isTransitioning = false;
  22139. $(_this4._element).trigger(shownEvent);
  22140. };
  22141. if (transition) {
  22142. var transitionDuration = Util.getTransitionDurationFromElement(this._dialog);
  22143. $(this._dialog).one(Util.TRANSITION_END, transitionComplete).emulateTransitionEnd(transitionDuration);
  22144. } else {
  22145. transitionComplete();
  22146. }
  22147. };
  22148. _proto._enforceFocus = function _enforceFocus() {
  22149. var _this5 = this;
  22150. $(document).off(Event$5.FOCUSIN) // Guard against infinite focus loop
  22151. .on(Event$5.FOCUSIN, function (event) {
  22152. if (document !== event.target && _this5._element !== event.target && $(_this5._element).has(event.target).length === 0) {
  22153. _this5._element.focus();
  22154. }
  22155. });
  22156. };
  22157. _proto._setEscapeEvent = function _setEscapeEvent() {
  22158. var _this6 = this;
  22159. if (this._isShown && this._config.keyboard) {
  22160. $(this._element).on(Event$5.KEYDOWN_DISMISS, function (event) {
  22161. if (event.which === ESCAPE_KEYCODE$1) {
  22162. _this6._triggerBackdropTransition();
  22163. }
  22164. });
  22165. } else if (!this._isShown) {
  22166. $(this._element).off(Event$5.KEYDOWN_DISMISS);
  22167. }
  22168. };
  22169. _proto._setResizeEvent = function _setResizeEvent() {
  22170. var _this7 = this;
  22171. if (this._isShown) {
  22172. $(window).on(Event$5.RESIZE, function (event) {
  22173. return _this7.handleUpdate(event);
  22174. });
  22175. } else {
  22176. $(window).off(Event$5.RESIZE);
  22177. }
  22178. };
  22179. _proto._hideModal = function _hideModal() {
  22180. var _this8 = this;
  22181. this._element.style.display = 'none';
  22182. this._element.setAttribute('aria-hidden', true);
  22183. this._element.removeAttribute('aria-modal');
  22184. this._isTransitioning = false;
  22185. this._showBackdrop(function () {
  22186. $(document.body).removeClass(ClassName$5.OPEN);
  22187. _this8._resetAdjustments();
  22188. _this8._resetScrollbar();
  22189. $(_this8._element).trigger(Event$5.HIDDEN);
  22190. });
  22191. };
  22192. _proto._removeBackdrop = function _removeBackdrop() {
  22193. if (this._backdrop) {
  22194. $(this._backdrop).remove();
  22195. this._backdrop = null;
  22196. }
  22197. };
  22198. _proto._showBackdrop = function _showBackdrop(callback) {
  22199. var _this9 = this;
  22200. var animate = $(this._element).hasClass(ClassName$5.FADE) ? ClassName$5.FADE : '';
  22201. if (this._isShown && this._config.backdrop) {
  22202. this._backdrop = document.createElement('div');
  22203. this._backdrop.className = ClassName$5.BACKDROP;
  22204. if (animate) {
  22205. this._backdrop.classList.add(animate);
  22206. }
  22207. $(this._backdrop).appendTo(document.body);
  22208. $(this._element).on(Event$5.CLICK_DISMISS, function (event) {
  22209. if (_this9._ignoreBackdropClick) {
  22210. _this9._ignoreBackdropClick = false;
  22211. return;
  22212. }
  22213. if (event.target !== event.currentTarget) {
  22214. return;
  22215. }
  22216. _this9._triggerBackdropTransition();
  22217. });
  22218. if (animate) {
  22219. Util.reflow(this._backdrop);
  22220. }
  22221. $(this._backdrop).addClass(ClassName$5.SHOW);
  22222. if (!callback) {
  22223. return;
  22224. }
  22225. if (!animate) {
  22226. callback();
  22227. return;
  22228. }
  22229. var backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop);
  22230. $(this._backdrop).one(Util.TRANSITION_END, callback).emulateTransitionEnd(backdropTransitionDuration);
  22231. } else if (!this._isShown && this._backdrop) {
  22232. $(this._backdrop).removeClass(ClassName$5.SHOW);
  22233. var callbackRemove = function callbackRemove() {
  22234. _this9._removeBackdrop();
  22235. if (callback) {
  22236. callback();
  22237. }
  22238. };
  22239. if ($(this._element).hasClass(ClassName$5.FADE)) {
  22240. var _backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop);
  22241. $(this._backdrop).one(Util.TRANSITION_END, callbackRemove).emulateTransitionEnd(_backdropTransitionDuration);
  22242. } else {
  22243. callbackRemove();
  22244. }
  22245. } else if (callback) {
  22246. callback();
  22247. }
  22248. } // ----------------------------------------------------------------------
  22249. // the following methods are used to handle overflowing modals
  22250. // todo (fat): these should probably be refactored out of modal.js
  22251. // ----------------------------------------------------------------------
  22252. ;
  22253. _proto._adjustDialog = function _adjustDialog() {
  22254. var isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;
  22255. if (!this._isBodyOverflowing && isModalOverflowing) {
  22256. this._element.style.paddingLeft = this._scrollbarWidth + "px";
  22257. }
  22258. if (this._isBodyOverflowing && !isModalOverflowing) {
  22259. this._element.style.paddingRight = this._scrollbarWidth + "px";
  22260. }
  22261. };
  22262. _proto._resetAdjustments = function _resetAdjustments() {
  22263. this._element.style.paddingLeft = '';
  22264. this._element.style.paddingRight = '';
  22265. };
  22266. _proto._checkScrollbar = function _checkScrollbar() {
  22267. var rect = document.body.getBoundingClientRect();
  22268. this._isBodyOverflowing = rect.left + rect.right < window.innerWidth;
  22269. this._scrollbarWidth = this._getScrollbarWidth();
  22270. };
  22271. _proto._setScrollbar = function _setScrollbar() {
  22272. var _this10 = this;
  22273. if (this._isBodyOverflowing) {
  22274. // Note: DOMNode.style.paddingRight returns the actual value or '' if not set
  22275. // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set
  22276. var fixedContent = [].slice.call(document.querySelectorAll(Selector$5.FIXED_CONTENT));
  22277. var stickyContent = [].slice.call(document.querySelectorAll(Selector$5.STICKY_CONTENT)); // Adjust fixed content padding
  22278. $(fixedContent).each(function (index, element) {
  22279. var actualPadding = element.style.paddingRight;
  22280. var calculatedPadding = $(element).css('padding-right');
  22281. $(element).data('padding-right', actualPadding).css('padding-right', parseFloat(calculatedPadding) + _this10._scrollbarWidth + "px");
  22282. }); // Adjust sticky content margin
  22283. $(stickyContent).each(function (index, element) {
  22284. var actualMargin = element.style.marginRight;
  22285. var calculatedMargin = $(element).css('margin-right');
  22286. $(element).data('margin-right', actualMargin).css('margin-right', parseFloat(calculatedMargin) - _this10._scrollbarWidth + "px");
  22287. }); // Adjust body padding
  22288. var actualPadding = document.body.style.paddingRight;
  22289. var calculatedPadding = $(document.body).css('padding-right');
  22290. $(document.body).data('padding-right', actualPadding).css('padding-right', parseFloat(calculatedPadding) + this._scrollbarWidth + "px");
  22291. }
  22292. $(document.body).addClass(ClassName$5.OPEN);
  22293. };
  22294. _proto._resetScrollbar = function _resetScrollbar() {
  22295. // Restore fixed content padding
  22296. var fixedContent = [].slice.call(document.querySelectorAll(Selector$5.FIXED_CONTENT));
  22297. $(fixedContent).each(function (index, element) {
  22298. var padding = $(element).data('padding-right');
  22299. $(element).removeData('padding-right');
  22300. element.style.paddingRight = padding ? padding : '';
  22301. }); // Restore sticky content
  22302. var elements = [].slice.call(document.querySelectorAll("" + Selector$5.STICKY_CONTENT));
  22303. $(elements).each(function (index, element) {
  22304. var margin = $(element).data('margin-right');
  22305. if (typeof margin !== 'undefined') {
  22306. $(element).css('margin-right', margin).removeData('margin-right');
  22307. }
  22308. }); // Restore body padding
  22309. var padding = $(document.body).data('padding-right');
  22310. $(document.body).removeData('padding-right');
  22311. document.body.style.paddingRight = padding ? padding : '';
  22312. };
  22313. _proto._getScrollbarWidth = function _getScrollbarWidth() {
  22314. // thx d.walsh
  22315. var scrollDiv = document.createElement('div');
  22316. scrollDiv.className = ClassName$5.SCROLLBAR_MEASURER;
  22317. document.body.appendChild(scrollDiv);
  22318. var scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth;
  22319. document.body.removeChild(scrollDiv);
  22320. return scrollbarWidth;
  22321. } // Static
  22322. ;
  22323. Modal._jQueryInterface = function _jQueryInterface(config, relatedTarget) {
  22324. return this.each(function () {
  22325. var data = $(this).data(DATA_KEY$5);
  22326. var _config = _objectSpread2({}, Default$3, {}, $(this).data(), {}, typeof config === 'object' && config ? config : {});
  22327. if (!data) {
  22328. data = new Modal(this, _config);
  22329. $(this).data(DATA_KEY$5, data);
  22330. }
  22331. if (typeof config === 'string') {
  22332. if (typeof data[config] === 'undefined') {
  22333. throw new TypeError("No method named \"" + config + "\"");
  22334. }
  22335. data[config](relatedTarget);
  22336. } else if (_config.show) {
  22337. data.show(relatedTarget);
  22338. }
  22339. });
  22340. };
  22341. _createClass(Modal, null, [{
  22342. key: "VERSION",
  22343. get: function get() {
  22344. return VERSION$5;
  22345. }
  22346. }, {
  22347. key: "Default",
  22348. get: function get() {
  22349. return Default$3;
  22350. }
  22351. }]);
  22352. return Modal;
  22353. }();
  22354. /**
  22355. * ------------------------------------------------------------------------
  22356. * Data Api implementation
  22357. * ------------------------------------------------------------------------
  22358. */
  22359. $(document).on(Event$5.CLICK_DATA_API, Selector$5.DATA_TOGGLE, function (event) {
  22360. var _this11 = this;
  22361. var target;
  22362. var selector = Util.getSelectorFromElement(this);
  22363. if (selector) {
  22364. target = document.querySelector(selector);
  22365. }
  22366. var config = $(target).data(DATA_KEY$5) ? 'toggle' : _objectSpread2({}, $(target).data(), {}, $(this).data());
  22367. if (this.tagName === 'A' || this.tagName === 'AREA') {
  22368. event.preventDefault();
  22369. }
  22370. var $target = $(target).one(Event$5.SHOW, function (showEvent) {
  22371. if (showEvent.isDefaultPrevented()) {
  22372. // Only register focus restorer if modal will actually get shown
  22373. return;
  22374. }
  22375. $target.one(Event$5.HIDDEN, function () {
  22376. if ($(_this11).is(':visible')) {
  22377. _this11.focus();
  22378. }
  22379. });
  22380. });
  22381. Modal._jQueryInterface.call($(target), config, this);
  22382. });
  22383. /**
  22384. * ------------------------------------------------------------------------
  22385. * jQuery
  22386. * ------------------------------------------------------------------------
  22387. */
  22388. $.fn[NAME$5] = Modal._jQueryInterface;
  22389. $.fn[NAME$5].Constructor = Modal;
  22390. $.fn[NAME$5].noConflict = function () {
  22391. $.fn[NAME$5] = JQUERY_NO_CONFLICT$5;
  22392. return Modal._jQueryInterface;
  22393. };
  22394. /**
  22395. * --------------------------------------------------------------------------
  22396. * Bootstrap (v4.4.1): tools/sanitizer.js
  22397. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
  22398. * --------------------------------------------------------------------------
  22399. */
  22400. var uriAttrs = ['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href'];
  22401. var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i;
  22402. var DefaultWhitelist = {
  22403. // Global attributes allowed on any supplied element below.
  22404. '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
  22405. a: ['target', 'href', 'title', 'rel'],
  22406. area: [],
  22407. b: [],
  22408. br: [],
  22409. col: [],
  22410. code: [],
  22411. div: [],
  22412. em: [],
  22413. hr: [],
  22414. h1: [],
  22415. h2: [],
  22416. h3: [],
  22417. h4: [],
  22418. h5: [],
  22419. h6: [],
  22420. i: [],
  22421. img: ['src', 'alt', 'title', 'width', 'height'],
  22422. li: [],
  22423. ol: [],
  22424. p: [],
  22425. pre: [],
  22426. s: [],
  22427. small: [],
  22428. span: [],
  22429. sub: [],
  22430. sup: [],
  22431. strong: [],
  22432. u: [],
  22433. ul: []
  22434. };
  22435. /**
  22436. * A pattern that recognizes a commonly useful subset of URLs that are safe.
  22437. *
  22438. * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
  22439. */
  22440. var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi;
  22441. /**
  22442. * A pattern that matches safe data URLs. Only matches image, video and audio types.
  22443. *
  22444. * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
  22445. */
  22446. var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i;
  22447. function allowedAttribute(attr, allowedAttributeList) {
  22448. var attrName = attr.nodeName.toLowerCase();
  22449. if (allowedAttributeList.indexOf(attrName) !== -1) {
  22450. if (uriAttrs.indexOf(attrName) !== -1) {
  22451. return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN));
  22452. }
  22453. return true;
  22454. }
  22455. var regExp = allowedAttributeList.filter(function (attrRegex) {
  22456. return attrRegex instanceof RegExp;
  22457. }); // Check if a regular expression validates the attribute.
  22458. for (var i = 0, l = regExp.length; i < l; i++) {
  22459. if (attrName.match(regExp[i])) {
  22460. return true;
  22461. }
  22462. }
  22463. return false;
  22464. }
  22465. function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {
  22466. if (unsafeHtml.length === 0) {
  22467. return unsafeHtml;
  22468. }
  22469. if (sanitizeFn && typeof sanitizeFn === 'function') {
  22470. return sanitizeFn(unsafeHtml);
  22471. }
  22472. var domParser = new window.DOMParser();
  22473. var createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');
  22474. var whitelistKeys = Object.keys(whiteList);
  22475. var elements = [].slice.call(createdDocument.body.querySelectorAll('*'));
  22476. var _loop = function _loop(i, len) {
  22477. var el = elements[i];
  22478. var elName = el.nodeName.toLowerCase();
  22479. if (whitelistKeys.indexOf(el.nodeName.toLowerCase()) === -1) {
  22480. el.parentNode.removeChild(el);
  22481. return "continue";
  22482. }
  22483. var attributeList = [].slice.call(el.attributes);
  22484. var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || []);
  22485. attributeList.forEach(function (attr) {
  22486. if (!allowedAttribute(attr, whitelistedAttributes)) {
  22487. el.removeAttribute(attr.nodeName);
  22488. }
  22489. });
  22490. };
  22491. for (var i = 0, len = elements.length; i < len; i++) {
  22492. var _ret = _loop(i);
  22493. if (_ret === "continue") continue;
  22494. }
  22495. return createdDocument.body.innerHTML;
  22496. }
  22497. /**
  22498. * ------------------------------------------------------------------------
  22499. * Constants
  22500. * ------------------------------------------------------------------------
  22501. */
  22502. var NAME$6 = 'tooltip';
  22503. var VERSION$6 = '4.4.1';
  22504. var DATA_KEY$6 = 'bs.tooltip';
  22505. var EVENT_KEY$6 = "." + DATA_KEY$6;
  22506. var JQUERY_NO_CONFLICT$6 = $.fn[NAME$6];
  22507. var CLASS_PREFIX = 'bs-tooltip';
  22508. var BSCLS_PREFIX_REGEX = new RegExp("(^|\\s)" + CLASS_PREFIX + "\\S+", 'g');
  22509. var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn'];
  22510. var DefaultType$4 = {
  22511. animation: 'boolean',
  22512. template: 'string',
  22513. title: '(string|element|function)',
  22514. trigger: 'string',
  22515. delay: '(number|object)',
  22516. html: 'boolean',
  22517. selector: '(string|boolean)',
  22518. placement: '(string|function)',
  22519. offset: '(number|string|function)',
  22520. container: '(string|element|boolean)',
  22521. fallbackPlacement: '(string|array)',
  22522. boundary: '(string|element)',
  22523. sanitize: 'boolean',
  22524. sanitizeFn: '(null|function)',
  22525. whiteList: 'object',
  22526. popperConfig: '(null|object)'
  22527. };
  22528. var AttachmentMap$1 = {
  22529. AUTO: 'auto',
  22530. TOP: 'top',
  22531. RIGHT: 'right',
  22532. BOTTOM: 'bottom',
  22533. LEFT: 'left'
  22534. };
  22535. var Default$4 = {
  22536. animation: true,
  22537. template: '<div class="tooltip" role="tooltip">' + '<div class="arrow"></div>' + '<div class="tooltip-inner"></div></div>',
  22538. trigger: 'hover focus',
  22539. title: '',
  22540. delay: 0,
  22541. html: false,
  22542. selector: false,
  22543. placement: 'top',
  22544. offset: 0,
  22545. container: false,
  22546. fallbackPlacement: 'flip',
  22547. boundary: 'scrollParent',
  22548. sanitize: true,
  22549. sanitizeFn: null,
  22550. whiteList: DefaultWhitelist,
  22551. popperConfig: null
  22552. };
  22553. var HoverState = {
  22554. SHOW: 'show',
  22555. OUT: 'out'
  22556. };
  22557. var Event$6 = {
  22558. HIDE: "hide" + EVENT_KEY$6,
  22559. HIDDEN: "hidden" + EVENT_KEY$6,
  22560. SHOW: "show" + EVENT_KEY$6,
  22561. SHOWN: "shown" + EVENT_KEY$6,
  22562. INSERTED: "inserted" + EVENT_KEY$6,
  22563. CLICK: "click" + EVENT_KEY$6,
  22564. FOCUSIN: "focusin" + EVENT_KEY$6,
  22565. FOCUSOUT: "focusout" + EVENT_KEY$6,
  22566. MOUSEENTER: "mouseenter" + EVENT_KEY$6,
  22567. MOUSELEAVE: "mouseleave" + EVENT_KEY$6
  22568. };
  22569. var ClassName$6 = {
  22570. FADE: 'fade',
  22571. SHOW: 'show'
  22572. };
  22573. var Selector$6 = {
  22574. TOOLTIP: '.tooltip',
  22575. TOOLTIP_INNER: '.tooltip-inner',
  22576. ARROW: '.arrow'
  22577. };
  22578. var Trigger = {
  22579. HOVER: 'hover',
  22580. FOCUS: 'focus',
  22581. CLICK: 'click',
  22582. MANUAL: 'manual'
  22583. };
  22584. /**
  22585. * ------------------------------------------------------------------------
  22586. * Class Definition
  22587. * ------------------------------------------------------------------------
  22588. */
  22589. var Tooltip =
  22590. /*#__PURE__*/
  22591. function () {
  22592. function Tooltip(element, config) {
  22593. if (typeof Popper === 'undefined') {
  22594. throw new TypeError('Bootstrap\'s tooltips require Popper.js (https://popper.js.org/)');
  22595. } // private
  22596. this._isEnabled = true;
  22597. this._timeout = 0;
  22598. this._hoverState = '';
  22599. this._activeTrigger = {};
  22600. this._popper = null; // Protected
  22601. this.element = element;
  22602. this.config = this._getConfig(config);
  22603. this.tip = null;
  22604. this._setListeners();
  22605. } // Getters
  22606. var _proto = Tooltip.prototype;
  22607. // Public
  22608. _proto.enable = function enable() {
  22609. this._isEnabled = true;
  22610. };
  22611. _proto.disable = function disable() {
  22612. this._isEnabled = false;
  22613. };
  22614. _proto.toggleEnabled = function toggleEnabled() {
  22615. this._isEnabled = !this._isEnabled;
  22616. };
  22617. _proto.toggle = function toggle(event) {
  22618. if (!this._isEnabled) {
  22619. return;
  22620. }
  22621. if (event) {
  22622. var dataKey = this.constructor.DATA_KEY;
  22623. var context = $(event.currentTarget).data(dataKey);
  22624. if (!context) {
  22625. context = new this.constructor(event.currentTarget, this._getDelegateConfig());
  22626. $(event.currentTarget).data(dataKey, context);
  22627. }
  22628. context._activeTrigger.click = !context._activeTrigger.click;
  22629. if (context._isWithActiveTrigger()) {
  22630. context._enter(null, context);
  22631. } else {
  22632. context._leave(null, context);
  22633. }
  22634. } else {
  22635. if ($(this.getTipElement()).hasClass(ClassName$6.SHOW)) {
  22636. this._leave(null, this);
  22637. return;
  22638. }
  22639. this._enter(null, this);
  22640. }
  22641. };
  22642. _proto.dispose = function dispose() {
  22643. clearTimeout(this._timeout);
  22644. $.removeData(this.element, this.constructor.DATA_KEY);
  22645. $(this.element).off(this.constructor.EVENT_KEY);
  22646. $(this.element).closest('.modal').off('hide.bs.modal', this._hideModalHandler);
  22647. if (this.tip) {
  22648. $(this.tip).remove();
  22649. }
  22650. this._isEnabled = null;
  22651. this._timeout = null;
  22652. this._hoverState = null;
  22653. this._activeTrigger = null;
  22654. if (this._popper) {
  22655. this._popper.destroy();
  22656. }
  22657. this._popper = null;
  22658. this.element = null;
  22659. this.config = null;
  22660. this.tip = null;
  22661. };
  22662. _proto.show = function show() {
  22663. var _this = this;
  22664. if ($(this.element).css('display') === 'none') {
  22665. throw new Error('Please use show on visible elements');
  22666. }
  22667. var showEvent = $.Event(this.constructor.Event.SHOW);
  22668. if (this.isWithContent() && this._isEnabled) {
  22669. $(this.element).trigger(showEvent);
  22670. var shadowRoot = Util.findShadowRoot(this.element);
  22671. var isInTheDom = $.contains(shadowRoot !== null ? shadowRoot : this.element.ownerDocument.documentElement, this.element);
  22672. if (showEvent.isDefaultPrevented() || !isInTheDom) {
  22673. return;
  22674. }
  22675. var tip = this.getTipElement();
  22676. var tipId = Util.getUID(this.constructor.NAME);
  22677. tip.setAttribute('id', tipId);
  22678. this.element.setAttribute('aria-describedby', tipId);
  22679. this.setContent();
  22680. if (this.config.animation) {
  22681. $(tip).addClass(ClassName$6.FADE);
  22682. }
  22683. var placement = typeof this.config.placement === 'function' ? this.config.placement.call(this, tip, this.element) : this.config.placement;
  22684. var attachment = this._getAttachment(placement);
  22685. this.addAttachmentClass(attachment);
  22686. var container = this._getContainer();
  22687. $(tip).data(this.constructor.DATA_KEY, this);
  22688. if (!$.contains(this.element.ownerDocument.documentElement, this.tip)) {
  22689. $(tip).appendTo(container);
  22690. }
  22691. $(this.element).trigger(this.constructor.Event.INSERTED);
  22692. this._popper = new Popper(this.element, tip, this._getPopperConfig(attachment));
  22693. $(tip).addClass(ClassName$6.SHOW); // If this is a touch-enabled device we add extra
  22694. // empty mouseover listeners to the body's immediate children;
  22695. // only needed because of broken event delegation on iOS
  22696. // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
  22697. if ('ontouchstart' in document.documentElement) {
  22698. $(document.body).children().on('mouseover', null, $.noop);
  22699. }
  22700. var complete = function complete() {
  22701. if (_this.config.animation) {
  22702. _this._fixTransition();
  22703. }
  22704. var prevHoverState = _this._hoverState;
  22705. _this._hoverState = null;
  22706. $(_this.element).trigger(_this.constructor.Event.SHOWN);
  22707. if (prevHoverState === HoverState.OUT) {
  22708. _this._leave(null, _this);
  22709. }
  22710. };
  22711. if ($(this.tip).hasClass(ClassName$6.FADE)) {
  22712. var transitionDuration = Util.getTransitionDurationFromElement(this.tip);
  22713. $(this.tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
  22714. } else {
  22715. complete();
  22716. }
  22717. }
  22718. };
  22719. _proto.hide = function hide(callback) {
  22720. var _this2 = this;
  22721. var tip = this.getTipElement();
  22722. var hideEvent = $.Event(this.constructor.Event.HIDE);
  22723. var complete = function complete() {
  22724. if (_this2._hoverState !== HoverState.SHOW && tip.parentNode) {
  22725. tip.parentNode.removeChild(tip);
  22726. }
  22727. _this2._cleanTipClass();
  22728. _this2.element.removeAttribute('aria-describedby');
  22729. $(_this2.element).trigger(_this2.constructor.Event.HIDDEN);
  22730. if (_this2._popper !== null) {
  22731. _this2._popper.destroy();
  22732. }
  22733. if (callback) {
  22734. callback();
  22735. }
  22736. };
  22737. $(this.element).trigger(hideEvent);
  22738. if (hideEvent.isDefaultPrevented()) {
  22739. return;
  22740. }
  22741. $(tip).removeClass(ClassName$6.SHOW); // If this is a touch-enabled device we remove the extra
  22742. // empty mouseover listeners we added for iOS support
  22743. if ('ontouchstart' in document.documentElement) {
  22744. $(document.body).children().off('mouseover', null, $.noop);
  22745. }
  22746. this._activeTrigger[Trigger.CLICK] = false;
  22747. this._activeTrigger[Trigger.FOCUS] = false;
  22748. this._activeTrigger[Trigger.HOVER] = false;
  22749. if ($(this.tip).hasClass(ClassName$6.FADE)) {
  22750. var transitionDuration = Util.getTransitionDurationFromElement(tip);
  22751. $(tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
  22752. } else {
  22753. complete();
  22754. }
  22755. this._hoverState = '';
  22756. };
  22757. _proto.update = function update() {
  22758. if (this._popper !== null) {
  22759. this._popper.scheduleUpdate();
  22760. }
  22761. } // Protected
  22762. ;
  22763. _proto.isWithContent = function isWithContent() {
  22764. return Boolean(this.getTitle());
  22765. };
  22766. _proto.addAttachmentClass = function addAttachmentClass(attachment) {
  22767. $(this.getTipElement()).addClass(CLASS_PREFIX + "-" + attachment);
  22768. };
  22769. _proto.getTipElement = function getTipElement() {
  22770. this.tip = this.tip || $(this.config.template)[0];
  22771. return this.tip;
  22772. };
  22773. _proto.setContent = function setContent() {
  22774. var tip = this.getTipElement();
  22775. this.setElementContent($(tip.querySelectorAll(Selector$6.TOOLTIP_INNER)), this.getTitle());
  22776. $(tip).removeClass(ClassName$6.FADE + " " + ClassName$6.SHOW);
  22777. };
  22778. _proto.setElementContent = function setElementContent($element, content) {
  22779. if (typeof content === 'object' && (content.nodeType || content.jquery)) {
  22780. // Content is a DOM node or a jQuery
  22781. if (this.config.html) {
  22782. if (!$(content).parent().is($element)) {
  22783. $element.empty().append(content);
  22784. }
  22785. } else {
  22786. $element.text($(content).text());
  22787. }
  22788. return;
  22789. }
  22790. if (this.config.html) {
  22791. if (this.config.sanitize) {
  22792. content = sanitizeHtml(content, this.config.whiteList, this.config.sanitizeFn);
  22793. }
  22794. $element.html(content);
  22795. } else {
  22796. $element.text(content);
  22797. }
  22798. };
  22799. _proto.getTitle = function getTitle() {
  22800. var title = this.element.getAttribute('data-original-title');
  22801. if (!title) {
  22802. title = typeof this.config.title === 'function' ? this.config.title.call(this.element) : this.config.title;
  22803. }
  22804. return title;
  22805. } // Private
  22806. ;
  22807. _proto._getPopperConfig = function _getPopperConfig(attachment) {
  22808. var _this3 = this;
  22809. var defaultBsConfig = {
  22810. placement: attachment,
  22811. modifiers: {
  22812. offset: this._getOffset(),
  22813. flip: {
  22814. behavior: this.config.fallbackPlacement
  22815. },
  22816. arrow: {
  22817. element: Selector$6.ARROW
  22818. },
  22819. preventOverflow: {
  22820. boundariesElement: this.config.boundary
  22821. }
  22822. },
  22823. onCreate: function onCreate(data) {
  22824. if (data.originalPlacement !== data.placement) {
  22825. _this3._handlePopperPlacementChange(data);
  22826. }
  22827. },
  22828. onUpdate: function onUpdate(data) {
  22829. return _this3._handlePopperPlacementChange(data);
  22830. }
  22831. };
  22832. return _objectSpread2({}, defaultBsConfig, {}, this.config.popperConfig);
  22833. };
  22834. _proto._getOffset = function _getOffset() {
  22835. var _this4 = this;
  22836. var offset = {};
  22837. if (typeof this.config.offset === 'function') {
  22838. offset.fn = function (data) {
  22839. data.offsets = _objectSpread2({}, data.offsets, {}, _this4.config.offset(data.offsets, _this4.element) || {});
  22840. return data;
  22841. };
  22842. } else {
  22843. offset.offset = this.config.offset;
  22844. }
  22845. return offset;
  22846. };
  22847. _proto._getContainer = function _getContainer() {
  22848. if (this.config.container === false) {
  22849. return document.body;
  22850. }
  22851. if (Util.isElement(this.config.container)) {
  22852. return $(this.config.container);
  22853. }
  22854. return $(document).find(this.config.container);
  22855. };
  22856. _proto._getAttachment = function _getAttachment(placement) {
  22857. return AttachmentMap$1[placement.toUpperCase()];
  22858. };
  22859. _proto._setListeners = function _setListeners() {
  22860. var _this5 = this;
  22861. var triggers = this.config.trigger.split(' ');
  22862. triggers.forEach(function (trigger) {
  22863. if (trigger === 'click') {
  22864. $(_this5.element).on(_this5.constructor.Event.CLICK, _this5.config.selector, function (event) {
  22865. return _this5.toggle(event);
  22866. });
  22867. } else if (trigger !== Trigger.MANUAL) {
  22868. var eventIn = trigger === Trigger.HOVER ? _this5.constructor.Event.MOUSEENTER : _this5.constructor.Event.FOCUSIN;
  22869. var eventOut = trigger === Trigger.HOVER ? _this5.constructor.Event.MOUSELEAVE : _this5.constructor.Event.FOCUSOUT;
  22870. $(_this5.element).on(eventIn, _this5.config.selector, function (event) {
  22871. return _this5._enter(event);
  22872. }).on(eventOut, _this5.config.selector, function (event) {
  22873. return _this5._leave(event);
  22874. });
  22875. }
  22876. });
  22877. this._hideModalHandler = function () {
  22878. if (_this5.element) {
  22879. _this5.hide();
  22880. }
  22881. };
  22882. $(this.element).closest('.modal').on('hide.bs.modal', this._hideModalHandler);
  22883. if (this.config.selector) {
  22884. this.config = _objectSpread2({}, this.config, {
  22885. trigger: 'manual',
  22886. selector: ''
  22887. });
  22888. } else {
  22889. this._fixTitle();
  22890. }
  22891. };
  22892. _proto._fixTitle = function _fixTitle() {
  22893. var titleType = typeof this.element.getAttribute('data-original-title');
  22894. if (this.element.getAttribute('title') || titleType !== 'string') {
  22895. this.element.setAttribute('data-original-title', this.element.getAttribute('title') || '');
  22896. this.element.setAttribute('title', '');
  22897. }
  22898. };
  22899. _proto._enter = function _enter(event, context) {
  22900. var dataKey = this.constructor.DATA_KEY;
  22901. context = context || $(event.currentTarget).data(dataKey);
  22902. if (!context) {
  22903. context = new this.constructor(event.currentTarget, this._getDelegateConfig());
  22904. $(event.currentTarget).data(dataKey, context);
  22905. }
  22906. if (event) {
  22907. context._activeTrigger[event.type === 'focusin' ? Trigger.FOCUS : Trigger.HOVER] = true;
  22908. }
  22909. if ($(context.getTipElement()).hasClass(ClassName$6.SHOW) || context._hoverState === HoverState.SHOW) {
  22910. context._hoverState = HoverState.SHOW;
  22911. return;
  22912. }
  22913. clearTimeout(context._timeout);
  22914. context._hoverState = HoverState.SHOW;
  22915. if (!context.config.delay || !context.config.delay.show) {
  22916. context.show();
  22917. return;
  22918. }
  22919. context._timeout = setTimeout(function () {
  22920. if (context._hoverState === HoverState.SHOW) {
  22921. context.show();
  22922. }
  22923. }, context.config.delay.show);
  22924. };
  22925. _proto._leave = function _leave(event, context) {
  22926. var dataKey = this.constructor.DATA_KEY;
  22927. context = context || $(event.currentTarget).data(dataKey);
  22928. if (!context) {
  22929. context = new this.constructor(event.currentTarget, this._getDelegateConfig());
  22930. $(event.currentTarget).data(dataKey, context);
  22931. }
  22932. if (event) {
  22933. context._activeTrigger[event.type === 'focusout' ? Trigger.FOCUS : Trigger.HOVER] = false;
  22934. }
  22935. if (context._isWithActiveTrigger()) {
  22936. return;
  22937. }
  22938. clearTimeout(context._timeout);
  22939. context._hoverState = HoverState.OUT;
  22940. if (!context.config.delay || !context.config.delay.hide) {
  22941. context.hide();
  22942. return;
  22943. }
  22944. context._timeout = setTimeout(function () {
  22945. if (context._hoverState === HoverState.OUT) {
  22946. context.hide();
  22947. }
  22948. }, context.config.delay.hide);
  22949. };
  22950. _proto._isWithActiveTrigger = function _isWithActiveTrigger() {
  22951. for (var trigger in this._activeTrigger) {
  22952. if (this._activeTrigger[trigger]) {
  22953. return true;
  22954. }
  22955. }
  22956. return false;
  22957. };
  22958. _proto._getConfig = function _getConfig(config) {
  22959. var dataAttributes = $(this.element).data();
  22960. Object.keys(dataAttributes).forEach(function (dataAttr) {
  22961. if (DISALLOWED_ATTRIBUTES.indexOf(dataAttr) !== -1) {
  22962. delete dataAttributes[dataAttr];
  22963. }
  22964. });
  22965. config = _objectSpread2({}, this.constructor.Default, {}, dataAttributes, {}, typeof config === 'object' && config ? config : {});
  22966. if (typeof config.delay === 'number') {
  22967. config.delay = {
  22968. show: config.delay,
  22969. hide: config.delay
  22970. };
  22971. }
  22972. if (typeof config.title === 'number') {
  22973. config.title = config.title.toString();
  22974. }
  22975. if (typeof config.content === 'number') {
  22976. config.content = config.content.toString();
  22977. }
  22978. Util.typeCheckConfig(NAME$6, config, this.constructor.DefaultType);
  22979. if (config.sanitize) {
  22980. config.template = sanitizeHtml(config.template, config.whiteList, config.sanitizeFn);
  22981. }
  22982. return config;
  22983. };
  22984. _proto._getDelegateConfig = function _getDelegateConfig() {
  22985. var config = {};
  22986. if (this.config) {
  22987. for (var key in this.config) {
  22988. if (this.constructor.Default[key] !== this.config[key]) {
  22989. config[key] = this.config[key];
  22990. }
  22991. }
  22992. }
  22993. return config;
  22994. };
  22995. _proto._cleanTipClass = function _cleanTipClass() {
  22996. var $tip = $(this.getTipElement());
  22997. var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX);
  22998. if (tabClass !== null && tabClass.length) {
  22999. $tip.removeClass(tabClass.join(''));
  23000. }
  23001. };
  23002. _proto._handlePopperPlacementChange = function _handlePopperPlacementChange(popperData) {
  23003. var popperInstance = popperData.instance;
  23004. this.tip = popperInstance.popper;
  23005. this._cleanTipClass();
  23006. this.addAttachmentClass(this._getAttachment(popperData.placement));
  23007. };
  23008. _proto._fixTransition = function _fixTransition() {
  23009. var tip = this.getTipElement();
  23010. var initConfigAnimation = this.config.animation;
  23011. if (tip.getAttribute('x-placement') !== null) {
  23012. return;
  23013. }
  23014. $(tip).removeClass(ClassName$6.FADE);
  23015. this.config.animation = false;
  23016. this.hide();
  23017. this.show();
  23018. this.config.animation = initConfigAnimation;
  23019. } // Static
  23020. ;
  23021. Tooltip._jQueryInterface = function _jQueryInterface(config) {
  23022. return this.each(function () {
  23023. var data = $(this).data(DATA_KEY$6);
  23024. var _config = typeof config === 'object' && config;
  23025. if (!data && /dispose|hide/.test(config)) {
  23026. return;
  23027. }
  23028. if (!data) {
  23029. data = new Tooltip(this, _config);
  23030. $(this).data(DATA_KEY$6, data);
  23031. }
  23032. if (typeof config === 'string') {
  23033. if (typeof data[config] === 'undefined') {
  23034. throw new TypeError("No method named \"" + config + "\"");
  23035. }
  23036. data[config]();
  23037. }
  23038. });
  23039. };
  23040. _createClass(Tooltip, null, [{
  23041. key: "VERSION",
  23042. get: function get() {
  23043. return VERSION$6;
  23044. }
  23045. }, {
  23046. key: "Default",
  23047. get: function get() {
  23048. return Default$4;
  23049. }
  23050. }, {
  23051. key: "NAME",
  23052. get: function get() {
  23053. return NAME$6;
  23054. }
  23055. }, {
  23056. key: "DATA_KEY",
  23057. get: function get() {
  23058. return DATA_KEY$6;
  23059. }
  23060. }, {
  23061. key: "Event",
  23062. get: function get() {
  23063. return Event$6;
  23064. }
  23065. }, {
  23066. key: "EVENT_KEY",
  23067. get: function get() {
  23068. return EVENT_KEY$6;
  23069. }
  23070. }, {
  23071. key: "DefaultType",
  23072. get: function get() {
  23073. return DefaultType$4;
  23074. }
  23075. }]);
  23076. return Tooltip;
  23077. }();
  23078. /**
  23079. * ------------------------------------------------------------------------
  23080. * jQuery
  23081. * ------------------------------------------------------------------------
  23082. */
  23083. $.fn[NAME$6] = Tooltip._jQueryInterface;
  23084. $.fn[NAME$6].Constructor = Tooltip;
  23085. $.fn[NAME$6].noConflict = function () {
  23086. $.fn[NAME$6] = JQUERY_NO_CONFLICT$6;
  23087. return Tooltip._jQueryInterface;
  23088. };
  23089. /**
  23090. * ------------------------------------------------------------------------
  23091. * Constants
  23092. * ------------------------------------------------------------------------
  23093. */
  23094. var NAME$7 = 'popover';
  23095. var VERSION$7 = '4.4.1';
  23096. var DATA_KEY$7 = 'bs.popover';
  23097. var EVENT_KEY$7 = "." + DATA_KEY$7;
  23098. var JQUERY_NO_CONFLICT$7 = $.fn[NAME$7];
  23099. var CLASS_PREFIX$1 = 'bs-popover';
  23100. var BSCLS_PREFIX_REGEX$1 = new RegExp("(^|\\s)" + CLASS_PREFIX$1 + "\\S+", 'g');
  23101. var Default$5 = _objectSpread2({}, Tooltip.Default, {
  23102. placement: 'right',
  23103. trigger: 'click',
  23104. content: '',
  23105. template: '<div class="popover" role="tooltip">' + '<div class="arrow"></div>' + '<h3 class="popover-header"></h3>' + '<div class="popover-body"></div></div>'
  23106. });
  23107. var DefaultType$5 = _objectSpread2({}, Tooltip.DefaultType, {
  23108. content: '(string|element|function)'
  23109. });
  23110. var ClassName$7 = {
  23111. FADE: 'fade',
  23112. SHOW: 'show'
  23113. };
  23114. var Selector$7 = {
  23115. TITLE: '.popover-header',
  23116. CONTENT: '.popover-body'
  23117. };
  23118. var Event$7 = {
  23119. HIDE: "hide" + EVENT_KEY$7,
  23120. HIDDEN: "hidden" + EVENT_KEY$7,
  23121. SHOW: "show" + EVENT_KEY$7,
  23122. SHOWN: "shown" + EVENT_KEY$7,
  23123. INSERTED: "inserted" + EVENT_KEY$7,
  23124. CLICK: "click" + EVENT_KEY$7,
  23125. FOCUSIN: "focusin" + EVENT_KEY$7,
  23126. FOCUSOUT: "focusout" + EVENT_KEY$7,
  23127. MOUSEENTER: "mouseenter" + EVENT_KEY$7,
  23128. MOUSELEAVE: "mouseleave" + EVENT_KEY$7
  23129. };
  23130. /**
  23131. * ------------------------------------------------------------------------
  23132. * Class Definition
  23133. * ------------------------------------------------------------------------
  23134. */
  23135. var Popover =
  23136. /*#__PURE__*/
  23137. function (_Tooltip) {
  23138. _inheritsLoose(Popover, _Tooltip);
  23139. function Popover() {
  23140. return _Tooltip.apply(this, arguments) || this;
  23141. }
  23142. var _proto = Popover.prototype;
  23143. // Overrides
  23144. _proto.isWithContent = function isWithContent() {
  23145. return this.getTitle() || this._getContent();
  23146. };
  23147. _proto.addAttachmentClass = function addAttachmentClass(attachment) {
  23148. $(this.getTipElement()).addClass(CLASS_PREFIX$1 + "-" + attachment);
  23149. };
  23150. _proto.getTipElement = function getTipElement() {
  23151. this.tip = this.tip || $(this.config.template)[0];
  23152. return this.tip;
  23153. };
  23154. _proto.setContent = function setContent() {
  23155. var $tip = $(this.getTipElement()); // We use append for html objects to maintain js events
  23156. this.setElementContent($tip.find(Selector$7.TITLE), this.getTitle());
  23157. var content = this._getContent();
  23158. if (typeof content === 'function') {
  23159. content = content.call(this.element);
  23160. }
  23161. this.setElementContent($tip.find(Selector$7.CONTENT), content);
  23162. $tip.removeClass(ClassName$7.FADE + " " + ClassName$7.SHOW);
  23163. } // Private
  23164. ;
  23165. _proto._getContent = function _getContent() {
  23166. return this.element.getAttribute('data-content') || this.config.content;
  23167. };
  23168. _proto._cleanTipClass = function _cleanTipClass() {
  23169. var $tip = $(this.getTipElement());
  23170. var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX$1);
  23171. if (tabClass !== null && tabClass.length > 0) {
  23172. $tip.removeClass(tabClass.join(''));
  23173. }
  23174. } // Static
  23175. ;
  23176. Popover._jQueryInterface = function _jQueryInterface(config) {
  23177. return this.each(function () {
  23178. var data = $(this).data(DATA_KEY$7);
  23179. var _config = typeof config === 'object' ? config : null;
  23180. if (!data && /dispose|hide/.test(config)) {
  23181. return;
  23182. }
  23183. if (!data) {
  23184. data = new Popover(this, _config);
  23185. $(this).data(DATA_KEY$7, data);
  23186. }
  23187. if (typeof config === 'string') {
  23188. if (typeof data[config] === 'undefined') {
  23189. throw new TypeError("No method named \"" + config + "\"");
  23190. }
  23191. data[config]();
  23192. }
  23193. });
  23194. };
  23195. _createClass(Popover, null, [{
  23196. key: "VERSION",
  23197. // Getters
  23198. get: function get() {
  23199. return VERSION$7;
  23200. }
  23201. }, {
  23202. key: "Default",
  23203. get: function get() {
  23204. return Default$5;
  23205. }
  23206. }, {
  23207. key: "NAME",
  23208. get: function get() {
  23209. return NAME$7;
  23210. }
  23211. }, {
  23212. key: "DATA_KEY",
  23213. get: function get() {
  23214. return DATA_KEY$7;
  23215. }
  23216. }, {
  23217. key: "Event",
  23218. get: function get() {
  23219. return Event$7;
  23220. }
  23221. }, {
  23222. key: "EVENT_KEY",
  23223. get: function get() {
  23224. return EVENT_KEY$7;
  23225. }
  23226. }, {
  23227. key: "DefaultType",
  23228. get: function get() {
  23229. return DefaultType$5;
  23230. }
  23231. }]);
  23232. return Popover;
  23233. }(Tooltip);
  23234. /**
  23235. * ------------------------------------------------------------------------
  23236. * jQuery
  23237. * ------------------------------------------------------------------------
  23238. */
  23239. $.fn[NAME$7] = Popover._jQueryInterface;
  23240. $.fn[NAME$7].Constructor = Popover;
  23241. $.fn[NAME$7].noConflict = function () {
  23242. $.fn[NAME$7] = JQUERY_NO_CONFLICT$7;
  23243. return Popover._jQueryInterface;
  23244. };
  23245. /**
  23246. * ------------------------------------------------------------------------
  23247. * Constants
  23248. * ------------------------------------------------------------------------
  23249. */
  23250. var NAME$8 = 'scrollspy';
  23251. var VERSION$8 = '4.4.1';
  23252. var DATA_KEY$8 = 'bs.scrollspy';
  23253. var EVENT_KEY$8 = "." + DATA_KEY$8;
  23254. var DATA_API_KEY$6 = '.data-api';
  23255. var JQUERY_NO_CONFLICT$8 = $.fn[NAME$8];
  23256. var Default$6 = {
  23257. offset: 10,
  23258. method: 'auto',
  23259. target: ''
  23260. };
  23261. var DefaultType$6 = {
  23262. offset: 'number',
  23263. method: 'string',
  23264. target: '(string|element)'
  23265. };
  23266. var Event$8 = {
  23267. ACTIVATE: "activate" + EVENT_KEY$8,
  23268. SCROLL: "scroll" + EVENT_KEY$8,
  23269. LOAD_DATA_API: "load" + EVENT_KEY$8 + DATA_API_KEY$6
  23270. };
  23271. var ClassName$8 = {
  23272. DROPDOWN_ITEM: 'dropdown-item',
  23273. DROPDOWN_MENU: 'dropdown-menu',
  23274. ACTIVE: 'active'
  23275. };
  23276. var Selector$8 = {
  23277. DATA_SPY: '[data-spy="scroll"]',
  23278. ACTIVE: '.active',
  23279. NAV_LIST_GROUP: '.nav, .list-group',
  23280. NAV_LINKS: '.nav-link',
  23281. NAV_ITEMS: '.nav-item',
  23282. LIST_ITEMS: '.list-group-item',
  23283. DROPDOWN: '.dropdown',
  23284. DROPDOWN_ITEMS: '.dropdown-item',
  23285. DROPDOWN_TOGGLE: '.dropdown-toggle'
  23286. };
  23287. var OffsetMethod = {
  23288. OFFSET: 'offset',
  23289. POSITION: 'position'
  23290. };
  23291. /**
  23292. * ------------------------------------------------------------------------
  23293. * Class Definition
  23294. * ------------------------------------------------------------------------
  23295. */
  23296. var ScrollSpy =
  23297. /*#__PURE__*/
  23298. function () {
  23299. function ScrollSpy(element, config) {
  23300. var _this = this;
  23301. this._element = element;
  23302. this._scrollElement = element.tagName === 'BODY' ? window : element;
  23303. this._config = this._getConfig(config);
  23304. this._selector = this._config.target + " " + Selector$8.NAV_LINKS + "," + (this._config.target + " " + Selector$8.LIST_ITEMS + ",") + (this._config.target + " " + Selector$8.DROPDOWN_ITEMS);
  23305. this._offsets = [];
  23306. this._targets = [];
  23307. this._activeTarget = null;
  23308. this._scrollHeight = 0;
  23309. $(this._scrollElement).on(Event$8.SCROLL, function (event) {
  23310. return _this._process(event);
  23311. });
  23312. this.refresh();
  23313. this._process();
  23314. } // Getters
  23315. var _proto = ScrollSpy.prototype;
  23316. // Public
  23317. _proto.refresh = function refresh() {
  23318. var _this2 = this;
  23319. var autoMethod = this._scrollElement === this._scrollElement.window ? OffsetMethod.OFFSET : OffsetMethod.POSITION;
  23320. var offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method;
  23321. var offsetBase = offsetMethod === OffsetMethod.POSITION ? this._getScrollTop() : 0;
  23322. this._offsets = [];
  23323. this._targets = [];
  23324. this._scrollHeight = this._getScrollHeight();
  23325. var targets = [].slice.call(document.querySelectorAll(this._selector));
  23326. targets.map(function (element) {
  23327. var target;
  23328. var targetSelector = Util.getSelectorFromElement(element);
  23329. if (targetSelector) {
  23330. target = document.querySelector(targetSelector);
  23331. }
  23332. if (target) {
  23333. var targetBCR = target.getBoundingClientRect();
  23334. if (targetBCR.width || targetBCR.height) {
  23335. // TODO (fat): remove sketch reliance on jQuery position/offset
  23336. return [$(target)[offsetMethod]().top + offsetBase, targetSelector];
  23337. }
  23338. }
  23339. return null;
  23340. }).filter(function (item) {
  23341. return item;
  23342. }).sort(function (a, b) {
  23343. return a[0] - b[0];
  23344. }).forEach(function (item) {
  23345. _this2._offsets.push(item[0]);
  23346. _this2._targets.push(item[1]);
  23347. });
  23348. };
  23349. _proto.dispose = function dispose() {
  23350. $.removeData(this._element, DATA_KEY$8);
  23351. $(this._scrollElement).off(EVENT_KEY$8);
  23352. this._element = null;
  23353. this._scrollElement = null;
  23354. this._config = null;
  23355. this._selector = null;
  23356. this._offsets = null;
  23357. this._targets = null;
  23358. this._activeTarget = null;
  23359. this._scrollHeight = null;
  23360. } // Private
  23361. ;
  23362. _proto._getConfig = function _getConfig(config) {
  23363. config = _objectSpread2({}, Default$6, {}, typeof config === 'object' && config ? config : {});
  23364. if (typeof config.target !== 'string') {
  23365. var id = $(config.target).attr('id');
  23366. if (!id) {
  23367. id = Util.getUID(NAME$8);
  23368. $(config.target).attr('id', id);
  23369. }
  23370. config.target = "#" + id;
  23371. }
  23372. Util.typeCheckConfig(NAME$8, config, DefaultType$6);
  23373. return config;
  23374. };
  23375. _proto._getScrollTop = function _getScrollTop() {
  23376. return this._scrollElement === window ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop;
  23377. };
  23378. _proto._getScrollHeight = function _getScrollHeight() {
  23379. return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
  23380. };
  23381. _proto._getOffsetHeight = function _getOffsetHeight() {
  23382. return this._scrollElement === window ? window.innerHeight : this._scrollElement.getBoundingClientRect().height;
  23383. };
  23384. _proto._process = function _process() {
  23385. var scrollTop = this._getScrollTop() + this._config.offset;
  23386. var scrollHeight = this._getScrollHeight();
  23387. var maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight();
  23388. if (this._scrollHeight !== scrollHeight) {
  23389. this.refresh();
  23390. }
  23391. if (scrollTop >= maxScroll) {
  23392. var target = this._targets[this._targets.length - 1];
  23393. if (this._activeTarget !== target) {
  23394. this._activate(target);
  23395. }
  23396. return;
  23397. }
  23398. if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) {
  23399. this._activeTarget = null;
  23400. this._clear();
  23401. return;
  23402. }
  23403. var offsetLength = this._offsets.length;
  23404. for (var i = offsetLength; i--;) {
  23405. var isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1]);
  23406. if (isActiveTarget) {
  23407. this._activate(this._targets[i]);
  23408. }
  23409. }
  23410. };
  23411. _proto._activate = function _activate(target) {
  23412. this._activeTarget = target;
  23413. this._clear();
  23414. var queries = this._selector.split(',').map(function (selector) {
  23415. return selector + "[data-target=\"" + target + "\"]," + selector + "[href=\"" + target + "\"]";
  23416. });
  23417. var $link = $([].slice.call(document.querySelectorAll(queries.join(','))));
  23418. if ($link.hasClass(ClassName$8.DROPDOWN_ITEM)) {
  23419. $link.closest(Selector$8.DROPDOWN).find(Selector$8.DROPDOWN_TOGGLE).addClass(ClassName$8.ACTIVE);
  23420. $link.addClass(ClassName$8.ACTIVE);
  23421. } else {
  23422. // Set triggered link as active
  23423. $link.addClass(ClassName$8.ACTIVE); // Set triggered links parents as active
  23424. // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor
  23425. $link.parents(Selector$8.NAV_LIST_GROUP).prev(Selector$8.NAV_LINKS + ", " + Selector$8.LIST_ITEMS).addClass(ClassName$8.ACTIVE); // Handle special case when .nav-link is inside .nav-item
  23426. $link.parents(Selector$8.NAV_LIST_GROUP).prev(Selector$8.NAV_ITEMS).children(Selector$8.NAV_LINKS).addClass(ClassName$8.ACTIVE);
  23427. }
  23428. $(this._scrollElement).trigger(Event$8.ACTIVATE, {
  23429. relatedTarget: target
  23430. });
  23431. };
  23432. _proto._clear = function _clear() {
  23433. [].slice.call(document.querySelectorAll(this._selector)).filter(function (node) {
  23434. return node.classList.contains(ClassName$8.ACTIVE);
  23435. }).forEach(function (node) {
  23436. return node.classList.remove(ClassName$8.ACTIVE);
  23437. });
  23438. } // Static
  23439. ;
  23440. ScrollSpy._jQueryInterface = function _jQueryInterface(config) {
  23441. return this.each(function () {
  23442. var data = $(this).data(DATA_KEY$8);
  23443. var _config = typeof config === 'object' && config;
  23444. if (!data) {
  23445. data = new ScrollSpy(this, _config);
  23446. $(this).data(DATA_KEY$8, data);
  23447. }
  23448. if (typeof config === 'string') {
  23449. if (typeof data[config] === 'undefined') {
  23450. throw new TypeError("No method named \"" + config + "\"");
  23451. }
  23452. data[config]();
  23453. }
  23454. });
  23455. };
  23456. _createClass(ScrollSpy, null, [{
  23457. key: "VERSION",
  23458. get: function get() {
  23459. return VERSION$8;
  23460. }
  23461. }, {
  23462. key: "Default",
  23463. get: function get() {
  23464. return Default$6;
  23465. }
  23466. }]);
  23467. return ScrollSpy;
  23468. }();
  23469. /**
  23470. * ------------------------------------------------------------------------
  23471. * Data Api implementation
  23472. * ------------------------------------------------------------------------
  23473. */
  23474. $(window).on(Event$8.LOAD_DATA_API, function () {
  23475. var scrollSpys = [].slice.call(document.querySelectorAll(Selector$8.DATA_SPY));
  23476. var scrollSpysLength = scrollSpys.length;
  23477. for (var i = scrollSpysLength; i--;) {
  23478. var $spy = $(scrollSpys[i]);
  23479. ScrollSpy._jQueryInterface.call($spy, $spy.data());
  23480. }
  23481. });
  23482. /**
  23483. * ------------------------------------------------------------------------
  23484. * jQuery
  23485. * ------------------------------------------------------------------------
  23486. */
  23487. $.fn[NAME$8] = ScrollSpy._jQueryInterface;
  23488. $.fn[NAME$8].Constructor = ScrollSpy;
  23489. $.fn[NAME$8].noConflict = function () {
  23490. $.fn[NAME$8] = JQUERY_NO_CONFLICT$8;
  23491. return ScrollSpy._jQueryInterface;
  23492. };
  23493. /**
  23494. * ------------------------------------------------------------------------
  23495. * Constants
  23496. * ------------------------------------------------------------------------
  23497. */
  23498. var NAME$9 = 'tab';
  23499. var VERSION$9 = '4.4.1';
  23500. var DATA_KEY$9 = 'bs.tab';
  23501. var EVENT_KEY$9 = "." + DATA_KEY$9;
  23502. var DATA_API_KEY$7 = '.data-api';
  23503. var JQUERY_NO_CONFLICT$9 = $.fn[NAME$9];
  23504. var Event$9 = {
  23505. HIDE: "hide" + EVENT_KEY$9,
  23506. HIDDEN: "hidden" + EVENT_KEY$9,
  23507. SHOW: "show" + EVENT_KEY$9,
  23508. SHOWN: "shown" + EVENT_KEY$9,
  23509. CLICK_DATA_API: "click" + EVENT_KEY$9 + DATA_API_KEY$7
  23510. };
  23511. var ClassName$9 = {
  23512. DROPDOWN_MENU: 'dropdown-menu',
  23513. ACTIVE: 'active',
  23514. DISABLED: 'disabled',
  23515. FADE: 'fade',
  23516. SHOW: 'show'
  23517. };
  23518. var Selector$9 = {
  23519. DROPDOWN: '.dropdown',
  23520. NAV_LIST_GROUP: '.nav, .list-group',
  23521. ACTIVE: '.active',
  23522. ACTIVE_UL: '> li > .active',
  23523. DATA_TOGGLE: '[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',
  23524. DROPDOWN_TOGGLE: '.dropdown-toggle',
  23525. DROPDOWN_ACTIVE_CHILD: '> .dropdown-menu .active'
  23526. };
  23527. /**
  23528. * ------------------------------------------------------------------------
  23529. * Class Definition
  23530. * ------------------------------------------------------------------------
  23531. */
  23532. var Tab =
  23533. /*#__PURE__*/
  23534. function () {
  23535. function Tab(element) {
  23536. this._element = element;
  23537. } // Getters
  23538. var _proto = Tab.prototype;
  23539. // Public
  23540. _proto.show = function show() {
  23541. var _this = this;
  23542. if (this._element.parentNode && this._element.parentNode.nodeType === Node.ELEMENT_NODE && $(this._element).hasClass(ClassName$9.ACTIVE) || $(this._element).hasClass(ClassName$9.DISABLED)) {
  23543. return;
  23544. }
  23545. var target;
  23546. var previous;
  23547. var listElement = $(this._element).closest(Selector$9.NAV_LIST_GROUP)[0];
  23548. var selector = Util.getSelectorFromElement(this._element);
  23549. if (listElement) {
  23550. var itemSelector = listElement.nodeName === 'UL' || listElement.nodeName === 'OL' ? Selector$9.ACTIVE_UL : Selector$9.ACTIVE;
  23551. previous = $.makeArray($(listElement).find(itemSelector));
  23552. previous = previous[previous.length - 1];
  23553. }
  23554. var hideEvent = $.Event(Event$9.HIDE, {
  23555. relatedTarget: this._element
  23556. });
  23557. var showEvent = $.Event(Event$9.SHOW, {
  23558. relatedTarget: previous
  23559. });
  23560. if (previous) {
  23561. $(previous).trigger(hideEvent);
  23562. }
  23563. $(this._element).trigger(showEvent);
  23564. if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) {
  23565. return;
  23566. }
  23567. if (selector) {
  23568. target = document.querySelector(selector);
  23569. }
  23570. this._activate(this._element, listElement);
  23571. var complete = function complete() {
  23572. var hiddenEvent = $.Event(Event$9.HIDDEN, {
  23573. relatedTarget: _this._element
  23574. });
  23575. var shownEvent = $.Event(Event$9.SHOWN, {
  23576. relatedTarget: previous
  23577. });
  23578. $(previous).trigger(hiddenEvent);
  23579. $(_this._element).trigger(shownEvent);
  23580. };
  23581. if (target) {
  23582. this._activate(target, target.parentNode, complete);
  23583. } else {
  23584. complete();
  23585. }
  23586. };
  23587. _proto.dispose = function dispose() {
  23588. $.removeData(this._element, DATA_KEY$9);
  23589. this._element = null;
  23590. } // Private
  23591. ;
  23592. _proto._activate = function _activate(element, container, callback) {
  23593. var _this2 = this;
  23594. var activeElements = container && (container.nodeName === 'UL' || container.nodeName === 'OL') ? $(container).find(Selector$9.ACTIVE_UL) : $(container).children(Selector$9.ACTIVE);
  23595. var active = activeElements[0];
  23596. var isTransitioning = callback && active && $(active).hasClass(ClassName$9.FADE);
  23597. var complete = function complete() {
  23598. return _this2._transitionComplete(element, active, callback);
  23599. };
  23600. if (active && isTransitioning) {
  23601. var transitionDuration = Util.getTransitionDurationFromElement(active);
  23602. $(active).removeClass(ClassName$9.SHOW).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
  23603. } else {
  23604. complete();
  23605. }
  23606. };
  23607. _proto._transitionComplete = function _transitionComplete(element, active, callback) {
  23608. if (active) {
  23609. $(active).removeClass(ClassName$9.ACTIVE);
  23610. var dropdownChild = $(active.parentNode).find(Selector$9.DROPDOWN_ACTIVE_CHILD)[0];
  23611. if (dropdownChild) {
  23612. $(dropdownChild).removeClass(ClassName$9.ACTIVE);
  23613. }
  23614. if (active.getAttribute('role') === 'tab') {
  23615. active.setAttribute('aria-selected', false);
  23616. }
  23617. }
  23618. $(element).addClass(ClassName$9.ACTIVE);
  23619. if (element.getAttribute('role') === 'tab') {
  23620. element.setAttribute('aria-selected', true);
  23621. }
  23622. Util.reflow(element);
  23623. if (element.classList.contains(ClassName$9.FADE)) {
  23624. element.classList.add(ClassName$9.SHOW);
  23625. }
  23626. if (element.parentNode && $(element.parentNode).hasClass(ClassName$9.DROPDOWN_MENU)) {
  23627. var dropdownElement = $(element).closest(Selector$9.DROPDOWN)[0];
  23628. if (dropdownElement) {
  23629. var dropdownToggleList = [].slice.call(dropdownElement.querySelectorAll(Selector$9.DROPDOWN_TOGGLE));
  23630. $(dropdownToggleList).addClass(ClassName$9.ACTIVE);
  23631. }
  23632. element.setAttribute('aria-expanded', true);
  23633. }
  23634. if (callback) {
  23635. callback();
  23636. }
  23637. } // Static
  23638. ;
  23639. Tab._jQueryInterface = function _jQueryInterface(config) {
  23640. return this.each(function () {
  23641. var $this = $(this);
  23642. var data = $this.data(DATA_KEY$9);
  23643. if (!data) {
  23644. data = new Tab(this);
  23645. $this.data(DATA_KEY$9, data);
  23646. }
  23647. if (typeof config === 'string') {
  23648. if (typeof data[config] === 'undefined') {
  23649. throw new TypeError("No method named \"" + config + "\"");
  23650. }
  23651. data[config]();
  23652. }
  23653. });
  23654. };
  23655. _createClass(Tab, null, [{
  23656. key: "VERSION",
  23657. get: function get() {
  23658. return VERSION$9;
  23659. }
  23660. }]);
  23661. return Tab;
  23662. }();
  23663. /**
  23664. * ------------------------------------------------------------------------
  23665. * Data Api implementation
  23666. * ------------------------------------------------------------------------
  23667. */
  23668. $(document).on(Event$9.CLICK_DATA_API, Selector$9.DATA_TOGGLE, function (event) {
  23669. event.preventDefault();
  23670. Tab._jQueryInterface.call($(this), 'show');
  23671. });
  23672. /**
  23673. * ------------------------------------------------------------------------
  23674. * jQuery
  23675. * ------------------------------------------------------------------------
  23676. */
  23677. $.fn[NAME$9] = Tab._jQueryInterface;
  23678. $.fn[NAME$9].Constructor = Tab;
  23679. $.fn[NAME$9].noConflict = function () {
  23680. $.fn[NAME$9] = JQUERY_NO_CONFLICT$9;
  23681. return Tab._jQueryInterface;
  23682. };
  23683. /**
  23684. * ------------------------------------------------------------------------
  23685. * Constants
  23686. * ------------------------------------------------------------------------
  23687. */
  23688. var NAME$a = 'toast';
  23689. var VERSION$a = '4.4.1';
  23690. var DATA_KEY$a = 'bs.toast';
  23691. var EVENT_KEY$a = "." + DATA_KEY$a;
  23692. var JQUERY_NO_CONFLICT$a = $.fn[NAME$a];
  23693. var Event$a = {
  23694. CLICK_DISMISS: "click.dismiss" + EVENT_KEY$a,
  23695. HIDE: "hide" + EVENT_KEY$a,
  23696. HIDDEN: "hidden" + EVENT_KEY$a,
  23697. SHOW: "show" + EVENT_KEY$a,
  23698. SHOWN: "shown" + EVENT_KEY$a
  23699. };
  23700. var ClassName$a = {
  23701. FADE: 'fade',
  23702. HIDE: 'hide',
  23703. SHOW: 'show',
  23704. SHOWING: 'showing'
  23705. };
  23706. var DefaultType$7 = {
  23707. animation: 'boolean',
  23708. autohide: 'boolean',
  23709. delay: 'number'
  23710. };
  23711. var Default$7 = {
  23712. animation: true,
  23713. autohide: true,
  23714. delay: 500
  23715. };
  23716. var Selector$a = {
  23717. DATA_DISMISS: '[data-dismiss="toast"]'
  23718. };
  23719. /**
  23720. * ------------------------------------------------------------------------
  23721. * Class Definition
  23722. * ------------------------------------------------------------------------
  23723. */
  23724. var Toast =
  23725. /*#__PURE__*/
  23726. function () {
  23727. function Toast(element, config) {
  23728. this._element = element;
  23729. this._config = this._getConfig(config);
  23730. this._timeout = null;
  23731. this._setListeners();
  23732. } // Getters
  23733. var _proto = Toast.prototype;
  23734. // Public
  23735. _proto.show = function show() {
  23736. var _this = this;
  23737. var showEvent = $.Event(Event$a.SHOW);
  23738. $(this._element).trigger(showEvent);
  23739. if (showEvent.isDefaultPrevented()) {
  23740. return;
  23741. }
  23742. if (this._config.animation) {
  23743. this._element.classList.add(ClassName$a.FADE);
  23744. }
  23745. var complete = function complete() {
  23746. _this._element.classList.remove(ClassName$a.SHOWING);
  23747. _this._element.classList.add(ClassName$a.SHOW);
  23748. $(_this._element).trigger(Event$a.SHOWN);
  23749. if (_this._config.autohide) {
  23750. _this._timeout = setTimeout(function () {
  23751. _this.hide();
  23752. }, _this._config.delay);
  23753. }
  23754. };
  23755. this._element.classList.remove(ClassName$a.HIDE);
  23756. Util.reflow(this._element);
  23757. this._element.classList.add(ClassName$a.SHOWING);
  23758. if (this._config.animation) {
  23759. var transitionDuration = Util.getTransitionDurationFromElement(this._element);
  23760. $(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
  23761. } else {
  23762. complete();
  23763. }
  23764. };
  23765. _proto.hide = function hide() {
  23766. if (!this._element.classList.contains(ClassName$a.SHOW)) {
  23767. return;
  23768. }
  23769. var hideEvent = $.Event(Event$a.HIDE);
  23770. $(this._element).trigger(hideEvent);
  23771. if (hideEvent.isDefaultPrevented()) {
  23772. return;
  23773. }
  23774. this._close();
  23775. };
  23776. _proto.dispose = function dispose() {
  23777. clearTimeout(this._timeout);
  23778. this._timeout = null;
  23779. if (this._element.classList.contains(ClassName$a.SHOW)) {
  23780. this._element.classList.remove(ClassName$a.SHOW);
  23781. }
  23782. $(this._element).off(Event$a.CLICK_DISMISS);
  23783. $.removeData(this._element, DATA_KEY$a);
  23784. this._element = null;
  23785. this._config = null;
  23786. } // Private
  23787. ;
  23788. _proto._getConfig = function _getConfig(config) {
  23789. config = _objectSpread2({}, Default$7, {}, $(this._element).data(), {}, typeof config === 'object' && config ? config : {});
  23790. Util.typeCheckConfig(NAME$a, config, this.constructor.DefaultType);
  23791. return config;
  23792. };
  23793. _proto._setListeners = function _setListeners() {
  23794. var _this2 = this;
  23795. $(this._element).on(Event$a.CLICK_DISMISS, Selector$a.DATA_DISMISS, function () {
  23796. return _this2.hide();
  23797. });
  23798. };
  23799. _proto._close = function _close() {
  23800. var _this3 = this;
  23801. var complete = function complete() {
  23802. _this3._element.classList.add(ClassName$a.HIDE);
  23803. $(_this3._element).trigger(Event$a.HIDDEN);
  23804. };
  23805. this._element.classList.remove(ClassName$a.SHOW);
  23806. if (this._config.animation) {
  23807. var transitionDuration = Util.getTransitionDurationFromElement(this._element);
  23808. $(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
  23809. } else {
  23810. complete();
  23811. }
  23812. } // Static
  23813. ;
  23814. Toast._jQueryInterface = function _jQueryInterface(config) {
  23815. return this.each(function () {
  23816. var $element = $(this);
  23817. var data = $element.data(DATA_KEY$a);
  23818. var _config = typeof config === 'object' && config;
  23819. if (!data) {
  23820. data = new Toast(this, _config);
  23821. $element.data(DATA_KEY$a, data);
  23822. }
  23823. if (typeof config === 'string') {
  23824. if (typeof data[config] === 'undefined') {
  23825. throw new TypeError("No method named \"" + config + "\"");
  23826. }
  23827. data[config](this);
  23828. }
  23829. });
  23830. };
  23831. _createClass(Toast, null, [{
  23832. key: "VERSION",
  23833. get: function get() {
  23834. return VERSION$a;
  23835. }
  23836. }, {
  23837. key: "DefaultType",
  23838. get: function get() {
  23839. return DefaultType$7;
  23840. }
  23841. }, {
  23842. key: "Default",
  23843. get: function get() {
  23844. return Default$7;
  23845. }
  23846. }]);
  23847. return Toast;
  23848. }();
  23849. /**
  23850. * ------------------------------------------------------------------------
  23851. * jQuery
  23852. * ------------------------------------------------------------------------
  23853. */
  23854. $.fn[NAME$a] = Toast._jQueryInterface;
  23855. $.fn[NAME$a].Constructor = Toast;
  23856. $.fn[NAME$a].noConflict = function () {
  23857. $.fn[NAME$a] = JQUERY_NO_CONFLICT$a;
  23858. return Toast._jQueryInterface;
  23859. };
  23860. exports.Alert = Alert;
  23861. exports.Button = Button;
  23862. exports.Carousel = Carousel;
  23863. exports.Collapse = Collapse;
  23864. exports.Dropdown = Dropdown;
  23865. exports.Modal = Modal;
  23866. exports.Popover = Popover;
  23867. exports.Scrollspy = ScrollSpy;
  23868. exports.Tab = Tab;
  23869. exports.Toast = Toast;
  23870. exports.Tooltip = Tooltip;
  23871. exports.Util = Util;
  23872. Object.defineProperty(exports, '__esModule', { value: true });
  23873. })));
  23874. //# sourceMappingURL=bootstrap.js.map
  23875. ;
  23876. /* **********************************************
  23877. Begin prism-core.js
  23878. ********************************************** */
  23879. var _self = (typeof window !== 'undefined')
  23880. ? window // if in browser
  23881. : (
  23882. (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
  23883. ? self // if in worker
  23884. : {} // if in node js
  23885. );
  23886. /**
  23887. * Prism: Lightweight, robust, elegant syntax highlighting
  23888. * MIT license http://www.opensource.org/licenses/mit-license.php/
  23889. * @author Lea Verou http://lea.verou.me
  23890. */
  23891. var Prism = (function (_self){
  23892. // Private helper vars
  23893. var lang = /\blang(?:uage)?-([\w-]+)\b/i;
  23894. var uniqueId = 0;
  23895. var _ = {
  23896. manual: _self.Prism && _self.Prism.manual,
  23897. disableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,
  23898. util: {
  23899. encode: function (tokens) {
  23900. if (tokens instanceof Token) {
  23901. return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias);
  23902. } else if (Array.isArray(tokens)) {
  23903. return tokens.map(_.util.encode);
  23904. } else {
  23905. return tokens.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
  23906. }
  23907. },
  23908. type: function (o) {
  23909. return Object.prototype.toString.call(o).slice(8, -1);
  23910. },
  23911. objId: function (obj) {
  23912. if (!obj['__id']) {
  23913. Object.defineProperty(obj, '__id', { value: ++uniqueId });
  23914. }
  23915. return obj['__id'];
  23916. },
  23917. // Deep clone a language definition (e.g. to extend it)
  23918. clone: function deepClone(o, visited) {
  23919. var clone, id, type = _.util.type(o);
  23920. visited = visited || {};
  23921. switch (type) {
  23922. case 'Object':
  23923. id = _.util.objId(o);
  23924. if (visited[id]) {
  23925. return visited[id];
  23926. }
  23927. clone = {};
  23928. visited[id] = clone;
  23929. for (var key in o) {
  23930. if (o.hasOwnProperty(key)) {
  23931. clone[key] = deepClone(o[key], visited);
  23932. }
  23933. }
  23934. return clone;
  23935. case 'Array':
  23936. id = _.util.objId(o);
  23937. if (visited[id]) {
  23938. return visited[id];
  23939. }
  23940. clone = [];
  23941. visited[id] = clone;
  23942. o.forEach(function (v, i) {
  23943. clone[i] = deepClone(v, visited);
  23944. });
  23945. return clone;
  23946. default:
  23947. return o;
  23948. }
  23949. },
  23950. /**
  23951. * Returns the Prism language of the given element set by a `language-xxxx` or `lang-xxxx` class.
  23952. *
  23953. * If no language is set for the element or the element is `null` or `undefined`, `none` will be returned.
  23954. *
  23955. * @param {Element} element
  23956. * @returns {string}
  23957. */
  23958. getLanguage: function (element) {
  23959. while (element && !lang.test(element.className)) {
  23960. element = element.parentElement;
  23961. }
  23962. if (element) {
  23963. return (element.className.match(lang) || [, 'none'])[1].toLowerCase();
  23964. }
  23965. return 'none';
  23966. },
  23967. /**
  23968. * Returns the script element that is currently executing.
  23969. *
  23970. * This does __not__ work for line script element.
  23971. *
  23972. * @returns {HTMLScriptElement | null}
  23973. */
  23974. currentScript: function () {
  23975. if (typeof document === 'undefined') {
  23976. return null;
  23977. }
  23978. if ('currentScript' in document) {
  23979. return document.currentScript;
  23980. }
  23981. // IE11 workaround
  23982. // we'll get the src of the current script by parsing IE11's error stack trace
  23983. // this will not work for inline scripts
  23984. try {
  23985. throw new Error();
  23986. } catch (err) {
  23987. // Get file src url from stack. Specifically works with the format of stack traces in IE.
  23988. // A stack will look like this:
  23989. //
  23990. // Error
  23991. // at _.util.currentScript (http://localhost/components/prism-core.js:119:5)
  23992. // at Global code (http://localhost/components/prism-core.js:606:1)
  23993. var src = (/at [^(\r\n]*\((.*):.+:.+\)$/i.exec(err.stack) || [])[1];
  23994. if (src) {
  23995. var scripts = document.getElementsByTagName('script');
  23996. for (var i in scripts) {
  23997. if (scripts[i].src == src) {
  23998. return scripts[i];
  23999. }
  24000. }
  24001. }
  24002. return null;
  24003. }
  24004. }
  24005. },
  24006. languages: {
  24007. extend: function (id, redef) {
  24008. var lang = _.util.clone(_.languages[id]);
  24009. for (var key in redef) {
  24010. lang[key] = redef[key];
  24011. }
  24012. return lang;
  24013. },
  24014. /**
  24015. * Insert a token before another token in a language literal
  24016. * As this needs to recreate the object (we cannot actually insert before keys in object literals),
  24017. * we cannot just provide an object, we need an object and a key.
  24018. * @param inside The key (or language id) of the parent
  24019. * @param before The key to insert before.
  24020. * @param insert Object with the key/value pairs to insert
  24021. * @param root The object that contains `inside`. If equal to Prism.languages, it can be omitted.
  24022. */
  24023. insertBefore: function (inside, before, insert, root) {
  24024. root = root || _.languages;
  24025. var grammar = root[inside];
  24026. var ret = {};
  24027. for (var token in grammar) {
  24028. if (grammar.hasOwnProperty(token)) {
  24029. if (token == before) {
  24030. for (var newToken in insert) {
  24031. if (insert.hasOwnProperty(newToken)) {
  24032. ret[newToken] = insert[newToken];
  24033. }
  24034. }
  24035. }
  24036. // Do not insert token which also occur in insert. See #1525
  24037. if (!insert.hasOwnProperty(token)) {
  24038. ret[token] = grammar[token];
  24039. }
  24040. }
  24041. }
  24042. var old = root[inside];
  24043. root[inside] = ret;
  24044. // Update references in other language definitions
  24045. _.languages.DFS(_.languages, function(key, value) {
  24046. if (value === old && key != inside) {
  24047. this[key] = ret;
  24048. }
  24049. });
  24050. return ret;
  24051. },
  24052. // Traverse a language definition with Depth First Search
  24053. DFS: function DFS(o, callback, type, visited) {
  24054. visited = visited || {};
  24055. var objId = _.util.objId;
  24056. for (var i in o) {
  24057. if (o.hasOwnProperty(i)) {
  24058. callback.call(o, i, o[i], type || i);
  24059. var property = o[i],
  24060. propertyType = _.util.type(property);
  24061. if (propertyType === 'Object' && !visited[objId(property)]) {
  24062. visited[objId(property)] = true;
  24063. DFS(property, callback, null, visited);
  24064. }
  24065. else if (propertyType === 'Array' && !visited[objId(property)]) {
  24066. visited[objId(property)] = true;
  24067. DFS(property, callback, i, visited);
  24068. }
  24069. }
  24070. }
  24071. }
  24072. },
  24073. plugins: {},
  24074. highlightAll: function(async, callback) {
  24075. _.highlightAllUnder(document, async, callback);
  24076. },
  24077. highlightAllUnder: function(container, async, callback) {
  24078. var env = {
  24079. callback: callback,
  24080. container: container,
  24081. selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
  24082. };
  24083. _.hooks.run('before-highlightall', env);
  24084. env.elements = Array.prototype.slice.apply(env.container.querySelectorAll(env.selector));
  24085. _.hooks.run('before-all-elements-highlight', env);
  24086. for (var i = 0, element; element = env.elements[i++];) {
  24087. _.highlightElement(element, async === true, env.callback);
  24088. }
  24089. },
  24090. highlightElement: function(element, async, callback) {
  24091. // Find language
  24092. var language = _.util.getLanguage(element);
  24093. var grammar = _.languages[language];
  24094. // Set language on the element, if not present
  24095. element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
  24096. // Set language on the parent, for styling
  24097. var parent = element.parentNode;
  24098. if (parent && parent.nodeName.toLowerCase() === 'pre') {
  24099. parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
  24100. }
  24101. var code = element.textContent;
  24102. var env = {
  24103. element: element,
  24104. language: language,
  24105. grammar: grammar,
  24106. code: code
  24107. };
  24108. function insertHighlightedCode(highlightedCode) {
  24109. env.highlightedCode = highlightedCode;
  24110. _.hooks.run('before-insert', env);
  24111. env.element.innerHTML = env.highlightedCode;
  24112. _.hooks.run('after-highlight', env);
  24113. _.hooks.run('complete', env);
  24114. callback && callback.call(env.element);
  24115. }
  24116. _.hooks.run('before-sanity-check', env);
  24117. if (!env.code) {
  24118. _.hooks.run('complete', env);
  24119. callback && callback.call(env.element);
  24120. return;
  24121. }
  24122. _.hooks.run('before-highlight', env);
  24123. if (!env.grammar) {
  24124. insertHighlightedCode(_.util.encode(env.code));
  24125. return;
  24126. }
  24127. if (async && _self.Worker) {
  24128. var worker = new Worker(_.filename);
  24129. worker.onmessage = function(evt) {
  24130. insertHighlightedCode(evt.data);
  24131. };
  24132. worker.postMessage(JSON.stringify({
  24133. language: env.language,
  24134. code: env.code,
  24135. immediateClose: true
  24136. }));
  24137. }
  24138. else {
  24139. insertHighlightedCode(_.highlight(env.code, env.grammar, env.language));
  24140. }
  24141. },
  24142. highlight: function (text, grammar, language) {
  24143. var env = {
  24144. code: text,
  24145. grammar: grammar,
  24146. language: language
  24147. };
  24148. _.hooks.run('before-tokenize', env);
  24149. env.tokens = _.tokenize(env.code, env.grammar);
  24150. _.hooks.run('after-tokenize', env);
  24151. return Token.stringify(_.util.encode(env.tokens), env.language);
  24152. },
  24153. matchGrammar: function (text, strarr, grammar, index, startPos, oneshot, target) {
  24154. for (var token in grammar) {
  24155. if (!grammar.hasOwnProperty(token) || !grammar[token]) {
  24156. continue;
  24157. }
  24158. var patterns = grammar[token];
  24159. patterns = Array.isArray(patterns) ? patterns : [patterns];
  24160. for (var j = 0; j < patterns.length; ++j) {
  24161. if (target && target == token + ',' + j) {
  24162. return;
  24163. }
  24164. var pattern = patterns[j],
  24165. inside = pattern.inside,
  24166. lookbehind = !!pattern.lookbehind,
  24167. greedy = !!pattern.greedy,
  24168. lookbehindLength = 0,
  24169. alias = pattern.alias;
  24170. if (greedy && !pattern.pattern.global) {
  24171. // Without the global flag, lastIndex won't work
  24172. var flags = pattern.pattern.toString().match(/[imsuy]*$/)[0];
  24173. pattern.pattern = RegExp(pattern.pattern.source, flags + 'g');
  24174. }
  24175. pattern = pattern.pattern || pattern;
  24176. // Don’t cache length as it changes during the loop
  24177. for (var i = index, pos = startPos; i < strarr.length; pos += strarr[i].length, ++i) {
  24178. var str = strarr[i];
  24179. if (strarr.length > text.length) {
  24180. // Something went terribly wrong, ABORT, ABORT!
  24181. return;
  24182. }
  24183. if (str instanceof Token) {
  24184. continue;
  24185. }
  24186. if (greedy && i != strarr.length - 1) {
  24187. pattern.lastIndex = pos;
  24188. var match = pattern.exec(text);
  24189. if (!match) {
  24190. break;
  24191. }
  24192. var from = match.index + (lookbehind && match[1] ? match[1].length : 0),
  24193. to = match.index + match[0].length,
  24194. k = i,
  24195. p = pos;
  24196. for (var len = strarr.length; k < len && (p < to || (!strarr[k].type && !strarr[k - 1].greedy)); ++k) {
  24197. p += strarr[k].length;
  24198. // Move the index i to the element in strarr that is closest to from
  24199. if (from >= p) {
  24200. ++i;
  24201. pos = p;
  24202. }
  24203. }
  24204. // If strarr[i] is a Token, then the match starts inside another Token, which is invalid
  24205. if (strarr[i] instanceof Token) {
  24206. continue;
  24207. }
  24208. // Number of tokens to delete and replace with the new match
  24209. delNum = k - i;
  24210. str = text.slice(pos, p);
  24211. match.index -= pos;
  24212. } else {
  24213. pattern.lastIndex = 0;
  24214. var match = pattern.exec(str),
  24215. delNum = 1;
  24216. }
  24217. if (!match) {
  24218. if (oneshot) {
  24219. break;
  24220. }
  24221. continue;
  24222. }
  24223. if(lookbehind) {
  24224. lookbehindLength = match[1] ? match[1].length : 0;
  24225. }
  24226. var from = match.index + lookbehindLength,
  24227. match = match[0].slice(lookbehindLength),
  24228. to = from + match.length,
  24229. before = str.slice(0, from),
  24230. after = str.slice(to);
  24231. var args = [i, delNum];
  24232. if (before) {
  24233. ++i;
  24234. pos += before.length;
  24235. args.push(before);
  24236. }
  24237. var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias, match, greedy);
  24238. args.push(wrapped);
  24239. if (after) {
  24240. args.push(after);
  24241. }
  24242. Array.prototype.splice.apply(strarr, args);
  24243. if (delNum != 1)
  24244. _.matchGrammar(text, strarr, grammar, i, pos, true, token + ',' + j);
  24245. if (oneshot)
  24246. break;
  24247. }
  24248. }
  24249. }
  24250. },
  24251. tokenize: function(text, grammar) {
  24252. var strarr = [text];
  24253. var rest = grammar.rest;
  24254. if (rest) {
  24255. for (var token in rest) {
  24256. grammar[token] = rest[token];
  24257. }
  24258. delete grammar.rest;
  24259. }
  24260. _.matchGrammar(text, strarr, grammar, 0, 0, false);
  24261. return strarr;
  24262. },
  24263. hooks: {
  24264. all: {},
  24265. add: function (name, callback) {
  24266. var hooks = _.hooks.all;
  24267. hooks[name] = hooks[name] || [];
  24268. hooks[name].push(callback);
  24269. },
  24270. run: function (name, env) {
  24271. var callbacks = _.hooks.all[name];
  24272. if (!callbacks || !callbacks.length) {
  24273. return;
  24274. }
  24275. for (var i=0, callback; callback = callbacks[i++];) {
  24276. callback(env);
  24277. }
  24278. }
  24279. },
  24280. Token: Token
  24281. };
  24282. _self.Prism = _;
  24283. function Token(type, content, alias, matchedStr, greedy) {
  24284. this.type = type;
  24285. this.content = content;
  24286. this.alias = alias;
  24287. // Copy of the full string this token was created from
  24288. this.length = (matchedStr || '').length|0;
  24289. this.greedy = !!greedy;
  24290. }
  24291. Token.stringify = function(o, language) {
  24292. if (typeof o == 'string') {
  24293. return o;
  24294. }
  24295. if (Array.isArray(o)) {
  24296. return o.map(function(element) {
  24297. return Token.stringify(element, language);
  24298. }).join('');
  24299. }
  24300. var env = {
  24301. type: o.type,
  24302. content: Token.stringify(o.content, language),
  24303. tag: 'span',
  24304. classes: ['token', o.type],
  24305. attributes: {},
  24306. language: language
  24307. };
  24308. if (o.alias) {
  24309. var aliases = Array.isArray(o.alias) ? o.alias : [o.alias];
  24310. Array.prototype.push.apply(env.classes, aliases);
  24311. }
  24312. _.hooks.run('wrap', env);
  24313. var attributes = Object.keys(env.attributes).map(function(name) {
  24314. return name + '="' + (env.attributes[name] || '').replace(/"/g, '&quot;') + '"';
  24315. }).join(' ');
  24316. return '<' + env.tag + ' class="' + env.classes.join(' ') + '"' + (attributes ? ' ' + attributes : '') + '>' + env.content + '</' + env.tag + '>';
  24317. };
  24318. if (!_self.document) {
  24319. if (!_self.addEventListener) {
  24320. // in Node.js
  24321. return _;
  24322. }
  24323. if (!_.disableWorkerMessageHandler) {
  24324. // In worker
  24325. _self.addEventListener('message', function (evt) {
  24326. var message = JSON.parse(evt.data),
  24327. lang = message.language,
  24328. code = message.code,
  24329. immediateClose = message.immediateClose;
  24330. _self.postMessage(_.highlight(code, _.languages[lang], lang));
  24331. if (immediateClose) {
  24332. _self.close();
  24333. }
  24334. }, false);
  24335. }
  24336. return _;
  24337. }
  24338. //Get current script and highlight
  24339. var script = _.util.currentScript();
  24340. if (script) {
  24341. _.filename = script.src;
  24342. if (script.hasAttribute('data-manual')) {
  24343. _.manual = true;
  24344. }
  24345. }
  24346. if (!_.manual) {
  24347. function highlightAutomaticallyCallback() {
  24348. if (!_.manual) {
  24349. _.highlightAll();
  24350. }
  24351. }
  24352. // If the document state is "loading", then we'll use DOMContentLoaded.
  24353. // If the document state is "interactive" and the prism.js script is deferred, then we'll also use the
  24354. // DOMContentLoaded event because there might be some plugins or languages which have also been deferred and they
  24355. // might take longer one animation frame to execute which can create a race condition where only some plugins have
  24356. // been loaded when Prism.highlightAll() is executed, depending on how fast resources are loaded.
  24357. // See https://github.com/PrismJS/prism/issues/2102
  24358. var readyState = document.readyState;
  24359. if (readyState === 'loading' || readyState === 'interactive' && script && script.defer) {
  24360. document.addEventListener('DOMContentLoaded', highlightAutomaticallyCallback);
  24361. } else {
  24362. if (window.requestAnimationFrame) {
  24363. window.requestAnimationFrame(highlightAutomaticallyCallback);
  24364. } else {
  24365. window.setTimeout(highlightAutomaticallyCallback, 16);
  24366. }
  24367. }
  24368. }
  24369. return _;
  24370. })(_self);
  24371. if (typeof module !== 'undefined' && module.exports) {
  24372. module.exports = Prism;
  24373. }
  24374. // hack for components to work correctly in node.js
  24375. if (typeof global !== 'undefined') {
  24376. global.Prism = Prism;
  24377. }
  24378. /* **********************************************
  24379. Begin prism-markup.js
  24380. ********************************************** */
  24381. Prism.languages.markup = {
  24382. 'comment': /<!--[\s\S]*?-->/,
  24383. 'prolog': /<\?[\s\S]+?\?>/,
  24384. 'doctype': {
  24385. pattern: /<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:(?!<!--)[^"'\]]|"[^"]*"|'[^']*'|<!--[\s\S]*?-->)*\]\s*)?>/i,
  24386. greedy: true
  24387. },
  24388. 'cdata': /<!\[CDATA\[[\s\S]*?]]>/i,
  24389. 'tag': {
  24390. pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/i,
  24391. greedy: true,
  24392. inside: {
  24393. 'tag': {
  24394. pattern: /^<\/?[^\s>\/]+/i,
  24395. inside: {
  24396. 'punctuation': /^<\/?/,
  24397. 'namespace': /^[^\s>\/:]+:/
  24398. }
  24399. },
  24400. 'attr-value': {
  24401. pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,
  24402. inside: {
  24403. 'punctuation': [
  24404. /^=/,
  24405. {
  24406. pattern: /^(\s*)["']|["']$/,
  24407. lookbehind: true
  24408. }
  24409. ]
  24410. }
  24411. },
  24412. 'punctuation': /\/?>/,
  24413. 'attr-name': {
  24414. pattern: /[^\s>\/]+/,
  24415. inside: {
  24416. 'namespace': /^[^\s>\/:]+:/
  24417. }
  24418. }
  24419. }
  24420. },
  24421. 'entity': /&#?[\da-z]{1,8};/i
  24422. };
  24423. Prism.languages.markup['tag'].inside['attr-value'].inside['entity'] =
  24424. Prism.languages.markup['entity'];
  24425. // Plugin to make entity title show the real entity, idea by Roman Komarov
  24426. Prism.hooks.add('wrap', function(env) {
  24427. if (env.type === 'entity') {
  24428. env.attributes['title'] = env.content.replace(/&amp;/, '&');
  24429. }
  24430. });
  24431. Object.defineProperty(Prism.languages.markup.tag, 'addInlined', {
  24432. /**
  24433. * Adds an inlined language to markup.
  24434. *
  24435. * An example of an inlined language is CSS with `<style>` tags.
  24436. *
  24437. * @param {string} tagName The name of the tag that contains the inlined language. This name will be treated as
  24438. * case insensitive.
  24439. * @param {string} lang The language key.
  24440. * @example
  24441. * addInlined('style', 'css');
  24442. */
  24443. value: function addInlined(tagName, lang) {
  24444. var includedCdataInside = {};
  24445. includedCdataInside['language-' + lang] = {
  24446. pattern: /(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,
  24447. lookbehind: true,
  24448. inside: Prism.languages[lang]
  24449. };
  24450. includedCdataInside['cdata'] = /^<!\[CDATA\[|\]\]>$/i;
  24451. var inside = {
  24452. 'included-cdata': {
  24453. pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i,
  24454. inside: includedCdataInside
  24455. }
  24456. };
  24457. inside['language-' + lang] = {
  24458. pattern: /[\s\S]+/,
  24459. inside: Prism.languages[lang]
  24460. };
  24461. var def = {};
  24462. def[tagName] = {
  24463. pattern: RegExp(/(<__[\s\S]*?>)(?:<!\[CDATA\[[\s\S]*?\]\]>\s*|[\s\S])*?(?=<\/__>)/.source.replace(/__/g, tagName), 'i'),
  24464. lookbehind: true,
  24465. greedy: true,
  24466. inside: inside
  24467. };
  24468. Prism.languages.insertBefore('markup', 'cdata', def);
  24469. }
  24470. });
  24471. Prism.languages.xml = Prism.languages.extend('markup', {});
  24472. Prism.languages.html = Prism.languages.markup;
  24473. Prism.languages.mathml = Prism.languages.markup;
  24474. Prism.languages.svg = Prism.languages.markup;
  24475. /* **********************************************
  24476. Begin prism-css.js
  24477. ********************************************** */
  24478. (function (Prism) {
  24479. var string = /("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;
  24480. Prism.languages.css = {
  24481. 'comment': /\/\*[\s\S]*?\*\//,
  24482. 'atrule': {
  24483. pattern: /@[\w-]+[\s\S]*?(?:;|(?=\s*\{))/,
  24484. inside: {
  24485. 'rule': /@[\w-]+/
  24486. // See rest below
  24487. }
  24488. },
  24489. 'url': {
  24490. pattern: RegExp('url\\((?:' + string.source + '|[^\n\r()]*)\\)', 'i'),
  24491. inside: {
  24492. 'function': /^url/i,
  24493. 'punctuation': /^\(|\)$/
  24494. }
  24495. },
  24496. 'selector': RegExp('[^{}\\s](?:[^{};"\']|' + string.source + ')*?(?=\\s*\\{)'),
  24497. 'string': {
  24498. pattern: string,
  24499. greedy: true
  24500. },
  24501. 'property': /[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,
  24502. 'important': /!important\b/i,
  24503. 'function': /[-a-z0-9]+(?=\()/i,
  24504. 'punctuation': /[(){};:,]/
  24505. };
  24506. Prism.languages.css['atrule'].inside.rest = Prism.languages.css;
  24507. var markup = Prism.languages.markup;
  24508. if (markup) {
  24509. markup.tag.addInlined('style', 'css');
  24510. Prism.languages.insertBefore('inside', 'attr-value', {
  24511. 'style-attr': {
  24512. pattern: /\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,
  24513. inside: {
  24514. 'attr-name': {
  24515. pattern: /^\s*style/i,
  24516. inside: markup.tag.inside
  24517. },
  24518. 'punctuation': /^\s*=\s*['"]|['"]\s*$/,
  24519. 'attr-value': {
  24520. pattern: /.+/i,
  24521. inside: Prism.languages.css
  24522. }
  24523. },
  24524. alias: 'language-css'
  24525. }
  24526. }, markup.tag);
  24527. }
  24528. }(Prism));
  24529. /* **********************************************
  24530. Begin prism-clike.js
  24531. ********************************************** */
  24532. Prism.languages.clike = {
  24533. 'comment': [
  24534. {
  24535. pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,
  24536. lookbehind: true
  24537. },
  24538. {
  24539. pattern: /(^|[^\\:])\/\/.*/,
  24540. lookbehind: true,
  24541. greedy: true
  24542. }
  24543. ],
  24544. 'string': {
  24545. pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
  24546. greedy: true
  24547. },
  24548. 'class-name': {
  24549. pattern: /(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,
  24550. lookbehind: true,
  24551. inside: {
  24552. 'punctuation': /[.\\]/
  24553. }
  24554. },
  24555. 'keyword': /\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,
  24556. 'boolean': /\b(?:true|false)\b/,
  24557. 'function': /\w+(?=\()/,
  24558. 'number': /\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,
  24559. 'operator': /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,
  24560. 'punctuation': /[{}[\];(),.:]/
  24561. };
  24562. /* **********************************************
  24563. Begin prism-javascript.js
  24564. ********************************************** */
  24565. Prism.languages.javascript = Prism.languages.extend('clike', {
  24566. 'class-name': [
  24567. Prism.languages.clike['class-name'],
  24568. {
  24569. pattern: /(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,
  24570. lookbehind: true
  24571. }
  24572. ],
  24573. 'keyword': [
  24574. {
  24575. pattern: /((?:^|})\s*)(?:catch|finally)\b/,
  24576. lookbehind: true
  24577. },
  24578. {
  24579. pattern: /(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,
  24580. lookbehind: true
  24581. },
  24582. ],
  24583. 'number': /\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,
  24584. // Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)
  24585. 'function': /#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,
  24586. 'operator': /--|\+\+|\*\*=?|=>|&&|\|\||[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?[.?]?|[~:]/
  24587. });
  24588. Prism.languages.javascript['class-name'][0].pattern = /(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/;
  24589. Prism.languages.insertBefore('javascript', 'keyword', {
  24590. 'regex': {
  24591. pattern: /((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*[\s\S]*?\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,
  24592. lookbehind: true,
  24593. greedy: true
  24594. },
  24595. // This must be declared before keyword because we use "function" inside the look-forward
  24596. 'function-variable': {
  24597. pattern: /#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,
  24598. alias: 'function'
  24599. },
  24600. 'parameter': [
  24601. {
  24602. pattern: /(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,
  24603. lookbehind: true,
  24604. inside: Prism.languages.javascript
  24605. },
  24606. {
  24607. pattern: /[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,
  24608. inside: Prism.languages.javascript
  24609. },
  24610. {
  24611. pattern: /(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,
  24612. lookbehind: true,
  24613. inside: Prism.languages.javascript
  24614. },
  24615. {
  24616. pattern: /((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,
  24617. lookbehind: true,
  24618. inside: Prism.languages.javascript
  24619. }
  24620. ],
  24621. 'constant': /\b[A-Z](?:[A-Z_]|\dx?)*\b/
  24622. });
  24623. Prism.languages.insertBefore('javascript', 'string', {
  24624. 'template-string': {
  24625. pattern: /`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,
  24626. greedy: true,
  24627. inside: {
  24628. 'template-punctuation': {
  24629. pattern: /^`|`$/,
  24630. alias: 'string'
  24631. },
  24632. 'interpolation': {
  24633. pattern: /((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,
  24634. lookbehind: true,
  24635. inside: {
  24636. 'interpolation-punctuation': {
  24637. pattern: /^\${|}$/,
  24638. alias: 'punctuation'
  24639. },
  24640. rest: Prism.languages.javascript
  24641. }
  24642. },
  24643. 'string': /[\s\S]+/
  24644. }
  24645. }
  24646. });
  24647. if (Prism.languages.markup) {
  24648. Prism.languages.markup.tag.addInlined('script', 'javascript');
  24649. }
  24650. Prism.languages.js = Prism.languages.javascript;
  24651. /* **********************************************
  24652. Begin prism-file-highlight.js
  24653. ********************************************** */
  24654. (function () {
  24655. if (typeof self === 'undefined' || !self.Prism || !self.document || !document.querySelector) {
  24656. return;
  24657. }
  24658. /**
  24659. * @param {Element} [container=document]
  24660. */
  24661. self.Prism.fileHighlight = function(container) {
  24662. container = container || document;
  24663. var Extensions = {
  24664. 'js': 'javascript',
  24665. 'py': 'python',
  24666. 'rb': 'ruby',
  24667. 'ps1': 'powershell',
  24668. 'psm1': 'powershell',
  24669. 'sh': 'bash',
  24670. 'bat': 'batch',
  24671. 'h': 'c',
  24672. 'tex': 'latex'
  24673. };
  24674. Array.prototype.slice.call(container.querySelectorAll('pre[data-src]')).forEach(function (pre) {
  24675. // ignore if already loaded
  24676. if (pre.hasAttribute('data-src-loaded')) {
  24677. return;
  24678. }
  24679. // load current
  24680. var src = pre.getAttribute('data-src');
  24681. var language, parent = pre;
  24682. var lang = /\blang(?:uage)?-([\w-]+)\b/i;
  24683. while (parent && !lang.test(parent.className)) {
  24684. parent = parent.parentNode;
  24685. }
  24686. if (parent) {
  24687. language = (pre.className.match(lang) || [, ''])[1];
  24688. }
  24689. if (!language) {
  24690. var extension = (src.match(/\.(\w+)$/) || [, ''])[1];
  24691. language = Extensions[extension] || extension;
  24692. }
  24693. var code = document.createElement('code');
  24694. code.className = 'language-' + language;
  24695. pre.textContent = '';
  24696. code.textContent = 'Loading…';
  24697. pre.appendChild(code);
  24698. var xhr = new XMLHttpRequest();
  24699. xhr.open('GET', src, true);
  24700. xhr.onreadystatechange = function () {
  24701. if (xhr.readyState == 4) {
  24702. if (xhr.status < 400 && xhr.responseText) {
  24703. code.textContent = xhr.responseText;
  24704. Prism.highlightElement(code);
  24705. // mark as loaded
  24706. pre.setAttribute('data-src-loaded', '');
  24707. }
  24708. else if (xhr.status >= 400) {
  24709. code.textContent = '✖ Error ' + xhr.status + ' while fetching file: ' + xhr.statusText;
  24710. }
  24711. else {
  24712. code.textContent = '✖ Error: File does not exist or is empty';
  24713. }
  24714. }
  24715. };
  24716. xhr.send(null);
  24717. });
  24718. };
  24719. document.addEventListener('DOMContentLoaded', function () {
  24720. // execute inside handler, for dropping Event as argument
  24721. self.Prism.fileHighlight();
  24722. });
  24723. })();
  24724. ;