viewer.js 222 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919
  1. /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
  3. /* Copyright 2012 Mozilla Foundation
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar,
  18. DownloadManager, getFileName, scrollIntoView, getPDFFileNameFromURL,
  19. PDFHistory, Preferences, SidebarView, ViewHistory, PageView,
  20. PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar,
  21. PasswordPrompt, PresentationMode, HandTool, Promise,
  22. DocumentProperties, DocumentOutlineView, DocumentAttachmentsView,
  23. OverlayManager, PDFFindController, PDFFindBar, getVisibleElements,
  24. watchScroll, PDFViewer, PDFRenderingQueue, PresentationModeState,
  25. RenderingStates, DEFAULT_SCALE, UNKNOWN_SCALE,
  26. IGNORE_CURRENT_POSITION_ON_ZOOM: true */
  27. 'use strict';
  28. var DEFAULT_URL =myurl;
  29. var DEFAULT_SCALE_DELTA = 1.1;
  30. var MIN_SCALE = 0.25;
  31. var MAX_SCALE = 10.0;
  32. var VIEW_HISTORY_MEMORY = 20;
  33. var SCALE_SELECT_CONTAINER_PADDING = 8;
  34. var SCALE_SELECT_PADDING = 22;
  35. var PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading';
  36. PDFJS.imageResourcesPath = mybasepath+'webpage/cn/com/lzt/pdf/images/';
  37. PDFJS.workerSrc = mybasepath+ 'webpage/cn/com/lzt/pdf/build/pdf.worker.js';
  38. PDFJS.cMapUrl = mybasepath+ 'webpage/cn/com/lzt/pdf/web/cmaps/';
  39. PDFJS.cMapPacked = true;
  40. var mozL10n = document.mozL10n || document.webL10n;
  41. customTitle=customTitle||'';//标题
  42. var CSS_UNITS = 96.0 / 72.0;
  43. var DEFAULT_SCALE = 'auto';
  44. var UNKNOWN_SCALE = 0;
  45. var MAX_AUTO_SCALE = 1.25;
  46. var SCROLLBAR_PADDING = 40;
  47. var VERTICAL_PADDING = 5;
  48. var DEFAULT_CACHE_SIZE = 10;
  49. // optimised CSS custom property getter/setter
  50. var CustomStyle = (function CustomStyleClosure() {
  51. // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
  52. // animate-css-transforms-firefox-webkit.html
  53. // in some versions of IE9 it is critical that ms appear in this list
  54. // before Moz
  55. var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
  56. var _cache = {};
  57. function CustomStyle() {}
  58. CustomStyle.getProp = function get(propName, element) {
  59. // check cache only when no element is given
  60. if (arguments.length === 1 && typeof _cache[propName] === 'string') {
  61. return _cache[propName];
  62. }
  63. element = element || document.documentElement;
  64. var style = element.style, prefixed, uPropName;
  65. // test standard property first
  66. if (typeof style[propName] === 'string') {
  67. return (_cache[propName] = propName);
  68. }
  69. // capitalize
  70. uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
  71. // test vendor specific properties
  72. for (var i = 0, l = prefixes.length; i < l; i++) {
  73. prefixed = prefixes[i] + uPropName;
  74. if (typeof style[prefixed] === 'string') {
  75. return (_cache[propName] = prefixed);
  76. }
  77. }
  78. //if all fails then set to undefined
  79. return (_cache[propName] = 'undefined');
  80. };
  81. CustomStyle.setProp = function set(propName, element, str) {
  82. var prop = this.getProp(propName);
  83. if (prop !== 'undefined') {
  84. element.style[prop] = str;
  85. }
  86. };
  87. return CustomStyle;
  88. })();
  89. function getFileName(url) {
  90. var anchor = url.indexOf('#');
  91. var query = url.indexOf('?');
  92. var end = Math.min(
  93. anchor > 0 ? anchor : url.length,
  94. query > 0 ? query : url.length);
  95. return url.substring(url.lastIndexOf('/', end) + 1, end);
  96. }
  97. /**
  98. * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
  99. * @return {Object} The object with horizontal (sx) and vertical (sy)
  100. scales. The scaled property is set to false if scaling is
  101. not required, true otherwise.
  102. */
  103. function getOutputScale(ctx) {
  104. var devicePixelRatio = window.devicePixelRatio || 1;
  105. var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
  106. ctx.mozBackingStorePixelRatio ||
  107. ctx.msBackingStorePixelRatio ||
  108. ctx.oBackingStorePixelRatio ||
  109. ctx.backingStorePixelRatio || 1;
  110. var pixelRatio = devicePixelRatio / backingStoreRatio;
  111. return {
  112. sx: pixelRatio,
  113. sy: pixelRatio,
  114. scaled: pixelRatio !== 1
  115. };
  116. }
  117. /**
  118. * Scrolls specified element into view of its parent.
  119. * element {Object} The element to be visible.
  120. * spot {Object} An object with optional top and left properties,
  121. * specifying the offset from the top left edge.
  122. */
  123. function scrollIntoView(element, spot) {
  124. // Assuming offsetParent is available (it's not available when viewer is in
  125. // hidden iframe or object). We have to scroll: if the offsetParent is not set
  126. // producing the error. See also animationStartedClosure.
  127. var parent = element.offsetParent;
  128. var offsetY = element.offsetTop + element.clientTop;
  129. var offsetX = element.offsetLeft + element.clientLeft;
  130. if (!parent) {
  131. console.error('offsetParent is not set -- cannot scroll');
  132. return;
  133. }
  134. while (parent.clientHeight === parent.scrollHeight) {
  135. if (parent.dataset._scaleY) {
  136. offsetY /= parent.dataset._scaleY;
  137. offsetX /= parent.dataset._scaleX;
  138. }
  139. offsetY += parent.offsetTop;
  140. offsetX += parent.offsetLeft;
  141. parent = parent.offsetParent;
  142. if (!parent) {
  143. return; // no need to scroll
  144. }
  145. }
  146. if (spot) {
  147. if (spot.top !== undefined) {
  148. offsetY += spot.top;
  149. }
  150. if (spot.left !== undefined) {
  151. offsetX += spot.left;
  152. parent.scrollLeft = offsetX;
  153. }
  154. }
  155. parent.scrollTop = offsetY;
  156. }
  157. /**
  158. * Helper function to start monitoring the scroll event and converting them into
  159. * PDF.js friendly one: with scroll debounce and scroll direction.
  160. */
  161. function watchScroll(viewAreaElement, callback) {
  162. var debounceScroll = function debounceScroll(evt) {
  163. if (rAF) {
  164. return;
  165. }
  166. // schedule an invocation of scroll for next animation frame.
  167. rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
  168. rAF = null;
  169. var currentY = viewAreaElement.scrollTop;
  170. var lastY = state.lastY;
  171. if (currentY > lastY) {
  172. state.down = true;
  173. } else if (currentY < lastY) {
  174. state.down = false;
  175. }
  176. state.lastY = currentY;
  177. // else do nothing and use previous value
  178. callback(state);
  179. });
  180. };
  181. var state = {
  182. down: true,
  183. lastY: viewAreaElement.scrollTop,
  184. _eventHandler: debounceScroll
  185. };
  186. var rAF = null;
  187. viewAreaElement.addEventListener('scroll', debounceScroll, true);
  188. return state;
  189. }
  190. /**
  191. * Generic helper to find out what elements are visible within a scroll pane.
  192. */
  193. function getVisibleElements(scrollEl, views, sortByVisibility) {
  194. var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
  195. var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
  196. var visible = [], view;
  197. var currentHeight, viewHeight, hiddenHeight, percentHeight;
  198. var currentWidth, viewWidth;
  199. for (var i = 0, ii = views.length; i < ii; ++i) {
  200. view = views[i];
  201. currentHeight = view.el.offsetTop + view.el.clientTop;
  202. viewHeight = view.el.clientHeight;
  203. if ((currentHeight + viewHeight) < top) {
  204. continue;
  205. }
  206. if (currentHeight > bottom) {
  207. break;
  208. }
  209. currentWidth = view.el.offsetLeft + view.el.clientLeft;
  210. viewWidth = view.el.clientWidth;
  211. if ((currentWidth + viewWidth) < left || currentWidth > right) {
  212. continue;
  213. }
  214. hiddenHeight = Math.max(0, top - currentHeight) +
  215. Math.max(0, currentHeight + viewHeight - bottom);
  216. percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
  217. visible.push({ id: view.id, x: currentWidth, y: currentHeight,
  218. view: view, percent: percentHeight });
  219. }
  220. var first = visible[0];
  221. var last = visible[visible.length - 1];
  222. if (sortByVisibility) {
  223. visible.sort(function(a, b) {
  224. var pc = a.percent - b.percent;
  225. if (Math.abs(pc) > 0.001) {
  226. return -pc;
  227. }
  228. return a.id - b.id; // ensure stability
  229. });
  230. }
  231. return {first: first, last: last, views: visible};
  232. }
  233. /**
  234. * Event handler to suppress context menu.
  235. */
  236. function noContextMenuHandler(e) {
  237. e.preventDefault();
  238. }
  239. /**
  240. * Returns the filename or guessed filename from the url (see issue 3455).
  241. * url {String} The original PDF location.
  242. * @return {String} Guessed PDF file name.
  243. */
  244. function getPDFFileNameFromURL(url) {
  245. var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
  246. // SCHEME HOST 1.PATH 2.QUERY 3.REF
  247. // Pattern to get last matching NAME.pdf
  248. var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
  249. var splitURI = reURI.exec(url);
  250. var suggestedFilename = reFilename.exec(splitURI[1]) ||
  251. reFilename.exec(splitURI[2]) ||
  252. reFilename.exec(splitURI[3]);
  253. if (suggestedFilename) {
  254. suggestedFilename = suggestedFilename[0];
  255. if (suggestedFilename.indexOf('%') !== -1) {
  256. // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
  257. try {
  258. suggestedFilename =
  259. reFilename.exec(decodeURIComponent(suggestedFilename))[0];
  260. } catch(e) { // Possible (extremely rare) errors:
  261. // URIError "Malformed URI", e.g. for "%AA.pdf"
  262. // TypeError "null has no properties", e.g. for "%2F.pdf"
  263. }
  264. }
  265. }
  266. return suggestedFilename || 'document.pdf';
  267. }
  268. var ProgressBar = (function ProgressBarClosure() {
  269. function clamp(v, min, max) {
  270. return Math.min(Math.max(v, min), max);
  271. }
  272. function ProgressBar(id, opts) {
  273. // Fetch the sub-elements for later.
  274. this.div = document.querySelector(id + ' .progress');
  275. // Get the loading bar element, so it can be resized to fit the viewer.
  276. this.bar = this.div.parentNode;
  277. // Get options, with sensible defaults.
  278. this.height = opts.height || 100;
  279. this.width = opts.width || 100;
  280. this.units = opts.units || '%';
  281. // Initialize heights.
  282. this.div.style.height = this.height + this.units;
  283. this.percent = 0;
  284. }
  285. ProgressBar.prototype = {
  286. updateBar: function ProgressBar_updateBar() {
  287. if (this._indeterminate) {
  288. this.div.classList.add('indeterminate');
  289. this.div.style.width = this.width + this.units;
  290. return;
  291. }
  292. this.div.classList.remove('indeterminate');
  293. var progressSize = this.width * this._percent / 100;
  294. this.div.style.width = progressSize + this.units;
  295. },
  296. get percent() {
  297. return this._percent;
  298. },
  299. set percent(val) {
  300. this._indeterminate = isNaN(val);
  301. this._percent = clamp(val, 0, 100);
  302. this.updateBar();
  303. },
  304. setWidth: function ProgressBar_setWidth(viewer) {
  305. if (viewer) {
  306. var container = viewer.parentNode;
  307. var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
  308. if (scrollbarWidth > 0) {
  309. this.bar.setAttribute('style', 'width: calc(100% - ' +
  310. scrollbarWidth + 'px);');
  311. }
  312. }
  313. },
  314. hide: function ProgressBar_hide() {
  315. this.bar.classList.add('hidden');
  316. this.bar.removeAttribute('style');
  317. }
  318. };
  319. return ProgressBar;
  320. })();
  321. var Cache = function cacheCache(size) {
  322. var data = [];
  323. this.push = function cachePush(view) {
  324. var i = data.indexOf(view);
  325. if (i >= 0) {
  326. data.splice(i, 1);
  327. }
  328. data.push(view);
  329. if (data.length > size) {
  330. data.shift().destroy();
  331. }
  332. };
  333. this.resize = function (newSize) {
  334. size = newSize;
  335. while (data.length > size) {
  336. data.shift().destroy();
  337. }
  338. };
  339. };
  340. var DEFAULT_PREFERENCES = {
  341. showPreviousViewOnLoad: true,
  342. defaultZoomValue: '',
  343. sidebarViewOnLoad: 0,
  344. enableHandToolOnLoad: false,
  345. enableWebGL: false,
  346. pdfBugEnabled: false,
  347. disableRange: false,
  348. disableStream: false,
  349. disableAutoFetch: false,
  350. disableFontFace: false,
  351. disableTextLayer: false,
  352. useOnlyCssZoom: false
  353. };
  354. var SidebarView = {
  355. NONE: 0,
  356. THUMBS: 1,
  357. OUTLINE: 2,
  358. ATTACHMENTS: 3
  359. };
  360. /**
  361. * Preferences - Utility for storing persistent settings.
  362. * Used for settings that should be applied to all opened documents,
  363. * or every time the viewer is loaded.
  364. */
  365. var Preferences = {
  366. prefs: Object.create(DEFAULT_PREFERENCES),
  367. isInitializedPromiseResolved: false,
  368. initializedPromise: null,
  369. /**
  370. * Initialize and fetch the current preference values from storage.
  371. * @return {Promise} A promise that is resolved when the preferences
  372. * have been initialized.
  373. */
  374. initialize: function preferencesInitialize() {
  375. return this.initializedPromise =
  376. this._readFromStorage(DEFAULT_PREFERENCES).then(function(prefObj) {
  377. this.isInitializedPromiseResolved = true;
  378. if (prefObj) {
  379. this.prefs = prefObj;
  380. }
  381. }.bind(this));
  382. },
  383. /**
  384. * Stub function for writing preferences to storage.
  385. * NOTE: This should be overridden by a build-specific function defined below.
  386. * @param {Object} prefObj The preferences that should be written to storage.
  387. * @return {Promise} A promise that is resolved when the preference values
  388. * have been written.
  389. */
  390. _writeToStorage: function preferences_writeToStorage(prefObj) {
  391. return Promise.resolve();
  392. },
  393. /**
  394. * Stub function for reading preferences from storage.
  395. * NOTE: This should be overridden by a build-specific function defined below.
  396. * @param {Object} prefObj The preferences that should be read from storage.
  397. * @return {Promise} A promise that is resolved with an {Object} containing
  398. * the preferences that have been read.
  399. */
  400. _readFromStorage: function preferences_readFromStorage(prefObj) {
  401. return Promise.resolve();
  402. },
  403. /**
  404. * Reset the preferences to their default values and update storage.
  405. * @return {Promise} A promise that is resolved when the preference values
  406. * have been reset.
  407. */
  408. reset: function preferencesReset() {
  409. return this.initializedPromise.then(function() {
  410. this.prefs = Object.create(DEFAULT_PREFERENCES);
  411. return this._writeToStorage(DEFAULT_PREFERENCES);
  412. }.bind(this));
  413. },
  414. /**
  415. * Replace the current preference values with the ones from storage.
  416. * @return {Promise} A promise that is resolved when the preference values
  417. * have been updated.
  418. */
  419. reload: function preferencesReload() {
  420. return this.initializedPromise.then(function () {
  421. this._readFromStorage(DEFAULT_PREFERENCES).then(function(prefObj) {
  422. if (prefObj) {
  423. this.prefs = prefObj;
  424. }
  425. }.bind(this));
  426. }.bind(this));
  427. },
  428. /**
  429. * Set the value of a preference.
  430. * @param {string} name The name of the preference that should be changed.
  431. * @param {boolean|number|string} value The new value of the preference.
  432. * @return {Promise} A promise that is resolved when the value has been set,
  433. * provided that the preference exists and the types match.
  434. */
  435. set: function preferencesSet(name, value) {
  436. return this.initializedPromise.then(function () {
  437. if (DEFAULT_PREFERENCES[name] === undefined) {
  438. throw new Error('preferencesSet: \'' + name + '\' is undefined.');
  439. } else if (value === undefined) {
  440. throw new Error('preferencesSet: no value is specified.');
  441. }
  442. var valueType = typeof value;
  443. var defaultType = typeof DEFAULT_PREFERENCES[name];
  444. if (valueType !== defaultType) {
  445. if (valueType === 'number' && defaultType === 'string') {
  446. value = value.toString();
  447. } else {
  448. throw new Error('Preferences_set: \'' + value + '\' is a \"' +
  449. valueType + '\", expected \"' + defaultType + '\".');
  450. }
  451. } else {
  452. if (valueType === 'number' && (value | 0) !== value) {
  453. throw new Error('Preferences_set: \'' + value +
  454. '\' must be an \"integer\".');
  455. }
  456. }
  457. this.prefs[name] = value;
  458. return this._writeToStorage(this.prefs);
  459. }.bind(this));
  460. },
  461. /**
  462. * Get the value of a preference.
  463. * @param {string} name The name of the preference whose value is requested.
  464. * @return {Promise} A promise that is resolved with a {boolean|number|string}
  465. * containing the value of the preference.
  466. */
  467. get: function preferencesGet(name) {
  468. return this.initializedPromise.then(function () {
  469. var defaultValue = DEFAULT_PREFERENCES[name];
  470. if (defaultValue === undefined) {
  471. throw new Error('preferencesGet: \'' + name + '\' is undefined.');
  472. } else {
  473. var prefValue = this.prefs[name];
  474. if (prefValue !== undefined) {
  475. return prefValue;
  476. }
  477. }
  478. return defaultValue;
  479. }.bind(this));
  480. }
  481. };
  482. Preferences._writeToStorage = function (prefObj) {
  483. return new Promise(function (resolve) {
  484. localStorage.setItem('pdfjs.preferences', JSON.stringify(prefObj));
  485. resolve();
  486. });
  487. };
  488. Preferences._readFromStorage = function (prefObj) {
  489. return new Promise(function (resolve) {
  490. var readPrefs = JSON.parse(localStorage.getItem('pdfjs.preferences'));
  491. resolve(readPrefs);
  492. });
  493. };
  494. (function mozPrintCallbackPolyfillClosure() {
  495. if ('mozPrintCallback' in document.createElement('canvas')) {
  496. return;
  497. }
  498. // Cause positive result on feature-detection:
  499. HTMLCanvasElement.prototype.mozPrintCallback = undefined;
  500. var canvases; // During print task: non-live NodeList of <canvas> elements
  501. var index; // Index of <canvas> element that is being processed
  502. var print = window.print;
  503. window.print = function print() {
  504. if (canvases) {
  505. console.warn('Ignored window.print() because of a pending print job.');
  506. return;
  507. }
  508. try {
  509. dispatchEvent('beforeprint');
  510. } finally {
  511. canvases = document.querySelectorAll('canvas');
  512. index = -1;
  513. next();
  514. }
  515. };
  516. function dispatchEvent(eventType) {
  517. var event = document.createEvent('CustomEvent');
  518. event.initCustomEvent(eventType, false, false, 'custom');
  519. window.dispatchEvent(event);
  520. }
  521. function next() {
  522. if (!canvases) {
  523. return; // Print task cancelled by user (state reset in abort())
  524. }
  525. renderProgress();
  526. if (++index < canvases.length) {
  527. var canvas = canvases[index];
  528. if (typeof canvas.mozPrintCallback === 'function') {
  529. canvas.mozPrintCallback({
  530. context: canvas.getContext('2d'),
  531. abort: abort,
  532. done: next
  533. });
  534. } else {
  535. next();
  536. }
  537. } else {
  538. renderProgress();
  539. print.call(window);
  540. setTimeout(abort, 20); // Tidy-up
  541. }
  542. }
  543. function abort() {
  544. if (canvases) {
  545. canvases = null;
  546. renderProgress();
  547. dispatchEvent('afterprint');
  548. }
  549. }
  550. function renderProgress() {
  551. var progressContainer = document.getElementById('mozPrintCallback-shim');
  552. if (canvases) {
  553. var progress = Math.round(100 * index / canvases.length);
  554. var progressBar = progressContainer.querySelector('progress');
  555. var progressPerc = progressContainer.querySelector('.relative-progress');
  556. progressBar.value = progress;
  557. progressPerc.textContent = progress + '%';
  558. progressContainer.removeAttribute('hidden');
  559. progressContainer.onclick = abort;
  560. } else {
  561. progressContainer.setAttribute('hidden', '');
  562. }
  563. }
  564. var hasAttachEvent = !!document.attachEvent;
  565. window.addEventListener('keydown', function(event) {
  566. // Intercept Cmd/Ctrl + P in all browsers.
  567. // Also intercept Cmd/Ctrl + Shift + P in Chrome and Opera
  568. if (event.keyCode === 80/*P*/ && (event.ctrlKey || event.metaKey) &&
  569. !event.altKey && (!event.shiftKey || window.chrome || window.opera)) {
  570. window.print();
  571. if (hasAttachEvent) {
  572. // Only attachEvent can cancel Ctrl + P dialog in IE <=10
  573. // attachEvent is gone in IE11, so the dialog will re-appear in IE11.
  574. return;
  575. }
  576. event.preventDefault();
  577. if (event.stopImmediatePropagation) {
  578. event.stopImmediatePropagation();
  579. } else {
  580. event.stopPropagation();
  581. }
  582. return;
  583. }
  584. if (event.keyCode === 27 && canvases) { // Esc
  585. abort();
  586. }
  587. }, true);
  588. if (hasAttachEvent) {
  589. document.attachEvent('onkeydown', function(event) {
  590. event = event || window.event;
  591. if (event.keyCode === 80/*P*/ && event.ctrlKey) {
  592. event.keyCode = 0;
  593. return false;
  594. }
  595. });
  596. }
  597. if ('onbeforeprint' in window) {
  598. // Do not propagate before/afterprint events when they are not triggered
  599. // from within this polyfill. (FF/IE).
  600. var stopPropagationIfNeeded = function(event) {
  601. if (event.detail !== 'custom' && event.stopImmediatePropagation) {
  602. event.stopImmediatePropagation();
  603. }
  604. };
  605. window.addEventListener('beforeprint', stopPropagationIfNeeded, false);
  606. window.addEventListener('afterprint', stopPropagationIfNeeded, false);
  607. }
  608. })();
  609. var DownloadManager = (function DownloadManagerClosure() {
  610. function download(blobUrl, filename) {
  611. var a = document.createElement('a');
  612. if (a.click) {
  613. // Use a.click() if available. Otherwise, Chrome might show
  614. // "Unsafe JavaScript attempt to initiate a navigation change
  615. // for frame with URL" and not open the PDF at all.
  616. // Supported by (not mentioned = untested):
  617. // - Firefox 6 - 19 (4- does not support a.click, 5 ignores a.click)
  618. // - Chrome 19 - 26 (18- does not support a.click)
  619. // - Opera 9 - 12.15
  620. // - Internet Explorer 6 - 10
  621. // - Safari 6 (5.1- does not support a.click)
  622. a.href = blobUrl;
  623. a.target = '_parent';
  624. // Use a.download if available. This increases the likelihood that
  625. // the file is downloaded instead of opened by another PDF plugin.
  626. if ('download' in a) {
  627. a.download = filename;
  628. }
  629. // <a> must be in the document for IE and recent Firefox versions.
  630. // (otherwise .click() is ignored)
  631. (document.body || document.documentElement).appendChild(a);
  632. a.click();
  633. a.parentNode.removeChild(a);
  634. } else {
  635. if (window.top === window &&
  636. blobUrl.split('#')[0] === window.location.href.split('#')[0]) {
  637. // If _parent == self, then opening an identical URL with different
  638. // location hash will only cause a navigation, not a download.
  639. var padCharacter = blobUrl.indexOf('?') === -1 ? '?' : '&';
  640. blobUrl = blobUrl.replace(/#|$/, padCharacter + '$&');
  641. }
  642. window.open(blobUrl, '_parent');
  643. }
  644. }
  645. function DownloadManager() {}
  646. DownloadManager.prototype = {
  647. downloadUrl: function DownloadManager_downloadUrl(url, filename) {
  648. if (!PDFJS.isValidUrl(url, true)) {
  649. return; // restricted/invalid URL
  650. }
  651. download(url + '#pdfjs.action=download', filename);
  652. },
  653. downloadData: function DownloadManager_downloadData(data, filename,
  654. contentType) {
  655. if (navigator.msSaveBlob) { // IE10 and above
  656. return navigator.msSaveBlob(new Blob([data], { type: contentType }),
  657. filename);
  658. }
  659. var blobUrl = PDFJS.createObjectURL(data, contentType);
  660. download(blobUrl, filename);
  661. },
  662. download: function DownloadManager_download(blob, url, filename) {
  663. if (!URL) {
  664. // URL.createObjectURL is not supported
  665. this.downloadUrl(url, filename);
  666. return;
  667. }
  668. if (navigator.msSaveBlob) {
  669. // IE10 / IE11
  670. if (!navigator.msSaveBlob(blob, filename)) {
  671. this.downloadUrl(url, filename);
  672. }
  673. return;
  674. }
  675. var blobUrl = URL.createObjectURL(blob);
  676. download(blobUrl, filename);
  677. }
  678. };
  679. return DownloadManager;
  680. })();
  681. /**
  682. * View History - This is a utility for saving various view parameters for
  683. * recently opened files.
  684. *
  685. * The way that the view parameters are stored depends on how PDF.js is built,
  686. * for 'node make <flag>' the following cases exist:
  687. * - FIREFOX or MOZCENTRAL - uses sessionStorage.
  688. * - B2G - uses asyncStorage.
  689. * - GENERIC or CHROME - uses localStorage, if it is available.
  690. */
  691. var ViewHistory = (function ViewHistoryClosure() {
  692. function ViewHistory(fingerprint) {
  693. this.fingerprint = fingerprint;
  694. this.isInitializedPromiseResolved = false;
  695. this.initializedPromise =
  696. this._readFromStorage().then(function (databaseStr) {
  697. this.isInitializedPromiseResolved = true;
  698. var database = JSON.parse(databaseStr || '{}');
  699. if (!('files' in database)) {
  700. database.files = [];
  701. }
  702. if (database.files.length >= VIEW_HISTORY_MEMORY) {
  703. database.files.shift();
  704. }
  705. var index;
  706. for (var i = 0, length = database.files.length; i < length; i++) {
  707. var branch = database.files[i];
  708. if (branch.fingerprint === this.fingerprint) {
  709. index = i;
  710. break;
  711. }
  712. }
  713. if (typeof index !== 'number') {
  714. index = database.files.push({fingerprint: this.fingerprint}) - 1;
  715. }
  716. this.file = database.files[index];
  717. this.database = database;
  718. }.bind(this));
  719. }
  720. ViewHistory.prototype = {
  721. _writeToStorage: function ViewHistory_writeToStorage() {
  722. return new Promise(function (resolve) {
  723. var databaseStr = JSON.stringify(this.database);
  724. localStorage.setItem('database', databaseStr);
  725. resolve();
  726. }.bind(this));
  727. },
  728. _readFromStorage: function ViewHistory_readFromStorage() {
  729. return new Promise(function (resolve) {
  730. resolve(localStorage.getItem('database'));
  731. });
  732. },
  733. set: function ViewHistory_set(name, val) {
  734. if (!this.isInitializedPromiseResolved) {
  735. return;
  736. }
  737. this.file[name] = val;
  738. return this._writeToStorage();
  739. },
  740. setMultiple: function ViewHistory_setMultiple(properties) {
  741. if (!this.isInitializedPromiseResolved) {
  742. return;
  743. }
  744. for (var name in properties) {
  745. this.file[name] = properties[name];
  746. }
  747. return this._writeToStorage();
  748. },
  749. get: function ViewHistory_get(name, defaultValue) {
  750. if (!this.isInitializedPromiseResolved) {
  751. return defaultValue;
  752. }
  753. return this.file[name] || defaultValue;
  754. }
  755. };
  756. return ViewHistory;
  757. })();
  758. /**
  759. * Creates a "search bar" given a set of DOM elements that act as controls
  760. * for searching or for setting search preferences in the UI. This object
  761. * also sets up the appropriate events for the controls. Actual searching
  762. * is done by PDFFindController.
  763. */
  764. var PDFFindBar = (function PDFFindBarClosure() {
  765. function PDFFindBar(options) {
  766. this.opened = false;
  767. this.bar = options.bar || null;
  768. this.toggleButton = options.toggleButton || null;
  769. this.findField = options.findField || null;
  770. this.highlightAll = options.highlightAllCheckbox || null;
  771. this.caseSensitive = options.caseSensitiveCheckbox || null;
  772. this.findMsg = options.findMsg || null;
  773. this.findStatusIcon = options.findStatusIcon || null;
  774. this.findPreviousButton = options.findPreviousButton || null;
  775. this.findNextButton = options.findNextButton || null;
  776. this.findController = options.findController || null;
  777. if (this.findController === null) {
  778. throw new Error('PDFFindBar cannot be used without a ' +
  779. 'PDFFindController instance.');
  780. }
  781. // Add event listeners to the DOM elements.
  782. var self = this;
  783. this.toggleButton.addEventListener('click', function() {
  784. self.toggle();
  785. });
  786. this.findField.addEventListener('input', function() {
  787. self.dispatchEvent('');
  788. });
  789. this.bar.addEventListener('keydown', function(evt) {
  790. switch (evt.keyCode) {
  791. case 13: // Enter
  792. if (evt.target === self.findField) {
  793. self.dispatchEvent('again', evt.shiftKey);
  794. }
  795. break;
  796. case 27: // Escape
  797. self.close();
  798. break;
  799. }
  800. });
  801. this.findPreviousButton.addEventListener('click', function() {
  802. self.dispatchEvent('again', true);
  803. });
  804. this.findNextButton.addEventListener('click', function() {
  805. self.dispatchEvent('again', false);
  806. });
  807. this.highlightAll.addEventListener('click', function() {
  808. self.dispatchEvent('highlightallchange');
  809. });
  810. this.caseSensitive.addEventListener('click', function() {
  811. self.dispatchEvent('casesensitivitychange');
  812. });
  813. }
  814. PDFFindBar.prototype = {
  815. dispatchEvent: function PDFFindBar_dispatchEvent(type, findPrev) {
  816. var event = document.createEvent('CustomEvent');
  817. event.initCustomEvent('find' + type, true, true, {
  818. query: this.findField.value,
  819. caseSensitive: this.caseSensitive.checked,
  820. highlightAll: this.highlightAll.checked,
  821. findPrevious: findPrev
  822. });
  823. return window.dispatchEvent(event);
  824. },
  825. updateUIState: function PDFFindBar_updateUIState(state, previous) {
  826. var notFound = false;
  827. var findMsg = '';
  828. var status = '';
  829. switch (state) {
  830. case FindStates.FIND_FOUND:
  831. break;
  832. case FindStates.FIND_PENDING:
  833. status = 'pending';
  834. break;
  835. case FindStates.FIND_NOTFOUND:
  836. findMsg = mozL10n.get('find_not_found', null, 'Phrase not found');
  837. notFound = true;
  838. break;
  839. case FindStates.FIND_WRAPPED:
  840. if (previous) {
  841. findMsg = mozL10n.get('find_reached_top', null,
  842. 'Reached top of document, continued from bottom');
  843. } else {
  844. findMsg = mozL10n.get('find_reached_bottom', null,
  845. 'Reached end of document, continued from top');
  846. }
  847. break;
  848. }
  849. if (notFound) {
  850. this.findField.classList.add('notFound');
  851. } else {
  852. this.findField.classList.remove('notFound');
  853. }
  854. this.findField.setAttribute('data-status', status);
  855. this.findMsg.textContent = findMsg;
  856. },
  857. open: function PDFFindBar_open() {
  858. if (!this.opened) {
  859. this.opened = true;
  860. this.toggleButton.classList.add('toggled');
  861. this.bar.classList.remove('hidden');
  862. }
  863. this.findField.select();
  864. this.findField.focus();
  865. },
  866. close: function PDFFindBar_close() {
  867. if (!this.opened) {
  868. return;
  869. }
  870. this.opened = false;
  871. this.toggleButton.classList.remove('toggled');
  872. this.bar.classList.add('hidden');
  873. this.findController.active = false;
  874. },
  875. toggle: function PDFFindBar_toggle() {
  876. if (this.opened) {
  877. this.close();
  878. } else {
  879. this.open();
  880. }
  881. }
  882. };
  883. return PDFFindBar;
  884. })();
  885. var FindStates = {
  886. FIND_FOUND: 0,
  887. FIND_NOTFOUND: 1,
  888. FIND_WRAPPED: 2,
  889. FIND_PENDING: 3
  890. };
  891. /**
  892. * Provides "search" or "find" functionality for the PDF.
  893. * This object actually performs the search for a given string.
  894. */
  895. var PDFFindController = (function PDFFindControllerClosure() {
  896. function PDFFindController(options) {
  897. this.startedTextExtraction = false;
  898. this.extractTextPromises = [];
  899. this.pendingFindMatches = {};
  900. this.active = false; // If active, find results will be highlighted.
  901. this.pageContents = []; // Stores the text for each page.
  902. this.pageMatches = [];
  903. this.selected = { // Currently selected match.
  904. pageIdx: -1,
  905. matchIdx: -1
  906. };
  907. this.offset = { // Where the find algorithm currently is in the document.
  908. pageIdx: null,
  909. matchIdx: null
  910. };
  911. this.pagesToSearch = null;
  912. this.resumePageIdx = null;
  913. this.state = null;
  914. this.dirtyMatch = false;
  915. this.findTimeout = null;
  916. this.pdfViewer = options.pdfViewer || null;
  917. this.integratedFind = options.integratedFind || false;
  918. this.charactersToNormalize = {
  919. '\u2018': '\'', // Left single quotation mark
  920. '\u2019': '\'', // Right single quotation mark
  921. '\u201A': '\'', // Single low-9 quotation mark
  922. '\u201B': '\'', // Single high-reversed-9 quotation mark
  923. '\u201C': '"', // Left double quotation mark
  924. '\u201D': '"', // Right double quotation mark
  925. '\u201E': '"', // Double low-9 quotation mark
  926. '\u201F': '"', // Double high-reversed-9 quotation mark
  927. '\u00BC': '1/4', // Vulgar fraction one quarter
  928. '\u00BD': '1/2', // Vulgar fraction one half
  929. '\u00BE': '3/4' // Vulgar fraction three quarters
  930. };
  931. this.findBar = options.findBar || null;
  932. // Compile the regular expression for text normalization once
  933. var replace = Object.keys(this.charactersToNormalize).join('');
  934. this.normalizationRegex = new RegExp('[' + replace + ']', 'g');
  935. var events = [
  936. 'find',
  937. 'findagain',
  938. 'findhighlightallchange',
  939. 'findcasesensitivitychange'
  940. ];
  941. this.firstPagePromise = new Promise(function (resolve) {
  942. this.resolveFirstPage = resolve;
  943. }.bind(this));
  944. this.handleEvent = this.handleEvent.bind(this);
  945. for (var i = 0, len = events.length; i < len; i++) {
  946. window.addEventListener(events[i], this.handleEvent);
  947. }
  948. }
  949. PDFFindController.prototype = {
  950. setFindBar: function PDFFindController_setFindBar(findBar) {
  951. this.findBar = findBar;
  952. },
  953. reset: function PDFFindController_reset() {
  954. this.startedTextExtraction = false;
  955. this.extractTextPromises = [];
  956. this.active = false;
  957. },
  958. normalize: function PDFFindController_normalize(text) {
  959. var self = this;
  960. return text.replace(this.normalizationRegex, function (ch) {
  961. return self.charactersToNormalize[ch];
  962. });
  963. },
  964. calcFindMatch: function PDFFindController_calcFindMatch(pageIndex) {
  965. var pageContent = this.normalize(this.pageContents[pageIndex]);
  966. var query = this.normalize(this.state.query);
  967. var caseSensitive = this.state.caseSensitive;
  968. var queryLen = query.length;
  969. if (queryLen === 0) {
  970. return; // Do nothing: the matches should be wiped out already.
  971. }
  972. if (!caseSensitive) {
  973. pageContent = pageContent.toLowerCase();
  974. query = query.toLowerCase();
  975. }
  976. var matches = [];
  977. var matchIdx = -queryLen;
  978. while (true) {
  979. matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
  980. if (matchIdx === -1) {
  981. break;
  982. }
  983. matches.push(matchIdx);
  984. }
  985. this.pageMatches[pageIndex] = matches;
  986. this.updatePage(pageIndex);
  987. if (this.resumePageIdx === pageIndex) {
  988. this.resumePageIdx = null;
  989. this.nextPageMatch();
  990. }
  991. },
  992. extractText: function PDFFindController_extractText() {
  993. if (this.startedTextExtraction) {
  994. return;
  995. }
  996. this.startedTextExtraction = true;
  997. this.pageContents = [];
  998. var extractTextPromisesResolves = [];
  999. var numPages = this.pdfViewer.pagesCount;
  1000. for (var i = 0; i < numPages; i++) {
  1001. this.extractTextPromises.push(new Promise(function (resolve) {
  1002. extractTextPromisesResolves.push(resolve);
  1003. }));
  1004. }
  1005. var self = this;
  1006. function extractPageText(pageIndex) {
  1007. self.pdfViewer.getPageTextContent(pageIndex).then(
  1008. function textContentResolved(textContent) {
  1009. var textItems = textContent.items;
  1010. var str = [];
  1011. for (var i = 0, len = textItems.length; i < len; i++) {
  1012. str.push(textItems[i].str);
  1013. }
  1014. // Store the pageContent as a string.
  1015. self.pageContents.push(str.join(''));
  1016. extractTextPromisesResolves[pageIndex](pageIndex);
  1017. if ((pageIndex + 1) < self.pdfViewer.pagesCount) {
  1018. extractPageText(pageIndex + 1);
  1019. }
  1020. }
  1021. );
  1022. }
  1023. extractPageText(0);
  1024. },
  1025. handleEvent: function PDFFindController_handleEvent(e) {
  1026. if (this.state === null || e.type !== 'findagain') {
  1027. this.dirtyMatch = true;
  1028. }
  1029. this.state = e.detail;
  1030. this.updateUIState(FindStates.FIND_PENDING);
  1031. this.firstPagePromise.then(function() {
  1032. this.extractText();
  1033. clearTimeout(this.findTimeout);
  1034. if (e.type === 'find') {
  1035. // Only trigger the find action after 250ms of silence.
  1036. this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
  1037. } else {
  1038. this.nextMatch();
  1039. }
  1040. }.bind(this));
  1041. },
  1042. updatePage: function PDFFindController_updatePage(index) {
  1043. var page = this.pdfViewer.getPageView(index);
  1044. if (this.selected.pageIdx === index) {
  1045. // If the page is selected, scroll the page into view, which triggers
  1046. // rendering the page, which adds the textLayer. Once the textLayer is
  1047. // build, it will scroll onto the selected match.
  1048. this.pdfViewer.scrollPageIntoView(index + 1);
  1049. }
  1050. if (page.textLayer) {
  1051. page.textLayer.updateMatches();
  1052. }
  1053. },
  1054. nextMatch: function PDFFindController_nextMatch() {
  1055. var previous = this.state.findPrevious;
  1056. var currentPageIndex = this.pdfViewer.currentPageNumber - 1;
  1057. var numPages = this.pdfViewer.pagesCount;
  1058. this.active = true;
  1059. if (this.dirtyMatch) {
  1060. // Need to recalculate the matches, reset everything.
  1061. this.dirtyMatch = false;
  1062. this.selected.pageIdx = this.selected.matchIdx = -1;
  1063. this.offset.pageIdx = currentPageIndex;
  1064. this.offset.matchIdx = null;
  1065. this.hadMatch = false;
  1066. this.resumePageIdx = null;
  1067. this.pageMatches = [];
  1068. var self = this;
  1069. for (var i = 0; i < numPages; i++) {
  1070. // Wipe out any previous highlighted matches.
  1071. this.updatePage(i);
  1072. // As soon as the text is extracted start finding the matches.
  1073. if (!(i in this.pendingFindMatches)) {
  1074. this.pendingFindMatches[i] = true;
  1075. this.extractTextPromises[i].then(function(pageIdx) {
  1076. delete self.pendingFindMatches[pageIdx];
  1077. self.calcFindMatch(pageIdx);
  1078. });
  1079. }
  1080. }
  1081. }
  1082. // If there's no query there's no point in searching.
  1083. if (this.state.query === '') {
  1084. this.updateUIState(FindStates.FIND_FOUND);
  1085. return;
  1086. }
  1087. // If we're waiting on a page, we return since we can't do anything else.
  1088. if (this.resumePageIdx) {
  1089. return;
  1090. }
  1091. var offset = this.offset;
  1092. // Keep track of how many pages we should maximally iterate through.
  1093. this.pagesToSearch = numPages;
  1094. // If there's already a matchIdx that means we are iterating through a
  1095. // page's matches.
  1096. if (offset.matchIdx !== null) {
  1097. var numPageMatches = this.pageMatches[offset.pageIdx].length;
  1098. if ((!previous && offset.matchIdx + 1 < numPageMatches) ||
  1099. (previous && offset.matchIdx > 0)) {
  1100. // The simple case; we just have advance the matchIdx to select
  1101. // the next match on the page.
  1102. this.hadMatch = true;
  1103. offset.matchIdx = (previous ? offset.matchIdx - 1 :
  1104. offset.matchIdx + 1);
  1105. this.updateMatch(true);
  1106. return;
  1107. }
  1108. // We went beyond the current page's matches, so we advance to
  1109. // the next page.
  1110. this.advanceOffsetPage(previous);
  1111. }
  1112. // Start searching through the page.
  1113. this.nextPageMatch();
  1114. },
  1115. matchesReady: function PDFFindController_matchesReady(matches) {
  1116. var offset = this.offset;
  1117. var numMatches = matches.length;
  1118. var previous = this.state.findPrevious;
  1119. if (numMatches) {
  1120. // There were matches for the page, so initialize the matchIdx.
  1121. this.hadMatch = true;
  1122. offset.matchIdx = (previous ? numMatches - 1 : 0);
  1123. this.updateMatch(true);
  1124. return true;
  1125. } else {
  1126. // No matches, so attempt to search the next page.
  1127. this.advanceOffsetPage(previous);
  1128. if (offset.wrapped) {
  1129. offset.matchIdx = null;
  1130. if (this.pagesToSearch < 0) {
  1131. // No point in wrapping again, there were no matches.
  1132. this.updateMatch(false);
  1133. // while matches were not found, searching for a page
  1134. // with matches should nevertheless halt.
  1135. return true;
  1136. }
  1137. }
  1138. // Matches were not found (and searching is not done).
  1139. return false;
  1140. }
  1141. },
  1142. nextPageMatch: function PDFFindController_nextPageMatch() {
  1143. if (this.resumePageIdx !== null) {
  1144. console.error('There can only be one pending page.');
  1145. }
  1146. do {
  1147. var pageIdx = this.offset.pageIdx;
  1148. var matches = this.pageMatches[pageIdx];
  1149. if (!matches) {
  1150. // The matches don't exist yet for processing by "matchesReady",
  1151. // so set a resume point for when they do exist.
  1152. this.resumePageIdx = pageIdx;
  1153. break;
  1154. }
  1155. } while (!this.matchesReady(matches));
  1156. },
  1157. advanceOffsetPage: function PDFFindController_advanceOffsetPage(previous) {
  1158. var offset = this.offset;
  1159. var numPages = this.extractTextPromises.length;
  1160. offset.pageIdx = (previous ? offset.pageIdx - 1 : offset.pageIdx + 1);
  1161. offset.matchIdx = null;
  1162. this.pagesToSearch--;
  1163. if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
  1164. offset.pageIdx = (previous ? numPages - 1 : 0);
  1165. offset.wrapped = true;
  1166. }
  1167. },
  1168. updateMatch: function PDFFindController_updateMatch(found) {
  1169. var state = FindStates.FIND_NOTFOUND;
  1170. var wrapped = this.offset.wrapped;
  1171. this.offset.wrapped = false;
  1172. if (found) {
  1173. var previousPage = this.selected.pageIdx;
  1174. this.selected.pageIdx = this.offset.pageIdx;
  1175. this.selected.matchIdx = this.offset.matchIdx;
  1176. state = (wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND);
  1177. // Update the currently selected page to wipe out any selected matches.
  1178. if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
  1179. this.updatePage(previousPage);
  1180. }
  1181. }
  1182. this.updateUIState(state, this.state.findPrevious);
  1183. if (this.selected.pageIdx !== -1) {
  1184. this.updatePage(this.selected.pageIdx);
  1185. }
  1186. },
  1187. updateUIState: function PDFFindController_updateUIState(state, previous) {
  1188. if (this.integratedFind) {
  1189. FirefoxCom.request('updateFindControlState',
  1190. { result: state, findPrevious: previous });
  1191. return;
  1192. }
  1193. if (this.findBar === null) {
  1194. throw new Error('PDFFindController is not initialized with a ' +
  1195. 'PDFFindBar instance.');
  1196. }
  1197. this.findBar.updateUIState(state, previous);
  1198. }
  1199. };
  1200. return PDFFindController;
  1201. })();
  1202. var PDFHistory = {
  1203. initialized: false,
  1204. initialDestination: null,
  1205. /**
  1206. * @param {string} fingerprint
  1207. * @param {IPDFLinkService} linkService
  1208. */
  1209. initialize: function pdfHistoryInitialize(fingerprint, linkService) {
  1210. this.initialized = true;
  1211. this.reInitialized = false;
  1212. this.allowHashChange = true;
  1213. this.historyUnlocked = true;
  1214. this.previousHash = window.location.hash.substring(1);
  1215. this.currentBookmark = '';
  1216. this.currentPage = 0;
  1217. this.updatePreviousBookmark = false;
  1218. this.previousBookmark = '';
  1219. this.previousPage = 0;
  1220. this.nextHashParam = '';
  1221. this.fingerprint = fingerprint;
  1222. this.linkService = linkService;
  1223. this.currentUid = this.uid = 0;
  1224. this.current = {};
  1225. var state = window.history.state;
  1226. if (this._isStateObjectDefined(state)) {
  1227. // This corresponds to navigating back to the document
  1228. // from another page in the browser history.
  1229. if (state.target.dest) {
  1230. this.initialDestination = state.target.dest;
  1231. } else {
  1232. linkService.setHash(state.target.hash);
  1233. }
  1234. this.currentUid = state.uid;
  1235. this.uid = state.uid + 1;
  1236. this.current = state.target;
  1237. } else {
  1238. // This corresponds to the loading of a new document.
  1239. if (state && state.fingerprint &&
  1240. this.fingerprint !== state.fingerprint) {
  1241. // Reinitialize the browsing history when a new document
  1242. // is opened in the web viewer.
  1243. this.reInitialized = true;
  1244. }
  1245. this._pushOrReplaceState({ fingerprint: this.fingerprint }, true);
  1246. }
  1247. var self = this;
  1248. window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
  1249. evt.preventDefault();
  1250. evt.stopPropagation();
  1251. if (!self.historyUnlocked) {
  1252. return;
  1253. }
  1254. if (evt.state) {
  1255. // Move back/forward in the history.
  1256. self._goTo(evt.state);
  1257. } else {
  1258. // Handle the user modifying the hash of a loaded document.
  1259. self.previousHash = window.location.hash.substring(1);
  1260. // If the history is empty when the hash changes,
  1261. // update the previous entry in the browser history.
  1262. if (self.uid === 0) {
  1263. var previousParams = (self.previousHash && self.currentBookmark &&
  1264. self.previousHash !== self.currentBookmark) ?
  1265. { hash: self.currentBookmark, page: self.currentPage } :
  1266. { page: 1 };
  1267. self.historyUnlocked = false;
  1268. self.allowHashChange = false;
  1269. window.history.back();
  1270. self._pushToHistory(previousParams, false, true);
  1271. window.history.forward();
  1272. self.historyUnlocked = true;
  1273. }
  1274. self._pushToHistory({ hash: self.previousHash }, false, true);
  1275. self._updatePreviousBookmark();
  1276. }
  1277. }, false);
  1278. function pdfHistoryBeforeUnload() {
  1279. var previousParams = self._getPreviousParams(null, true);
  1280. if (previousParams) {
  1281. var replacePrevious = (!self.current.dest &&
  1282. self.current.hash !== self.previousHash);
  1283. self._pushToHistory(previousParams, false, replacePrevious);
  1284. self._updatePreviousBookmark();
  1285. }
  1286. // Remove the event listener when navigating away from the document,
  1287. // since 'beforeunload' prevents Firefox from caching the document.
  1288. window.removeEventListener('beforeunload', pdfHistoryBeforeUnload, false);
  1289. }
  1290. window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
  1291. window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
  1292. // If the entire viewer (including the PDF file) is cached in the browser,
  1293. // we need to reattach the 'beforeunload' event listener since
  1294. // the 'DOMContentLoaded' event is not fired on 'pageshow'.
  1295. window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
  1296. }, false);
  1297. },
  1298. _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
  1299. return (state && state.uid >= 0 &&
  1300. state.fingerprint && this.fingerprint === state.fingerprint &&
  1301. state.target && state.target.hash) ? true : false;
  1302. },
  1303. _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
  1304. replace) {
  1305. if (replace) {
  1306. window.history.replaceState(stateObj, '', document.URL);
  1307. } else {
  1308. window.history.pushState(stateObj, '', document.URL);
  1309. }
  1310. },
  1311. get isHashChangeUnlocked() {
  1312. if (!this.initialized) {
  1313. return true;
  1314. }
  1315. // If the current hash changes when moving back/forward in the history,
  1316. // this will trigger a 'popstate' event *as well* as a 'hashchange' event.
  1317. // Since the hash generally won't correspond to the exact the position
  1318. // stored in the history's state object, triggering the 'hashchange' event
  1319. // can thus corrupt the browser history.
  1320. //
  1321. // When the hash changes during a 'popstate' event, we *only* prevent the
  1322. // first 'hashchange' event and immediately reset allowHashChange.
  1323. // If it is not reset, the user would not be able to change the hash.
  1324. var temp = this.allowHashChange;
  1325. this.allowHashChange = true;
  1326. return temp;
  1327. },
  1328. _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
  1329. if (this.updatePreviousBookmark &&
  1330. this.currentBookmark && this.currentPage) {
  1331. this.previousBookmark = this.currentBookmark;
  1332. this.previousPage = this.currentPage;
  1333. this.updatePreviousBookmark = false;
  1334. }
  1335. },
  1336. updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
  1337. pageNum) {
  1338. if (this.initialized) {
  1339. this.currentBookmark = bookmark.substring(1);
  1340. this.currentPage = pageNum | 0;
  1341. this._updatePreviousBookmark();
  1342. }
  1343. },
  1344. updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
  1345. if (this.initialized) {
  1346. this.nextHashParam = param;
  1347. }
  1348. },
  1349. push: function pdfHistoryPush(params, isInitialBookmark) {
  1350. if (!(this.initialized && this.historyUnlocked)) {
  1351. return;
  1352. }
  1353. if (params.dest && !params.hash) {
  1354. params.hash = (this.current.hash && this.current.dest &&
  1355. this.current.dest === params.dest) ?
  1356. this.current.hash :
  1357. this.linkService.getDestinationHash(params.dest).split('#')[1];
  1358. }
  1359. if (params.page) {
  1360. params.page |= 0;
  1361. }
  1362. if (isInitialBookmark) {
  1363. var target = window.history.state.target;
  1364. if (!target) {
  1365. // Invoked when the user specifies an initial bookmark,
  1366. // thus setting initialBookmark, when the document is loaded.
  1367. this._pushToHistory(params, false);
  1368. this.previousHash = window.location.hash.substring(1);
  1369. }
  1370. this.updatePreviousBookmark = this.nextHashParam ? false : true;
  1371. if (target) {
  1372. // If the current document is reloaded,
  1373. // avoid creating duplicate entries in the history.
  1374. this._updatePreviousBookmark();
  1375. }
  1376. return;
  1377. }
  1378. if (this.nextHashParam) {
  1379. if (this.nextHashParam === params.hash) {
  1380. this.nextHashParam = null;
  1381. this.updatePreviousBookmark = true;
  1382. return;
  1383. } else {
  1384. this.nextHashParam = null;
  1385. }
  1386. }
  1387. if (params.hash) {
  1388. if (this.current.hash) {
  1389. if (this.current.hash !== params.hash) {
  1390. this._pushToHistory(params, true);
  1391. } else {
  1392. if (!this.current.page && params.page) {
  1393. this._pushToHistory(params, false, true);
  1394. }
  1395. this.updatePreviousBookmark = true;
  1396. }
  1397. } else {
  1398. this._pushToHistory(params, true);
  1399. }
  1400. } else if (this.current.page && params.page &&
  1401. this.current.page !== params.page) {
  1402. this._pushToHistory(params, true);
  1403. }
  1404. },
  1405. _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage,
  1406. beforeUnload) {
  1407. if (!(this.currentBookmark && this.currentPage)) {
  1408. return null;
  1409. } else if (this.updatePreviousBookmark) {
  1410. this.updatePreviousBookmark = false;
  1411. }
  1412. if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
  1413. // Prevent the history from getting stuck in the current state,
  1414. // effectively preventing the user from going back/forward in the history.
  1415. //
  1416. // This happens if the current position in the document didn't change when
  1417. // the history was previously updated. The reasons for this are either:
  1418. // 1. The current zoom value is such that the document does not need to,
  1419. // or cannot, be scrolled to display the destination.
  1420. // 2. The previous destination is broken, and doesn't actally point to a
  1421. // position within the document.
  1422. // (This is either due to a bad PDF generator, or the user making a
  1423. // mistake when entering a destination in the hash parameters.)
  1424. return null;
  1425. }
  1426. if ((!this.current.dest && !onlyCheckPage) || beforeUnload) {
  1427. if (this.previousBookmark === this.currentBookmark) {
  1428. return null;
  1429. }
  1430. } else if (this.current.page || onlyCheckPage) {
  1431. if (this.previousPage === this.currentPage) {
  1432. return null;
  1433. }
  1434. } else {
  1435. return null;
  1436. }
  1437. var params = { hash: this.currentBookmark, page: this.currentPage };
  1438. if (PresentationMode.active) {
  1439. params.hash = null;
  1440. }
  1441. return params;
  1442. },
  1443. _stateObj: function pdfHistory_stateObj(params) {
  1444. return { fingerprint: this.fingerprint, uid: this.uid, target: params };
  1445. },
  1446. _pushToHistory: function pdfHistory_pushToHistory(params,
  1447. addPrevious, overwrite) {
  1448. if (!this.initialized) {
  1449. return;
  1450. }
  1451. if (!params.hash && params.page) {
  1452. params.hash = ('page=' + params.page);
  1453. }
  1454. if (addPrevious && !overwrite) {
  1455. var previousParams = this._getPreviousParams();
  1456. if (previousParams) {
  1457. var replacePrevious = (!this.current.dest &&
  1458. this.current.hash !== this.previousHash);
  1459. this._pushToHistory(previousParams, false, replacePrevious);
  1460. }
  1461. }
  1462. this._pushOrReplaceState(this._stateObj(params),
  1463. (overwrite || this.uid === 0));
  1464. this.currentUid = this.uid++;
  1465. this.current = params;
  1466. this.updatePreviousBookmark = true;
  1467. },
  1468. _goTo: function pdfHistory_goTo(state) {
  1469. if (!(this.initialized && this.historyUnlocked &&
  1470. this._isStateObjectDefined(state))) {
  1471. return;
  1472. }
  1473. if (!this.reInitialized && state.uid < this.currentUid) {
  1474. var previousParams = this._getPreviousParams(true);
  1475. if (previousParams) {
  1476. this._pushToHistory(this.current, false);
  1477. this._pushToHistory(previousParams, false);
  1478. this.currentUid = state.uid;
  1479. window.history.back();
  1480. return;
  1481. }
  1482. }
  1483. this.historyUnlocked = false;
  1484. if (state.target.dest) {
  1485. this.linkService.navigateTo(state.target.dest);
  1486. } else {
  1487. this.linkService.setHash(state.target.hash);
  1488. }
  1489. this.currentUid = state.uid;
  1490. if (state.uid > this.uid) {
  1491. this.uid = state.uid;
  1492. }
  1493. this.current = state.target;
  1494. this.updatePreviousBookmark = true;
  1495. var currentHash = window.location.hash.substring(1);
  1496. if (this.previousHash !== currentHash) {
  1497. this.allowHashChange = false;
  1498. }
  1499. this.previousHash = currentHash;
  1500. this.historyUnlocked = true;
  1501. },
  1502. back: function pdfHistoryBack() {
  1503. this.go(-1);
  1504. },
  1505. forward: function pdfHistoryForward() {
  1506. this.go(1);
  1507. },
  1508. go: function pdfHistoryGo(direction) {
  1509. if (this.initialized && this.historyUnlocked) {
  1510. var state = window.history.state;
  1511. if (direction === -1 && state && state.uid > 0) {
  1512. window.history.back();
  1513. } else if (direction === 1 && state && state.uid < (this.uid - 1)) {
  1514. window.history.forward();
  1515. }
  1516. }
  1517. }
  1518. };
  1519. var SecondaryToolbar = {
  1520. opened: false,
  1521. previousContainerHeight: null,
  1522. newContainerHeight: null,
  1523. initialize: function secondaryToolbarInitialize(options) {
  1524. this.toolbar = options.toolbar;
  1525. this.presentationMode = options.presentationMode;
  1526. this.documentProperties = options.documentProperties;
  1527. this.buttonContainer = this.toolbar.firstElementChild;
  1528. // Define the toolbar buttons.
  1529. this.toggleButton = options.toggleButton;
  1530. this.presentationModeButton = options.presentationModeButton;
  1531. this.openFile = options.openFile;
  1532. this.print = options.print;
  1533. this.download = options.download;
  1534. this.viewBookmark = options.viewBookmark;
  1535. this.firstPage = options.firstPage;
  1536. this.lastPage = options.lastPage;
  1537. this.pageRotateCw = options.pageRotateCw;
  1538. this.pageRotateCcw = options.pageRotateCcw;
  1539. this.documentPropertiesButton = options.documentPropertiesButton;
  1540. // Attach the event listeners.
  1541. var elements = [
  1542. // Button to toggle the visibility of the secondary toolbar:
  1543. { element: this.toggleButton, handler: this.toggle },
  1544. // All items within the secondary toolbar
  1545. // (except for toggleHandTool, hand_tool.js is responsible for it):
  1546. { element: this.presentationModeButton,
  1547. handler: this.presentationModeClick },
  1548. { element: this.openFile, handler: this.openFileClick },
  1549. { element: this.print, handler: this.printClick },
  1550. { element: this.download, handler: this.downloadClick },
  1551. { element: this.viewBookmark, handler: this.viewBookmarkClick },
  1552. { element: this.firstPage, handler: this.firstPageClick },
  1553. { element: this.lastPage, handler: this.lastPageClick },
  1554. { element: this.pageRotateCw, handler: this.pageRotateCwClick },
  1555. { element: this.pageRotateCcw, handler: this.pageRotateCcwClick },
  1556. { element: this.documentPropertiesButton,
  1557. handler: this.documentPropertiesClick }
  1558. ];
  1559. for (var item in elements) {
  1560. var element = elements[item].element;
  1561. if (element) {
  1562. element.addEventListener('click', elements[item].handler.bind(this));
  1563. }
  1564. }
  1565. },
  1566. // Event handling functions.
  1567. presentationModeClick: function secondaryToolbarPresentationModeClick(evt) {
  1568. this.presentationMode.request();
  1569. this.close();
  1570. },
  1571. openFileClick: function secondaryToolbarOpenFileClick(evt) {
  1572. document.getElementById('fileInput').click();
  1573. this.close();
  1574. },
  1575. printClick: function secondaryToolbarPrintClick(evt) {
  1576. window.print();
  1577. this.close();
  1578. },
  1579. downloadClick: function secondaryToolbarDownloadClick(evt) {
  1580. PDFViewerApplication.download();
  1581. this.close();
  1582. },
  1583. viewBookmarkClick: function secondaryToolbarViewBookmarkClick(evt) {
  1584. this.close();
  1585. },
  1586. firstPageClick: function secondaryToolbarFirstPageClick(evt) {
  1587. PDFViewerApplication.page = 1;
  1588. this.close();
  1589. },
  1590. lastPageClick: function secondaryToolbarLastPageClick(evt) {
  1591. if (PDFViewerApplication.pdfDocument) {
  1592. PDFViewerApplication.page = PDFViewerApplication.pagesCount;
  1593. }
  1594. this.close();
  1595. },
  1596. pageRotateCwClick: function secondaryToolbarPageRotateCwClick(evt) {
  1597. PDFViewerApplication.rotatePages(90);
  1598. },
  1599. pageRotateCcwClick: function secondaryToolbarPageRotateCcwClick(evt) {
  1600. PDFViewerApplication.rotatePages(-90);
  1601. },
  1602. documentPropertiesClick: function secondaryToolbarDocumentPropsClick(evt) {
  1603. this.documentProperties.open();
  1604. this.close();
  1605. },
  1606. // Misc. functions for interacting with the toolbar.
  1607. setMaxHeight: function secondaryToolbarSetMaxHeight(container) {
  1608. if (!container || !this.buttonContainer) {
  1609. return;
  1610. }
  1611. this.newContainerHeight = container.clientHeight;
  1612. if (this.previousContainerHeight === this.newContainerHeight) {
  1613. return;
  1614. }
  1615. this.buttonContainer.setAttribute('style',
  1616. 'max-height: ' + (this.newContainerHeight - SCROLLBAR_PADDING) + 'px;');
  1617. this.previousContainerHeight = this.newContainerHeight;
  1618. },
  1619. open: function secondaryToolbarOpen() {
  1620. if (this.opened) {
  1621. return;
  1622. }
  1623. this.opened = true;
  1624. this.toggleButton.classList.add('toggled');
  1625. this.toolbar.classList.remove('hidden');
  1626. },
  1627. close: function secondaryToolbarClose(target) {
  1628. if (!this.opened) {
  1629. return;
  1630. } else if (target && !this.toolbar.contains(target)) {
  1631. return;
  1632. }
  1633. this.opened = false;
  1634. this.toolbar.classList.add('hidden');
  1635. this.toggleButton.classList.remove('toggled');
  1636. },
  1637. toggle: function secondaryToolbarToggle() {
  1638. if (this.opened) {
  1639. this.close();
  1640. } else {
  1641. this.open();
  1642. }
  1643. }
  1644. };
  1645. var DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms
  1646. var SELECTOR = 'presentationControls';
  1647. var DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1000; // in ms
  1648. var PresentationMode = {
  1649. active: false,
  1650. args: null,
  1651. contextMenuOpen: false,
  1652. prevCoords: { x: null, y: null },
  1653. initialize: function presentationModeInitialize(options) {
  1654. this.container = options.container;
  1655. this.secondaryToolbar = options.secondaryToolbar;
  1656. this.viewer = this.container.firstElementChild;
  1657. this.firstPage = options.firstPage;
  1658. this.lastPage = options.lastPage;
  1659. this.pageRotateCw = options.pageRotateCw;
  1660. this.pageRotateCcw = options.pageRotateCcw;
  1661. this.firstPage.addEventListener('click', function() {
  1662. this.contextMenuOpen = false;
  1663. this.secondaryToolbar.firstPageClick();
  1664. }.bind(this));
  1665. this.lastPage.addEventListener('click', function() {
  1666. this.contextMenuOpen = false;
  1667. this.secondaryToolbar.lastPageClick();
  1668. }.bind(this));
  1669. this.pageRotateCw.addEventListener('click', function() {
  1670. this.contextMenuOpen = false;
  1671. this.secondaryToolbar.pageRotateCwClick();
  1672. }.bind(this));
  1673. this.pageRotateCcw.addEventListener('click', function() {
  1674. this.contextMenuOpen = false;
  1675. this.secondaryToolbar.pageRotateCcwClick();
  1676. }.bind(this));
  1677. },
  1678. get isFullscreen() {
  1679. return (document.fullscreenElement ||
  1680. document.mozFullScreen ||
  1681. document.webkitIsFullScreen ||
  1682. document.msFullscreenElement);
  1683. },
  1684. /**
  1685. * Initialize a timeout that is used to specify switchInProgress when the
  1686. * browser transitions to fullscreen mode. Since resize events are triggered
  1687. * multiple times during the switch to fullscreen mode, this is necessary in
  1688. * order to prevent the page from being scrolled partially, or completely,
  1689. * out of view when Presentation Mode is enabled.
  1690. * Note: This is only an issue at certain zoom levels, e.g. 'page-width'.
  1691. */
  1692. _setSwitchInProgress: function presentationMode_setSwitchInProgress() {
  1693. if (this.switchInProgress) {
  1694. clearTimeout(this.switchInProgress);
  1695. }
  1696. this.switchInProgress = setTimeout(function switchInProgressTimeout() {
  1697. delete this.switchInProgress;
  1698. this._notifyStateChange();
  1699. }.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS);
  1700. },
  1701. _resetSwitchInProgress: function presentationMode_resetSwitchInProgress() {
  1702. if (this.switchInProgress) {
  1703. clearTimeout(this.switchInProgress);
  1704. delete this.switchInProgress;
  1705. }
  1706. },
  1707. request: function presentationModeRequest() {
  1708. if (!PDFViewerApplication.supportsFullscreen || this.isFullscreen ||
  1709. !this.viewer.hasChildNodes()) {
  1710. return false;
  1711. }
  1712. this._setSwitchInProgress();
  1713. this._notifyStateChange();
  1714. if (this.container.requestFullscreen) {
  1715. this.container.requestFullscreen();
  1716. } else if (this.container.mozRequestFullScreen) {
  1717. this.container.mozRequestFullScreen();
  1718. } else if (this.container.webkitRequestFullScreen) {
  1719. this.container.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
  1720. } else if (this.container.msRequestFullscreen) {
  1721. this.container.msRequestFullscreen();
  1722. } else {
  1723. return false;
  1724. }
  1725. this.args = {
  1726. page: PDFViewerApplication.page,
  1727. previousScale: PDFViewerApplication.currentScaleValue
  1728. };
  1729. return true;
  1730. },
  1731. _notifyStateChange: function presentationModeNotifyStateChange() {
  1732. var event = document.createEvent('CustomEvent');
  1733. event.initCustomEvent('presentationmodechanged', true, true, {
  1734. active: PresentationMode.active,
  1735. switchInProgress: !!PresentationMode.switchInProgress
  1736. });
  1737. window.dispatchEvent(event);
  1738. },
  1739. enter: function presentationModeEnter() {
  1740. this.active = true;
  1741. this._resetSwitchInProgress();
  1742. this._notifyStateChange();
  1743. // Ensure that the correct page is scrolled into view when entering
  1744. // Presentation Mode, by waiting until fullscreen mode in enabled.
  1745. // Note: This is only necessary in non-Mozilla browsers.
  1746. setTimeout(function enterPresentationModeTimeout() {
  1747. PDFViewerApplication.page = this.args.page;
  1748. PDFViewerApplication.setScale('page-fit', true);
  1749. }.bind(this), 0);
  1750. window.addEventListener('mousemove', this.mouseMove, false);
  1751. window.addEventListener('mousedown', this.mouseDown, false);
  1752. window.addEventListener('contextmenu', this.contextMenu, false);
  1753. this.showControls();
  1754. HandTool.enterPresentationMode();
  1755. this.contextMenuOpen = false;
  1756. this.container.setAttribute('contextmenu', 'viewerContextMenu');
  1757. },
  1758. exit: function presentationModeExit() {
  1759. var page = PDFViewerApplication.page;
  1760. // Ensure that the correct page is scrolled into view when exiting
  1761. // Presentation Mode, by waiting until fullscreen mode is disabled.
  1762. // Note: This is only necessary in non-Mozilla browsers.
  1763. setTimeout(function exitPresentationModeTimeout() {
  1764. this.active = false;
  1765. this._notifyStateChange();
  1766. PDFViewerApplication.setScale(this.args.previousScale, true);
  1767. PDFViewerApplication.page = page;
  1768. this.args = null;
  1769. }.bind(this), 0);
  1770. window.removeEventListener('mousemove', this.mouseMove, false);
  1771. window.removeEventListener('mousedown', this.mouseDown, false);
  1772. window.removeEventListener('contextmenu', this.contextMenu, false);
  1773. this.hideControls();
  1774. PDFViewerApplication.clearMouseScrollState();
  1775. HandTool.exitPresentationMode();
  1776. this.container.removeAttribute('contextmenu');
  1777. this.contextMenuOpen = false;
  1778. // Ensure that the thumbnail of the current page is visible
  1779. // when exiting presentation mode.
  1780. scrollIntoView(document.getElementById('thumbnailContainer' + page));
  1781. },
  1782. showControls: function presentationModeShowControls() {
  1783. if (this.controlsTimeout) {
  1784. clearTimeout(this.controlsTimeout);
  1785. } else {
  1786. this.container.classList.add(SELECTOR);
  1787. }
  1788. this.controlsTimeout = setTimeout(function hideControlsTimeout() {
  1789. this.container.classList.remove(SELECTOR);
  1790. delete this.controlsTimeout;
  1791. }.bind(this), DELAY_BEFORE_HIDING_CONTROLS);
  1792. },
  1793. hideControls: function presentationModeHideControls() {
  1794. if (!this.controlsTimeout) {
  1795. return;
  1796. }
  1797. this.container.classList.remove(SELECTOR);
  1798. clearTimeout(this.controlsTimeout);
  1799. delete this.controlsTimeout;
  1800. },
  1801. mouseMove: function presentationModeMouseMove(evt) {
  1802. // Workaround for a bug in WebKit browsers that causes the 'mousemove' event
  1803. // to be fired when the cursor is changed. For details, see:
  1804. // http://code.google.com/p/chromium/issues/detail?id=103041.
  1805. var currCoords = { x: evt.clientX, y: evt.clientY };
  1806. var prevCoords = PresentationMode.prevCoords;
  1807. PresentationMode.prevCoords = currCoords;
  1808. if (currCoords.x === prevCoords.x && currCoords.y === prevCoords.y) {
  1809. return;
  1810. }
  1811. PresentationMode.showControls();
  1812. },
  1813. mouseDown: function presentationModeMouseDown(evt) {
  1814. var self = PresentationMode;
  1815. if (self.contextMenuOpen) {
  1816. self.contextMenuOpen = false;
  1817. evt.preventDefault();
  1818. return;
  1819. }
  1820. if (evt.button === 0) {
  1821. // Enable clicking of links in presentation mode. Please note:
  1822. // Only links pointing to destinations in the current PDF document work.
  1823. var isInternalLink = (evt.target.href &&
  1824. evt.target.classList.contains('internalLink'));
  1825. if (!isInternalLink) {
  1826. // Unless an internal link was clicked, advance one page.
  1827. evt.preventDefault();
  1828. PDFViewerApplication.page += (evt.shiftKey ? -1 : 1);
  1829. }
  1830. }
  1831. },
  1832. contextMenu: function presentationModeContextMenu(evt) {
  1833. PresentationMode.contextMenuOpen = true;
  1834. }
  1835. };
  1836. (function presentationModeClosure() {
  1837. function presentationModeChange(e) {
  1838. if (PresentationMode.isFullscreen) {
  1839. PresentationMode.enter();
  1840. } else {
  1841. PresentationMode.exit();
  1842. }
  1843. }
  1844. window.addEventListener('fullscreenchange', presentationModeChange, false);
  1845. window.addEventListener('mozfullscreenchange', presentationModeChange, false);
  1846. window.addEventListener('webkitfullscreenchange', presentationModeChange,
  1847. false);
  1848. window.addEventListener('MSFullscreenChange', presentationModeChange, false);
  1849. })();
  1850. /* Copyright 2013 Rob Wu <gwnRob@gmail.com>
  1851. * https://github.com/Rob--W/grab-to-pan.js
  1852. *
  1853. * Licensed under the Apache License, Version 2.0 (the "License");
  1854. * you may not use this file except in compliance with the License.
  1855. * You may obtain a copy of the License at
  1856. *
  1857. * http://www.apache.org/licenses/LICENSE-2.0
  1858. *
  1859. * Unless required by applicable law or agreed to in writing, software
  1860. * distributed under the License is distributed on an "AS IS" BASIS,
  1861. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1862. * See the License for the specific language governing permissions and
  1863. * limitations under the License.
  1864. */
  1865. 'use strict';
  1866. var GrabToPan = (function GrabToPanClosure() {
  1867. /**
  1868. * Construct a GrabToPan instance for a given HTML element.
  1869. * @param options.element {Element}
  1870. * @param options.ignoreTarget {function} optional. See `ignoreTarget(node)`
  1871. * @param options.onActiveChanged {function(boolean)} optional. Called
  1872. * when grab-to-pan is (de)activated. The first argument is a boolean that
  1873. * shows whether grab-to-pan is activated.
  1874. */
  1875. function GrabToPan(options) {
  1876. this.element = options.element;
  1877. this.document = options.element.ownerDocument;
  1878. if (typeof options.ignoreTarget === 'function') {
  1879. this.ignoreTarget = options.ignoreTarget;
  1880. }
  1881. this.onActiveChanged = options.onActiveChanged;
  1882. // Bind the contexts to ensure that `this` always points to
  1883. // the GrabToPan instance.
  1884. this.activate = this.activate.bind(this);
  1885. this.deactivate = this.deactivate.bind(this);
  1886. this.toggle = this.toggle.bind(this);
  1887. this._onmousedown = this._onmousedown.bind(this);
  1888. this._onmousemove = this._onmousemove.bind(this);
  1889. this._endPan = this._endPan.bind(this);
  1890. // This overlay will be inserted in the document when the mouse moves during
  1891. // a grab operation, to ensure that the cursor has the desired appearance.
  1892. var overlay = this.overlay = document.createElement('div');
  1893. overlay.className = 'grab-to-pan-grabbing';
  1894. }
  1895. GrabToPan.prototype = {
  1896. /**
  1897. * Class name of element which can be grabbed
  1898. */
  1899. CSS_CLASS_GRAB: 'grab-to-pan-grab',
  1900. /**
  1901. * Bind a mousedown event to the element to enable grab-detection.
  1902. */
  1903. activate: function GrabToPan_activate() {
  1904. if (!this.active) {
  1905. this.active = true;
  1906. this.element.addEventListener('mousedown', this._onmousedown, true);
  1907. this.element.classList.add(this.CSS_CLASS_GRAB);
  1908. if (this.onActiveChanged) {
  1909. this.onActiveChanged(true);
  1910. }
  1911. }
  1912. },
  1913. /**
  1914. * Removes all events. Any pending pan session is immediately stopped.
  1915. */
  1916. deactivate: function GrabToPan_deactivate() {
  1917. if (this.active) {
  1918. this.active = false;
  1919. this.element.removeEventListener('mousedown', this._onmousedown, true);
  1920. this._endPan();
  1921. this.element.classList.remove(this.CSS_CLASS_GRAB);
  1922. if (this.onActiveChanged) {
  1923. this.onActiveChanged(false);
  1924. }
  1925. }
  1926. },
  1927. toggle: function GrabToPan_toggle() {
  1928. if (this.active) {
  1929. this.deactivate();
  1930. } else {
  1931. this.activate();
  1932. }
  1933. },
  1934. /**
  1935. * Whether to not pan if the target element is clicked.
  1936. * Override this method to change the default behaviour.
  1937. *
  1938. * @param node {Element} The target of the event
  1939. * @return {boolean} Whether to not react to the click event.
  1940. */
  1941. ignoreTarget: function GrabToPan_ignoreTarget(node) {
  1942. // Use matchesSelector to check whether the clicked element
  1943. // is (a child of) an input element / link
  1944. return node[matchesSelector](
  1945. 'a[href], a[href] *, input, textarea, button, button *, select, option'
  1946. );
  1947. },
  1948. /**
  1949. * @private
  1950. */
  1951. _onmousedown: function GrabToPan__onmousedown(event) {
  1952. if (event.button !== 0 || this.ignoreTarget(event.target)) {
  1953. return;
  1954. }
  1955. if (event.originalTarget) {
  1956. try {
  1957. /* jshint expr:true */
  1958. event.originalTarget.tagName;
  1959. } catch (e) {
  1960. // Mozilla-specific: element is a scrollbar (XUL element)
  1961. return;
  1962. }
  1963. }
  1964. this.scrollLeftStart = this.element.scrollLeft;
  1965. this.scrollTopStart = this.element.scrollTop;
  1966. this.clientXStart = event.clientX;
  1967. this.clientYStart = event.clientY;
  1968. this.document.addEventListener('mousemove', this._onmousemove, true);
  1969. this.document.addEventListener('mouseup', this._endPan, true);
  1970. // When a scroll event occurs before a mousemove, assume that the user
  1971. // dragged a scrollbar (necessary for Opera Presto, Safari and IE)
  1972. // (not needed for Chrome/Firefox)
  1973. this.element.addEventListener('scroll', this._endPan, true);
  1974. event.preventDefault();
  1975. event.stopPropagation();
  1976. this.document.documentElement.classList.add(this.CSS_CLASS_GRABBING);
  1977. var focusedElement = document.activeElement;
  1978. if (focusedElement && !focusedElement.contains(event.target)) {
  1979. focusedElement.blur();
  1980. }
  1981. },
  1982. /**
  1983. * @private
  1984. */
  1985. _onmousemove: function GrabToPan__onmousemove(event) {
  1986. this.element.removeEventListener('scroll', this._endPan, true);
  1987. if (isLeftMouseReleased(event)) {
  1988. this._endPan();
  1989. return;
  1990. }
  1991. var xDiff = event.clientX - this.clientXStart;
  1992. var yDiff = event.clientY - this.clientYStart;
  1993. this.element.scrollTop = this.scrollTopStart - yDiff;
  1994. this.element.scrollLeft = this.scrollLeftStart - xDiff;
  1995. if (!this.overlay.parentNode) {
  1996. document.body.appendChild(this.overlay);
  1997. }
  1998. },
  1999. /**
  2000. * @private
  2001. */
  2002. _endPan: function GrabToPan__endPan() {
  2003. this.element.removeEventListener('scroll', this._endPan, true);
  2004. this.document.removeEventListener('mousemove', this._onmousemove, true);
  2005. this.document.removeEventListener('mouseup', this._endPan, true);
  2006. if (this.overlay.parentNode) {
  2007. this.overlay.parentNode.removeChild(this.overlay);
  2008. }
  2009. }
  2010. };
  2011. // Get the correct (vendor-prefixed) name of the matches method.
  2012. var matchesSelector;
  2013. ['webkitM', 'mozM', 'msM', 'oM', 'm'].some(function(prefix) {
  2014. var name = prefix + 'atches';
  2015. if (name in document.documentElement) {
  2016. matchesSelector = name;
  2017. }
  2018. name += 'Selector';
  2019. if (name in document.documentElement) {
  2020. matchesSelector = name;
  2021. }
  2022. return matchesSelector; // If found, then truthy, and [].some() ends.
  2023. });
  2024. // Browser sniffing because it's impossible to feature-detect
  2025. // whether event.which for onmousemove is reliable
  2026. var isNotIEorIsIE10plus = !document.documentMode || document.documentMode > 9;
  2027. var chrome = window.chrome;
  2028. var isChrome15OrOpera15plus = chrome && (chrome.webstore || chrome.app);
  2029. // ^ Chrome 15+ ^ Opera 15+
  2030. var isSafari6plus = /Apple/.test(navigator.vendor) &&
  2031. /Version\/([6-9]\d*|[1-5]\d+)/.test(navigator.userAgent);
  2032. /**
  2033. * Whether the left mouse is not pressed.
  2034. * @param event {MouseEvent}
  2035. * @return {boolean} True if the left mouse button is not pressed.
  2036. * False if unsure or if the left mouse button is pressed.
  2037. */
  2038. function isLeftMouseReleased(event) {
  2039. if ('buttons' in event && isNotIEorIsIE10plus) {
  2040. // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-buttons
  2041. // Firefox 15+
  2042. // Internet Explorer 10+
  2043. return !(event.buttons | 1);
  2044. }
  2045. if (isChrome15OrOpera15plus || isSafari6plus) {
  2046. // Chrome 14+
  2047. // Opera 15+
  2048. // Safari 6.0+
  2049. return event.which === 0;
  2050. }
  2051. }
  2052. return GrabToPan;
  2053. })();
  2054. var HandTool = {
  2055. initialize: function handToolInitialize(options) {
  2056. var toggleHandTool = options.toggleHandTool;
  2057. this.handTool = new GrabToPan({
  2058. element: options.container,
  2059. onActiveChanged: function(isActive) {
  2060. if (!toggleHandTool) {
  2061. return;
  2062. }
  2063. if (isActive) {
  2064. toggleHandTool.title =
  2065. mozL10n.get('hand_tool_disable.title', null, 'Disable hand tool');
  2066. toggleHandTool.firstElementChild.textContent =
  2067. mozL10n.get('hand_tool_disable_label', null, 'Disable hand tool');
  2068. } else {
  2069. toggleHandTool.title =
  2070. mozL10n.get('hand_tool_enable.title', null, 'Enable hand tool');
  2071. toggleHandTool.firstElementChild.textContent =
  2072. mozL10n.get('hand_tool_enable_label', null, 'Enable hand tool');
  2073. }
  2074. }
  2075. });
  2076. if (toggleHandTool) {
  2077. toggleHandTool.addEventListener('click', this.toggle.bind(this), false);
  2078. window.addEventListener('localized', function (evt) {
  2079. Preferences.get('enableHandToolOnLoad').then(function resolved(value) {
  2080. if (value) {
  2081. this.handTool.activate();
  2082. }
  2083. }.bind(this), function rejected(reason) {});
  2084. }.bind(this));
  2085. }
  2086. },
  2087. toggle: function handToolToggle() {
  2088. this.handTool.toggle();
  2089. SecondaryToolbar.close();
  2090. },
  2091. enterPresentationMode: function handToolEnterPresentationMode() {
  2092. if (this.handTool.active) {
  2093. this.wasActive = true;
  2094. this.handTool.deactivate();
  2095. }
  2096. },
  2097. exitPresentationMode: function handToolExitPresentationMode() {
  2098. if (this.wasActive) {
  2099. this.wasActive = null;
  2100. this.handTool.activate();
  2101. }
  2102. }
  2103. };
  2104. var OverlayManager = {
  2105. overlays: {},
  2106. active: null,
  2107. /**
  2108. * @param {string} name The name of the overlay that is registered. This must
  2109. * be equal to the ID of the overlay's DOM element.
  2110. * @param {function} callerCloseMethod (optional) The method that, if present,
  2111. * will call OverlayManager.close from the Object
  2112. * registering the overlay. Access to this method is
  2113. * necessary in order to run cleanup code when e.g.
  2114. * the overlay is force closed. The default is null.
  2115. * @param {boolean} canForceClose (optional) Indicates if opening the overlay
  2116. * will close an active overlay. The default is false.
  2117. * @returns {Promise} A promise that is resolved when the overlay has been
  2118. * registered.
  2119. */
  2120. register: function overlayManagerRegister(name,
  2121. callerCloseMethod, canForceClose) {
  2122. return new Promise(function (resolve) {
  2123. var element, container;
  2124. if (!name || !(element = document.getElementById(name)) ||
  2125. !(container = element.parentNode)) {
  2126. throw new Error('Not enough parameters.');
  2127. } else if (this.overlays[name]) {
  2128. throw new Error('The overlay is already registered.');
  2129. }
  2130. this.overlays[name] = { element: element,
  2131. container: container,
  2132. callerCloseMethod: (callerCloseMethod || null),
  2133. canForceClose: (canForceClose || false) };
  2134. resolve();
  2135. }.bind(this));
  2136. },
  2137. /**
  2138. * @param {string} name The name of the overlay that is unregistered.
  2139. * @returns {Promise} A promise that is resolved when the overlay has been
  2140. * unregistered.
  2141. */
  2142. unregister: function overlayManagerUnregister(name) {
  2143. return new Promise(function (resolve) {
  2144. if (!this.overlays[name]) {
  2145. throw new Error('The overlay does not exist.');
  2146. } else if (this.active === name) {
  2147. throw new Error('The overlay cannot be removed while it is active.');
  2148. }
  2149. delete this.overlays[name];
  2150. resolve();
  2151. }.bind(this));
  2152. },
  2153. /**
  2154. * @param {string} name The name of the overlay that should be opened.
  2155. * @returns {Promise} A promise that is resolved when the overlay has been
  2156. * opened.
  2157. */
  2158. open: function overlayManagerOpen(name) {
  2159. return new Promise(function (resolve) {
  2160. if (!this.overlays[name]) {
  2161. throw new Error('The overlay does not exist.');
  2162. } else if (this.active) {
  2163. if (this.overlays[name].canForceClose) {
  2164. this._closeThroughCaller();
  2165. } else if (this.active === name) {
  2166. throw new Error('The overlay is already active.');
  2167. } else {
  2168. throw new Error('Another overlay is currently active.');
  2169. }
  2170. }
  2171. this.active = name;
  2172. this.overlays[this.active].element.classList.remove('hidden');
  2173. this.overlays[this.active].container.classList.remove('hidden');
  2174. window.addEventListener('keydown', this._keyDown);
  2175. resolve();
  2176. }.bind(this));
  2177. },
  2178. /**
  2179. * @param {string} name The name of the overlay that should be closed.
  2180. * @returns {Promise} A promise that is resolved when the overlay has been
  2181. * closed.
  2182. */
  2183. close: function overlayManagerClose(name) {
  2184. return new Promise(function (resolve) {
  2185. if (!this.overlays[name]) {
  2186. throw new Error('The overlay does not exist.');
  2187. } else if (!this.active) {
  2188. throw new Error('The overlay is currently not active.');
  2189. } else if (this.active !== name) {
  2190. throw new Error('Another overlay is currently active.');
  2191. }
  2192. this.overlays[this.active].container.classList.add('hidden');
  2193. this.overlays[this.active].element.classList.add('hidden');
  2194. this.active = null;
  2195. window.removeEventListener('keydown', this._keyDown);
  2196. resolve();
  2197. }.bind(this));
  2198. },
  2199. /**
  2200. * @private
  2201. */
  2202. _keyDown: function overlayManager_keyDown(evt) {
  2203. var self = OverlayManager;
  2204. if (self.active && evt.keyCode === 27) { // Esc key.
  2205. self._closeThroughCaller();
  2206. evt.preventDefault();
  2207. }
  2208. },
  2209. /**
  2210. * @private
  2211. */
  2212. _closeThroughCaller: function overlayManager_closeThroughCaller() {
  2213. if (this.overlays[this.active].callerCloseMethod) {
  2214. this.overlays[this.active].callerCloseMethod();
  2215. }
  2216. if (this.active) {
  2217. this.close(this.active);
  2218. }
  2219. }
  2220. };
  2221. var PasswordPrompt = {
  2222. overlayName: null,
  2223. updatePassword: null,
  2224. reason: null,
  2225. passwordField: null,
  2226. passwordText: null,
  2227. passwordSubmit: null,
  2228. passwordCancel: null,
  2229. initialize: function secondaryToolbarInitialize(options) {
  2230. this.overlayName = options.overlayName;
  2231. this.passwordField = options.passwordField;
  2232. this.passwordText = options.passwordText;
  2233. this.passwordSubmit = options.passwordSubmit;
  2234. this.passwordCancel = options.passwordCancel;
  2235. // Attach the event listeners.
  2236. this.passwordSubmit.addEventListener('click',
  2237. this.verifyPassword.bind(this));
  2238. this.passwordCancel.addEventListener('click', this.close.bind(this));
  2239. this.passwordField.addEventListener('keydown', function (e) {
  2240. if (e.keyCode === 13) { // Enter key
  2241. this.verifyPassword();
  2242. }
  2243. }.bind(this));
  2244. OverlayManager.register(this.overlayName, this.close.bind(this), true);
  2245. },
  2246. open: function passwordPromptOpen() {
  2247. OverlayManager.open(this.overlayName).then(function () {
  2248. this.passwordField.focus();
  2249. var promptString = mozL10n.get('password_label', null,
  2250. 'Enter the password to open this PDF file.');
  2251. if (this.reason === PDFJS.PasswordResponses.INCORRECT_PASSWORD) {
  2252. promptString = mozL10n.get('password_invalid', null,
  2253. 'Invalid password. Please try again.');
  2254. }
  2255. this.passwordText.textContent = promptString;
  2256. }.bind(this));
  2257. },
  2258. close: function passwordPromptClose() {
  2259. OverlayManager.close(this.overlayName).then(function () {
  2260. this.passwordField.value = '';
  2261. }.bind(this));
  2262. },
  2263. verifyPassword: function passwordPromptVerifyPassword() {
  2264. var password = this.passwordField.value;
  2265. if (password && password.length > 0) {
  2266. this.close();
  2267. return this.updatePassword(password);
  2268. }
  2269. }
  2270. };
  2271. var DocumentProperties = {
  2272. overlayName: null,
  2273. rawFileSize: 0,
  2274. // Document property fields (in the viewer).
  2275. fileNameField: null,
  2276. fileSizeField: null,
  2277. titleField: null,
  2278. authorField: null,
  2279. subjectField: null,
  2280. keywordsField: null,
  2281. creationDateField: null,
  2282. modificationDateField: null,
  2283. creatorField: null,
  2284. producerField: null,
  2285. versionField: null,
  2286. pageCountField: null,
  2287. url: null,
  2288. pdfDocument: null,
  2289. initialize: function documentPropertiesInitialize(options) {
  2290. this.overlayName = options.overlayName;
  2291. // Set the document property fields.
  2292. this.fileNameField = options.fileNameField;
  2293. this.fileSizeField = options.fileSizeField;
  2294. this.titleField = options.titleField;
  2295. this.authorField = options.authorField;
  2296. this.subjectField = options.subjectField;
  2297. this.keywordsField = options.keywordsField;
  2298. this.creationDateField = options.creationDateField;
  2299. this.modificationDateField = options.modificationDateField;
  2300. this.creatorField = options.creatorField;
  2301. this.producerField = options.producerField;
  2302. this.versionField = options.versionField;
  2303. this.pageCountField = options.pageCountField;
  2304. // Bind the event listener for the Close button.
  2305. if (options.closeButton) {
  2306. options.closeButton.addEventListener('click', this.close.bind(this));
  2307. }
  2308. this.dataAvailablePromise = new Promise(function (resolve) {
  2309. this.resolveDataAvailable = resolve;
  2310. }.bind(this));
  2311. OverlayManager.register(this.overlayName, this.close.bind(this));
  2312. },
  2313. getProperties: function documentPropertiesGetProperties() {
  2314. if (!OverlayManager.active) {
  2315. // If the dialog was closed before dataAvailablePromise was resolved,
  2316. // don't bother updating the properties.
  2317. return;
  2318. }
  2319. // Get the file size (if it hasn't already been set).
  2320. this.pdfDocument.getDownloadInfo().then(function(data) {
  2321. if (data.length === this.rawFileSize) {
  2322. return;
  2323. }
  2324. this.setFileSize(data.length);
  2325. this.updateUI(this.fileSizeField, this.parseFileSize());
  2326. }.bind(this));
  2327. // Get the document properties.
  2328. this.pdfDocument.getMetadata().then(function(data) {
  2329. var fields = [
  2330. { field: this.fileNameField,
  2331. content: getPDFFileNameFromURL(this.url) },
  2332. { field: this.fileSizeField, content: this.parseFileSize() },
  2333. { field: this.titleField, content: data.info.Title },
  2334. { field: this.authorField, content: data.info.Author },
  2335. { field: this.subjectField, content: data.info.Subject },
  2336. { field: this.keywordsField, content: data.info.Keywords },
  2337. { field: this.creationDateField,
  2338. content: this.parseDate(data.info.CreationDate) },
  2339. { field: this.modificationDateField,
  2340. content: this.parseDate(data.info.ModDate) },
  2341. { field: this.creatorField, content: data.info.Creator },
  2342. { field: this.producerField, content: data.info.Producer },
  2343. { field: this.versionField, content: data.info.PDFFormatVersion },
  2344. { field: this.pageCountField, content: this.pdfDocument.numPages }
  2345. ];
  2346. // Show the properties in the dialog.
  2347. for (var item in fields) {
  2348. var element = fields[item];
  2349. this.updateUI(element.field, element.content);
  2350. }
  2351. }.bind(this));
  2352. },
  2353. updateUI: function documentPropertiesUpdateUI(field, content) {
  2354. if (field && content !== undefined && content !== '') {
  2355. field.textContent = content;
  2356. }
  2357. },
  2358. setFileSize: function documentPropertiesSetFileSize(fileSize) {
  2359. if (fileSize > 0) {
  2360. this.rawFileSize = fileSize;
  2361. }
  2362. },
  2363. parseFileSize: function documentPropertiesParseFileSize() {
  2364. var fileSize = this.rawFileSize, kb = fileSize / 1024;
  2365. if (!kb) {
  2366. return;
  2367. } else if (kb < 1024) {
  2368. return mozL10n.get('document_properties_kb', {
  2369. size_kb: (+kb.toPrecision(3)).toLocaleString(),
  2370. size_b: fileSize.toLocaleString()
  2371. }, '{{size_kb}} KB ({{size_b}} bytes)');
  2372. } else {
  2373. return mozL10n.get('document_properties_mb', {
  2374. size_mb: (+(kb / 1024).toPrecision(3)).toLocaleString(),
  2375. size_b: fileSize.toLocaleString()
  2376. }, '{{size_mb}} MB ({{size_b}} bytes)');
  2377. }
  2378. },
  2379. open: function documentPropertiesOpen() {
  2380. Promise.all([OverlayManager.open(this.overlayName),
  2381. this.dataAvailablePromise]).then(function () {
  2382. this.getProperties();
  2383. }.bind(this));
  2384. },
  2385. close: function documentPropertiesClose() {
  2386. OverlayManager.close(this.overlayName);
  2387. },
  2388. parseDate: function documentPropertiesParseDate(inputDate) {
  2389. // This is implemented according to the PDF specification (see
  2390. // http://www.gnupdf.org/Date for an overview), but note that
  2391. // Adobe Reader doesn't handle changing the date to universal time
  2392. // and doesn't use the user's time zone (they're effectively ignoring
  2393. // the HH' and mm' parts of the date string).
  2394. var dateToParse = inputDate;
  2395. if (dateToParse === undefined) {
  2396. return '';
  2397. }
  2398. // Remove the D: prefix if it is available.
  2399. if (dateToParse.substring(0,2) === 'D:') {
  2400. dateToParse = dateToParse.substring(2);
  2401. }
  2402. // Get all elements from the PDF date string.
  2403. // JavaScript's Date object expects the month to be between
  2404. // 0 and 11 instead of 1 and 12, so we're correcting for this.
  2405. var year = parseInt(dateToParse.substring(0,4), 10);
  2406. var month = parseInt(dateToParse.substring(4,6), 10) - 1;
  2407. var day = parseInt(dateToParse.substring(6,8), 10);
  2408. var hours = parseInt(dateToParse.substring(8,10), 10);
  2409. var minutes = parseInt(dateToParse.substring(10,12), 10);
  2410. var seconds = parseInt(dateToParse.substring(12,14), 10);
  2411. var utRel = dateToParse.substring(14,15);
  2412. var offsetHours = parseInt(dateToParse.substring(15,17), 10);
  2413. var offsetMinutes = parseInt(dateToParse.substring(18,20), 10);
  2414. // As per spec, utRel = 'Z' means equal to universal time.
  2415. // The other cases ('-' and '+') have to be handled here.
  2416. if (utRel === '-') {
  2417. hours += offsetHours;
  2418. minutes += offsetMinutes;
  2419. } else if (utRel === '+') {
  2420. hours -= offsetHours;
  2421. minutes -= offsetMinutes;
  2422. }
  2423. // Return the new date format from the user's locale.
  2424. var date = new Date(Date.UTC(year, month, day, hours, minutes, seconds));
  2425. var dateString = date.toLocaleDateString();
  2426. var timeString = date.toLocaleTimeString();
  2427. return mozL10n.get('document_properties_date_string',
  2428. {date: dateString, time: timeString},
  2429. '{{date}}, {{time}}');
  2430. }
  2431. };
  2432. var PresentationModeState = {
  2433. UNKNOWN: 0,
  2434. NORMAL: 1,
  2435. CHANGING: 2,
  2436. FULLSCREEN: 3,
  2437. };
  2438. var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
  2439. var CLEANUP_TIMEOUT = 30000;
  2440. var RenderingStates = {
  2441. INITIAL: 0,
  2442. RUNNING: 1,
  2443. PAUSED: 2,
  2444. FINISHED: 3
  2445. };
  2446. /**
  2447. * Controls rendering of the views for pages and thumbnails.
  2448. * @class
  2449. */
  2450. var PDFRenderingQueue = (function PDFRenderingQueueClosure() {
  2451. /**
  2452. * @constructs
  2453. */
  2454. function PDFRenderingQueue() {
  2455. this.pdfViewer = null;
  2456. this.pdfThumbnailViewer = null;
  2457. this.onIdle = null;
  2458. this.highestPriorityPage = null;
  2459. this.idleTimeout = null;
  2460. this.printing = false;
  2461. this.isThumbnailViewEnabled = false;
  2462. }
  2463. PDFRenderingQueue.prototype = /** @lends PDFRenderingQueue.prototype */ {
  2464. /**
  2465. * @param {PDFViewer} pdfViewer
  2466. */
  2467. setViewer: function PDFRenderingQueue_setViewer(pdfViewer) {
  2468. this.pdfViewer = pdfViewer;
  2469. },
  2470. /**
  2471. * @param {PDFThumbnailViewer} pdfThumbnailViewer
  2472. */
  2473. setThumbnailViewer:
  2474. function PDFRenderingQueue_setThumbnailViewer(pdfThumbnailViewer) {
  2475. this.pdfThumbnailViewer = pdfThumbnailViewer;
  2476. },
  2477. /**
  2478. * @param {IRenderableView} view
  2479. * @returns {boolean}
  2480. */
  2481. isHighestPriority: function PDFRenderingQueue_isHighestPriority(view) {
  2482. return this.highestPriorityPage === view.renderingId;
  2483. },
  2484. renderHighestPriority: function
  2485. PDFRenderingQueue_renderHighestPriority(currentlyVisiblePages) {
  2486. if (this.idleTimeout) {
  2487. clearTimeout(this.idleTimeout);
  2488. this.idleTimeout = null;
  2489. }
  2490. // Pages have a higher priority than thumbnails, so check them first.
  2491. if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
  2492. return;
  2493. }
  2494. // No pages needed rendering so check thumbnails.
  2495. if (this.pdfThumbnailViewer && this.isThumbnailViewEnabled) {
  2496. if (this.pdfThumbnailViewer.forceRendering()) {
  2497. return;
  2498. }
  2499. }
  2500. if (this.printing) {
  2501. // If printing is currently ongoing do not reschedule cleanup.
  2502. return;
  2503. }
  2504. if (this.onIdle) {
  2505. this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
  2506. }
  2507. },
  2508. getHighestPriority: function
  2509. PDFRenderingQueue_getHighestPriority(visible, views, scrolledDown) {
  2510. // The state has changed figure out which page has the highest priority to
  2511. // render next (if any).
  2512. // Priority:
  2513. // 1 visible pages
  2514. // 2 if last scrolled down page after the visible pages
  2515. // 2 if last scrolled up page before the visible pages
  2516. var visibleViews = visible.views;
  2517. var numVisible = visibleViews.length;
  2518. if (numVisible === 0) {
  2519. return false;
  2520. }
  2521. for (var i = 0; i < numVisible; ++i) {
  2522. var view = visibleViews[i].view;
  2523. if (!this.isViewFinished(view)) {
  2524. return view;
  2525. }
  2526. }
  2527. // All the visible views have rendered, try to render next/previous pages.
  2528. if (scrolledDown) {
  2529. var nextPageIndex = visible.last.id;
  2530. // ID's start at 1 so no need to add 1.
  2531. if (views[nextPageIndex] &&
  2532. !this.isViewFinished(views[nextPageIndex])) {
  2533. return views[nextPageIndex];
  2534. }
  2535. } else {
  2536. var previousPageIndex = visible.first.id - 2;
  2537. if (views[previousPageIndex] &&
  2538. !this.isViewFinished(views[previousPageIndex])) {
  2539. return views[previousPageIndex];
  2540. }
  2541. }
  2542. // Everything that needs to be rendered has been.
  2543. return null;
  2544. },
  2545. /**
  2546. * @param {IRenderableView} view
  2547. * @returns {boolean}
  2548. */
  2549. isViewFinished: function PDFRenderingQueue_isViewFinished(view) {
  2550. return view.renderingState === RenderingStates.FINISHED;
  2551. },
  2552. /**
  2553. * Render a page or thumbnail view. This calls the appropriate function
  2554. * based on the views state. If the view is already rendered it will return
  2555. * false.
  2556. * @param {IRenderableView} view
  2557. */
  2558. renderView: function PDFRenderingQueue_renderView(view) {
  2559. var state = view.renderingState;
  2560. switch (state) {
  2561. case RenderingStates.FINISHED:
  2562. return false;
  2563. case RenderingStates.PAUSED:
  2564. this.highestPriorityPage = view.renderingId;
  2565. view.resume();
  2566. break;
  2567. case RenderingStates.RUNNING:
  2568. this.highestPriorityPage = view.renderingId;
  2569. break;
  2570. case RenderingStates.INITIAL:
  2571. this.highestPriorityPage = view.renderingId;
  2572. view.draw(this.renderHighestPriority.bind(this));
  2573. break;
  2574. }
  2575. return true;
  2576. },
  2577. };
  2578. return PDFRenderingQueue;
  2579. })();
  2580. /**
  2581. * @constructor
  2582. * @param {HTMLDivElement} container - The viewer element.
  2583. * @param {number} id - The page unique ID (normally its number).
  2584. * @param {number} scale - The page scale display.
  2585. * @param {PageViewport} defaultViewport - The page viewport.
  2586. * @param {IPDFLinkService} linkService - The navigation/linking service.
  2587. * @param {PDFRenderingQueue} renderingQueue - The rendering queue object.
  2588. * @param {Cache} cache - The page cache.
  2589. * @param {PDFPageSource} pageSource
  2590. * @param {PDFViewer} viewer
  2591. *
  2592. * @implements {IRenderableView}
  2593. */
  2594. var PageView = function pageView(container, id, scale, defaultViewport,
  2595. linkService, renderingQueue, cache,
  2596. pageSource, viewer) {
  2597. this.id = id;
  2598. this.renderingId = 'page' + id;
  2599. this.rotation = 0;
  2600. this.scale = scale || 1.0;
  2601. this.viewport = defaultViewport;
  2602. this.pdfPageRotate = defaultViewport.rotation;
  2603. this.hasRestrictedScaling = false;
  2604. this.linkService = linkService;
  2605. this.renderingQueue = renderingQueue;
  2606. this.cache = cache;
  2607. this.pageSource = pageSource;
  2608. this.viewer = viewer;
  2609. this.renderingState = RenderingStates.INITIAL;
  2610. this.resume = null;
  2611. this.textLayer = null;
  2612. this.zoomLayer = null;
  2613. this.annotationLayer = null;
  2614. var anchor = document.createElement('a');
  2615. anchor.name = '' + this.id;
  2616. var div = this.el = document.createElement('div');
  2617. div.id = 'pageContainer' + this.id;
  2618. div.className = 'page';
  2619. div.style.width = Math.floor(this.viewport.width) + 'px';
  2620. div.style.height = Math.floor(this.viewport.height) + 'px';
  2621. container.appendChild(anchor);
  2622. container.appendChild(div);
  2623. this.setPdfPage = function pageViewSetPdfPage(pdfPage) {
  2624. this.pdfPage = pdfPage;
  2625. this.pdfPageRotate = pdfPage.rotate;
  2626. var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
  2627. this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation);
  2628. this.stats = pdfPage.stats;
  2629. this.reset();
  2630. };
  2631. this.destroy = function pageViewDestroy() {
  2632. this.zoomLayer = null;
  2633. this.reset();
  2634. if (this.pdfPage) {
  2635. this.pdfPage.destroy();
  2636. }
  2637. };
  2638. this.reset = function pageViewReset(keepAnnotations) {
  2639. if (this.renderTask) {
  2640. this.renderTask.cancel();
  2641. }
  2642. this.resume = null;
  2643. this.renderingState = RenderingStates.INITIAL;
  2644. div.style.width = Math.floor(this.viewport.width) + 'px';
  2645. div.style.height = Math.floor(this.viewport.height) + 'px';
  2646. var childNodes = div.childNodes;
  2647. for (var i = div.childNodes.length - 1; i >= 0; i--) {
  2648. var node = childNodes[i];
  2649. if ((this.zoomLayer && this.zoomLayer === node) ||
  2650. (keepAnnotations && this.annotationLayer === node)) {
  2651. continue;
  2652. }
  2653. div.removeChild(node);
  2654. }
  2655. div.removeAttribute('data-loaded');
  2656. if (keepAnnotations) {
  2657. if (this.annotationLayer) {
  2658. // Hide annotationLayer until all elements are resized
  2659. // so they are not displayed on the already-resized page
  2660. this.annotationLayer.setAttribute('hidden', 'true');
  2661. }
  2662. } else {
  2663. this.annotationLayer = null;
  2664. }
  2665. if (this.canvas) {
  2666. // Zeroing the width and height causes Firefox to release graphics
  2667. // resources immediately, which can greatly reduce memory consumption.
  2668. this.canvas.width = 0;
  2669. this.canvas.height = 0;
  2670. delete this.canvas;
  2671. }
  2672. this.loadingIconDiv = document.createElement('div');
  2673. this.loadingIconDiv.className = 'loadingIcon';
  2674. div.appendChild(this.loadingIconDiv);
  2675. };
  2676. this.update = function pageViewUpdate(scale, rotation) {
  2677. this.scale = scale || this.scale;
  2678. if (typeof rotation !== 'undefined') {
  2679. this.rotation = rotation;
  2680. }
  2681. var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
  2682. this.viewport = this.viewport.clone({
  2683. scale: this.scale * CSS_UNITS,
  2684. rotation: totalRotation
  2685. });
  2686. var isScalingRestricted = false;
  2687. if (this.canvas && PDFJS.maxCanvasPixels > 0) {
  2688. var ctx = this.canvas.getContext('2d');
  2689. var outputScale = getOutputScale(ctx);
  2690. var pixelsInViewport = this.viewport.width * this.viewport.height;
  2691. var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
  2692. if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
  2693. ((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
  2694. PDFJS.maxCanvasPixels) {
  2695. isScalingRestricted = true;
  2696. }
  2697. }
  2698. if (this.canvas &&
  2699. (PDFJS.useOnlyCssZoom ||
  2700. (this.hasRestrictedScaling && isScalingRestricted))) {
  2701. this.cssTransform(this.canvas, true);
  2702. return;
  2703. } else if (this.canvas && !this.zoomLayer) {
  2704. this.zoomLayer = this.canvas.parentNode;
  2705. this.zoomLayer.style.position = 'absolute';
  2706. }
  2707. if (this.zoomLayer) {
  2708. this.cssTransform(this.zoomLayer.firstChild);
  2709. }
  2710. this.reset(true);
  2711. };
  2712. this.cssTransform = function pageCssTransform(canvas, redrawAnnotations) {
  2713. // Scale canvas, canvas wrapper, and page container.
  2714. var width = this.viewport.width;
  2715. var height = this.viewport.height;
  2716. canvas.style.width = canvas.parentNode.style.width = div.style.width =
  2717. Math.floor(width) + 'px';
  2718. canvas.style.height = canvas.parentNode.style.height = div.style.height =
  2719. Math.floor(height) + 'px';
  2720. // The canvas may have been originally rotated, so rotate relative to that.
  2721. var relativeRotation = this.viewport.rotation - canvas._viewport.rotation;
  2722. var absRotation = Math.abs(relativeRotation);
  2723. var scaleX = 1, scaleY = 1;
  2724. if (absRotation === 90 || absRotation === 270) {
  2725. // Scale x and y because of the rotation.
  2726. scaleX = height / width;
  2727. scaleY = width / height;
  2728. }
  2729. var cssTransform = 'rotate(' + relativeRotation + 'deg) ' +
  2730. 'scale(' + scaleX + ',' + scaleY + ')';
  2731. CustomStyle.setProp('transform', canvas, cssTransform);
  2732. if (this.textLayer) {
  2733. // Rotating the text layer is more complicated since the divs inside the
  2734. // the text layer are rotated.
  2735. // TODO: This could probably be simplified by drawing the text layer in
  2736. // one orientation then rotating overall.
  2737. var textLayerViewport = this.textLayer.viewport;
  2738. var textRelativeRotation = this.viewport.rotation -
  2739. textLayerViewport.rotation;
  2740. var textAbsRotation = Math.abs(textRelativeRotation);
  2741. var scale = width / textLayerViewport.width;
  2742. if (textAbsRotation === 90 || textAbsRotation === 270) {
  2743. scale = width / textLayerViewport.height;
  2744. }
  2745. var textLayerDiv = this.textLayer.textLayerDiv;
  2746. var transX, transY;
  2747. switch (textAbsRotation) {
  2748. case 0:
  2749. transX = transY = 0;
  2750. break;
  2751. case 90:
  2752. transX = 0;
  2753. transY = '-' + textLayerDiv.style.height;
  2754. break;
  2755. case 180:
  2756. transX = '-' + textLayerDiv.style.width;
  2757. transY = '-' + textLayerDiv.style.height;
  2758. break;
  2759. case 270:
  2760. transX = '-' + textLayerDiv.style.width;
  2761. transY = 0;
  2762. break;
  2763. default:
  2764. console.error('Bad rotation value.');
  2765. break;
  2766. }
  2767. CustomStyle.setProp('transform', textLayerDiv,
  2768. 'rotate(' + textAbsRotation + 'deg) ' +
  2769. 'scale(' + scale + ', ' + scale + ') ' +
  2770. 'translate(' + transX + ', ' + transY + ')');
  2771. CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
  2772. }
  2773. if (redrawAnnotations && this.annotationLayer) {
  2774. setupAnnotations(div, this.pdfPage, this.viewport);
  2775. }
  2776. };
  2777. Object.defineProperty(this, 'width', {
  2778. get: function PageView_getWidth() {
  2779. return this.viewport.width;
  2780. },
  2781. enumerable: true
  2782. });
  2783. Object.defineProperty(this, 'height', {
  2784. get: function PageView_getHeight() {
  2785. return this.viewport.height;
  2786. },
  2787. enumerable: true
  2788. });
  2789. var self = this;
  2790. function setupAnnotations(pageDiv, pdfPage, viewport) {
  2791. function bindLink(link, dest) {
  2792. link.href = linkService.getDestinationHash(dest);
  2793. link.onclick = function pageViewSetupLinksOnclick() {
  2794. if (dest) {
  2795. linkService.navigateTo(dest);
  2796. }
  2797. return false;
  2798. };
  2799. if (dest) {
  2800. link.className = 'internalLink';
  2801. }
  2802. }
  2803. function bindNamedAction(link, action) {
  2804. link.href = linkService.getAnchorUrl('');
  2805. link.onclick = function pageViewSetupNamedActionOnClick() {
  2806. linkService.executeNamedAction(action);
  2807. return false;
  2808. };
  2809. link.className = 'internalLink';
  2810. }
  2811. pdfPage.getAnnotations().then(function(annotationsData) {
  2812. viewport = viewport.clone({ dontFlip: true });
  2813. var transform = viewport.transform;
  2814. var transformStr = 'matrix(' + transform.join(',') + ')';
  2815. var data, element, i, ii;
  2816. if (self.annotationLayer) {
  2817. // If an annotationLayer already exists, refresh its children's
  2818. // transformation matrices
  2819. for (i = 0, ii = annotationsData.length; i < ii; i++) {
  2820. data = annotationsData[i];
  2821. element = self.annotationLayer.querySelector(
  2822. '[data-annotation-id="' + data.id + '"]');
  2823. if (element) {
  2824. CustomStyle.setProp('transform', element, transformStr);
  2825. }
  2826. }
  2827. // See this.reset()
  2828. self.annotationLayer.removeAttribute('hidden');
  2829. } else {
  2830. for (i = 0, ii = annotationsData.length; i < ii; i++) {
  2831. data = annotationsData[i];
  2832. if (!data || !data.hasHtml) {
  2833. continue;
  2834. }
  2835. element = PDFJS.AnnotationUtils.getHtmlElement(data,
  2836. pdfPage.commonObjs);
  2837. element.setAttribute('data-annotation-id', data.id);
  2838. mozL10n.translate(element);
  2839. var rect = data.rect;
  2840. var view = pdfPage.view;
  2841. rect = PDFJS.Util.normalizeRect([
  2842. rect[0],
  2843. view[3] - rect[1] + view[1],
  2844. rect[2],
  2845. view[3] - rect[3] + view[1]
  2846. ]);
  2847. element.style.left = rect[0] + 'px';
  2848. element.style.top = rect[1] + 'px';
  2849. element.style.position = 'absolute';
  2850. CustomStyle.setProp('transform', element, transformStr);
  2851. var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
  2852. CustomStyle.setProp('transformOrigin', element, transformOriginStr);
  2853. if (data.subtype === 'Link' && !data.url) {
  2854. var link = element.getElementsByTagName('a')[0];
  2855. if (link) {
  2856. if (data.action) {
  2857. bindNamedAction(link, data.action);
  2858. } else {
  2859. bindLink(link, ('dest' in data) ? data.dest : null);
  2860. }
  2861. }
  2862. }
  2863. if (!self.annotationLayer) {
  2864. var annotationLayerDiv = document.createElement('div');
  2865. annotationLayerDiv.className = 'annotationLayer';
  2866. pageDiv.appendChild(annotationLayerDiv);
  2867. self.annotationLayer = annotationLayerDiv;
  2868. }
  2869. self.annotationLayer.appendChild(element);
  2870. }
  2871. }
  2872. });
  2873. }
  2874. this.getPagePoint = function pageViewGetPagePoint(x, y) {
  2875. return this.viewport.convertToPdfPoint(x, y);
  2876. };
  2877. this.draw = function pageviewDraw(callback) {
  2878. var pdfPage = this.pdfPage;
  2879. if (this.pagePdfPromise) {
  2880. return;
  2881. }
  2882. if (!pdfPage) {
  2883. var promise = this.pageSource.getPage();
  2884. promise.then(function(pdfPage) {
  2885. delete this.pagePdfPromise;
  2886. this.setPdfPage(pdfPage);
  2887. this.draw(callback);
  2888. }.bind(this));
  2889. this.pagePdfPromise = promise;
  2890. return;
  2891. }
  2892. if (this.renderingState !== RenderingStates.INITIAL) {
  2893. console.error('Must be in new state before drawing');
  2894. }
  2895. this.renderingState = RenderingStates.RUNNING;
  2896. var viewport = this.viewport;
  2897. // Wrap the canvas so if it has a css transform for highdpi the overflow
  2898. // will be hidden in FF.
  2899. var canvasWrapper = document.createElement('div');
  2900. canvasWrapper.style.width = div.style.width;
  2901. canvasWrapper.style.height = div.style.height;
  2902. canvasWrapper.classList.add('canvasWrapper');
  2903. var canvas = document.createElement('canvas');
  2904. canvas.id = 'page' + this.id;
  2905. canvasWrapper.appendChild(canvas);
  2906. if (this.annotationLayer) {
  2907. // annotationLayer needs to stay on top
  2908. div.insertBefore(canvasWrapper, this.annotationLayer);
  2909. } else {
  2910. div.appendChild(canvasWrapper);
  2911. }
  2912. this.canvas = canvas;
  2913. var ctx = canvas.getContext('2d');
  2914. var outputScale = getOutputScale(ctx);
  2915. if (PDFJS.useOnlyCssZoom) {
  2916. var actualSizeViewport = viewport.clone({ scale: CSS_UNITS });
  2917. // Use a scale that will make the canvas be the original intended size
  2918. // of the page.
  2919. outputScale.sx *= actualSizeViewport.width / viewport.width;
  2920. outputScale.sy *= actualSizeViewport.height / viewport.height;
  2921. outputScale.scaled = true;
  2922. }
  2923. if (PDFJS.maxCanvasPixels > 0) {
  2924. var pixelsInViewport = viewport.width * viewport.height;
  2925. var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
  2926. if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
  2927. outputScale.sx = maxScale;
  2928. outputScale.sy = maxScale;
  2929. outputScale.scaled = true;
  2930. this.hasRestrictedScaling = true;
  2931. } else {
  2932. this.hasRestrictedScaling = false;
  2933. }
  2934. }
  2935. canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0;
  2936. canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0;
  2937. canvas.style.width = Math.floor(viewport.width) + 'px';
  2938. canvas.style.height = Math.floor(viewport.height) + 'px';
  2939. // Add the viewport so it's known what it was originally drawn with.
  2940. canvas._viewport = viewport;
  2941. var textLayerDiv = null;
  2942. var textLayer = null;
  2943. if (!PDFJS.disableTextLayer) {
  2944. textLayerDiv = document.createElement('div');
  2945. textLayerDiv.className = 'textLayer';
  2946. textLayerDiv.style.width = canvas.style.width;
  2947. textLayerDiv.style.height = canvas.style.height;
  2948. if (this.annotationLayer) {
  2949. // annotationLayer needs to stay on top
  2950. div.insertBefore(textLayerDiv, this.annotationLayer);
  2951. } else {
  2952. div.appendChild(textLayerDiv);
  2953. }
  2954. textLayer = this.viewer.createTextLayerBuilder(textLayerDiv, this.id - 1,
  2955. this.viewport);
  2956. }
  2957. this.textLayer = textLayer;
  2958. // TODO(mack): use data attributes to store these
  2959. ctx._scaleX = outputScale.sx;
  2960. ctx._scaleY = outputScale.sy;
  2961. if (outputScale.scaled) {
  2962. ctx.scale(outputScale.sx, outputScale.sy);
  2963. }
  2964. // Rendering area
  2965. var self = this;
  2966. function pageViewDrawCallback(error) {
  2967. // The renderTask may have been replaced by a new one, so only remove the
  2968. // reference to the renderTask if it matches the one that is triggering
  2969. // this callback.
  2970. if (renderTask === self.renderTask) {
  2971. self.renderTask = null;
  2972. }
  2973. if (error === 'cancelled') {
  2974. return;
  2975. }
  2976. self.renderingState = RenderingStates.FINISHED;
  2977. if (self.loadingIconDiv) {
  2978. div.removeChild(self.loadingIconDiv);
  2979. delete self.loadingIconDiv;
  2980. }
  2981. if (self.zoomLayer) {
  2982. div.removeChild(self.zoomLayer);
  2983. self.zoomLayer = null;
  2984. }
  2985. self.error = error;
  2986. self.stats = pdfPage.stats;
  2987. self.updateStats();
  2988. if (self.onAfterDraw) {
  2989. self.onAfterDraw();
  2990. }
  2991. var event = document.createEvent('CustomEvent');
  2992. event.initCustomEvent('pagerender', true, true, {
  2993. pageNumber: pdfPage.pageNumber
  2994. });
  2995. div.dispatchEvent(event);
  2996. callback();
  2997. }
  2998. var renderContext = {
  2999. canvasContext: ctx,
  3000. viewport: this.viewport,
  3001. // intent: 'default', // === 'display'
  3002. continueCallback: function pdfViewcContinueCallback(cont) {
  3003. if (!self.renderingQueue.isHighestPriority(self)) {
  3004. self.renderingState = RenderingStates.PAUSED;
  3005. self.resume = function resumeCallback() {
  3006. self.renderingState = RenderingStates.RUNNING;
  3007. cont();
  3008. };
  3009. return;
  3010. }
  3011. cont();
  3012. }
  3013. };
  3014. var renderTask = this.renderTask = this.pdfPage.render(renderContext);
  3015. this.renderTask.promise.then(
  3016. function pdfPageRenderCallback() {
  3017. pageViewDrawCallback(null);
  3018. if (textLayer) {
  3019. self.pdfPage.getTextContent().then(
  3020. function textContentResolved(textContent) {
  3021. textLayer.setTextContent(textContent);
  3022. }
  3023. );
  3024. }
  3025. },
  3026. function pdfPageRenderError(error) {
  3027. pageViewDrawCallback(error);
  3028. }
  3029. );
  3030. setupAnnotations(div, pdfPage, this.viewport);
  3031. div.setAttribute('data-loaded', true);
  3032. // Add the page to the cache at the start of drawing. That way it can be
  3033. // evicted from the cache and destroyed even if we pause its rendering.
  3034. cache.push(this);
  3035. };
  3036. this.beforePrint = function pageViewBeforePrint() {
  3037. var pdfPage = this.pdfPage;
  3038. var viewport = pdfPage.getViewport(1);
  3039. // Use the same hack we use for high dpi displays for printing to get better
  3040. // output until bug 811002 is fixed in FF.
  3041. var PRINT_OUTPUT_SCALE = 2;
  3042. var canvas = document.createElement('canvas');
  3043. canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
  3044. canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
  3045. canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt';
  3046. canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt';
  3047. var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
  3048. (1 / PRINT_OUTPUT_SCALE) + ')';
  3049. CustomStyle.setProp('transform' , canvas, cssScale);
  3050. CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
  3051. var printContainer = document.getElementById('printContainer');
  3052. var canvasWrapper = document.createElement('div');
  3053. canvasWrapper.style.width = viewport.width + 'pt';
  3054. canvasWrapper.style.height = viewport.height + 'pt';
  3055. canvasWrapper.appendChild(canvas);
  3056. printContainer.appendChild(canvasWrapper);
  3057. canvas.mozPrintCallback = function(obj) {
  3058. var ctx = obj.context;
  3059. ctx.save();
  3060. ctx.fillStyle = 'rgb(255, 255, 255)';
  3061. ctx.fillRect(0, 0, canvas.width, canvas.height);
  3062. ctx.restore();
  3063. ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
  3064. var renderContext = {
  3065. canvasContext: ctx,
  3066. viewport: viewport,
  3067. intent: 'print'
  3068. };
  3069. pdfPage.render(renderContext).promise.then(function() {
  3070. // Tell the printEngine that rendering this canvas/page has finished.
  3071. obj.done();
  3072. }, function(error) {
  3073. console.error(error);
  3074. // Tell the printEngine that rendering this canvas/page has failed.
  3075. // This will make the print proces stop.
  3076. if ('abort' in obj) {
  3077. obj.abort();
  3078. } else {
  3079. obj.done();
  3080. }
  3081. });
  3082. };
  3083. };
  3084. this.updateStats = function pageViewUpdateStats() {
  3085. if (!this.stats) {
  3086. return;
  3087. }
  3088. if (PDFJS.pdfBug && Stats.enabled) {
  3089. var stats = this.stats;
  3090. Stats.add(this.id, stats);
  3091. }
  3092. };
  3093. };
  3094. var FIND_SCROLL_OFFSET_TOP = -50;
  3095. var FIND_SCROLL_OFFSET_LEFT = -400;
  3096. var MAX_TEXT_DIVS_TO_RENDER = 100000;
  3097. var RENDER_DELAY = 200; // ms
  3098. var NonWhitespaceRegexp = /\S/;
  3099. function isAllWhitespace(str) {
  3100. return !NonWhitespaceRegexp.test(str);
  3101. }
  3102. /**
  3103. * @typedef {Object} TextLayerBuilderOptions
  3104. * @property {HTMLDivElement} textLayerDiv - The text layer container.
  3105. * @property {number} pageIndex - The page index.
  3106. * @property {PageViewport} viewport - The viewport of the text layer.
  3107. * @property {ILastScrollSource} lastScrollSource - The object that records when
  3108. * last time scroll happened.
  3109. * @property {boolean} isViewerInPresentationMode
  3110. * @property {PDFFindController} findController
  3111. */
  3112. /**
  3113. * TextLayerBuilder provides text-selection functionality for the PDF.
  3114. * It does this by creating overlay divs over the PDF text. These divs
  3115. * contain text that matches the PDF text they are overlaying. This object
  3116. * also provides a way to highlight text that is being searched for.
  3117. * @class
  3118. */
  3119. var TextLayerBuilder = (function TextLayerBuilderClosure() {
  3120. function TextLayerBuilder(options) {
  3121. this.textLayerDiv = options.textLayerDiv;
  3122. this.layoutDone = false;
  3123. this.divContentDone = false;
  3124. this.pageIdx = options.pageIndex;
  3125. this.matches = [];
  3126. this.lastScrollSource = options.lastScrollSource || null;
  3127. this.viewport = options.viewport;
  3128. this.isViewerInPresentationMode = options.isViewerInPresentationMode;
  3129. this.textDivs = [];
  3130. this.findController = options.findController || null;
  3131. }
  3132. TextLayerBuilder.prototype = {
  3133. renderLayer: function TextLayerBuilder_renderLayer() {
  3134. var textLayerFrag = document.createDocumentFragment();
  3135. var textDivs = this.textDivs;
  3136. var textDivsLength = textDivs.length;
  3137. var canvas = document.createElement('canvas');
  3138. var ctx = canvas.getContext('2d');
  3139. // No point in rendering many divs as it would make the browser
  3140. // unusable even after the divs are rendered.
  3141. if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
  3142. return;
  3143. }
  3144. var lastFontSize;
  3145. var lastFontFamily;
  3146. for (var i = 0; i < textDivsLength; i++) {
  3147. var textDiv = textDivs[i];
  3148. if (textDiv.dataset.isWhitespace !== undefined) {
  3149. continue;
  3150. }
  3151. var fontSize = textDiv.style.fontSize;
  3152. var fontFamily = textDiv.style.fontFamily;
  3153. // Only build font string and set to context if different from last.
  3154. if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
  3155. ctx.font = fontSize + ' ' + fontFamily;
  3156. lastFontSize = fontSize;
  3157. lastFontFamily = fontFamily;
  3158. }
  3159. var width = ctx.measureText(textDiv.textContent).width;
  3160. if (width > 0) {
  3161. textLayerFrag.appendChild(textDiv);
  3162. var transform;
  3163. if (textDiv.dataset.canvasWidth !== undefined) {
  3164. // Dataset values come of type string.
  3165. var textScale = textDiv.dataset.canvasWidth / width;
  3166. transform = 'scaleX(' + textScale + ')';
  3167. } else {
  3168. transform = '';
  3169. }
  3170. var rotation = textDiv.dataset.angle;
  3171. if (rotation) {
  3172. transform = 'rotate(' + rotation + 'deg) ' + transform;
  3173. }
  3174. if (transform) {
  3175. CustomStyle.setProp('transform' , textDiv, transform);
  3176. }
  3177. }
  3178. }
  3179. this.textLayerDiv.appendChild(textLayerFrag);
  3180. this.renderingDone = true;
  3181. this.updateMatches();
  3182. },
  3183. setupRenderLayoutTimer:
  3184. function TextLayerBuilder_setupRenderLayoutTimer() {
  3185. // Schedule renderLayout() if the user has been scrolling,
  3186. // otherwise run it right away.
  3187. var self = this;
  3188. var lastScroll = (this.lastScrollSource === null ?
  3189. 0 : this.lastScrollSource.lastScroll);
  3190. if (Date.now() - lastScroll > RENDER_DELAY) { // Render right away
  3191. this.renderLayer();
  3192. } else { // Schedule
  3193. if (this.renderTimer) {
  3194. clearTimeout(this.renderTimer);
  3195. }
  3196. this.renderTimer = setTimeout(function() {
  3197. self.setupRenderLayoutTimer();
  3198. }, RENDER_DELAY);
  3199. }
  3200. },
  3201. appendText: function TextLayerBuilder_appendText(geom, styles) {
  3202. var style = styles[geom.fontName];
  3203. var textDiv = document.createElement('div');
  3204. this.textDivs.push(textDiv);
  3205. if (isAllWhitespace(geom.str)) {
  3206. textDiv.dataset.isWhitespace = true;
  3207. return;
  3208. }
  3209. var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform);
  3210. var angle = Math.atan2(tx[1], tx[0]);
  3211. if (style.vertical) {
  3212. angle += Math.PI / 2;
  3213. }
  3214. var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
  3215. var fontAscent = fontHeight;
  3216. if (style.ascent) {
  3217. fontAscent = style.ascent * fontAscent;
  3218. } else if (style.descent) {
  3219. fontAscent = (1 + style.descent) * fontAscent;
  3220. }
  3221. var left;
  3222. var top;
  3223. if (angle === 0) {
  3224. left = tx[4];
  3225. top = tx[5] - fontAscent;
  3226. } else {
  3227. left = tx[4] + (fontAscent * Math.sin(angle));
  3228. top = tx[5] - (fontAscent * Math.cos(angle));
  3229. }
  3230. textDiv.style.left = left + 'px';
  3231. textDiv.style.top = top + 'px';
  3232. textDiv.style.fontSize = fontHeight + 'px';
  3233. textDiv.style.fontFamily = style.fontFamily;
  3234. textDiv.textContent = geom.str;
  3235. // |fontName| is only used by the Font Inspector. This test will succeed
  3236. // when e.g. the Font Inspector is off but the Stepper is on, but it's
  3237. // not worth the effort to do a more accurate test.
  3238. if (PDFJS.pdfBug) {
  3239. textDiv.dataset.fontName = geom.fontName;
  3240. }
  3241. // Storing into dataset will convert number into string.
  3242. if (angle !== 0) {
  3243. textDiv.dataset.angle = angle * (180 / Math.PI);
  3244. }
  3245. // We don't bother scaling single-char text divs, because it has very
  3246. // little effect on text highlighting. This makes scrolling on docs with
  3247. // lots of such divs a lot faster.
  3248. if (textDiv.textContent.length > 1) {
  3249. if (style.vertical) {
  3250. textDiv.dataset.canvasWidth = geom.height * this.viewport.scale;
  3251. } else {
  3252. textDiv.dataset.canvasWidth = geom.width * this.viewport.scale;
  3253. }
  3254. }
  3255. },
  3256. setTextContent: function TextLayerBuilder_setTextContent(textContent) {
  3257. this.textContent = textContent;
  3258. var textItems = textContent.items;
  3259. for (var i = 0, len = textItems.length; i < len; i++) {
  3260. this.appendText(textItems[i], textContent.styles);
  3261. }
  3262. this.divContentDone = true;
  3263. this.setupRenderLayoutTimer();
  3264. },
  3265. convertMatches: function TextLayerBuilder_convertMatches(matches) {
  3266. var i = 0;
  3267. var iIndex = 0;
  3268. var bidiTexts = this.textContent.items;
  3269. var end = bidiTexts.length - 1;
  3270. var queryLen = (this.findController === null ?
  3271. 0 : this.findController.state.query.length);
  3272. var ret = [];
  3273. for (var m = 0, len = matches.length; m < len; m++) {
  3274. // Calculate the start position.
  3275. var matchIdx = matches[m];
  3276. // Loop over the divIdxs.
  3277. while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
  3278. iIndex += bidiTexts[i].str.length;
  3279. i++;
  3280. }
  3281. if (i === bidiTexts.length) {
  3282. console.error('Could not find a matching mapping');
  3283. }
  3284. var match = {
  3285. begin: {
  3286. divIdx: i,
  3287. offset: matchIdx - iIndex
  3288. }
  3289. };
  3290. // Calculate the end position.
  3291. matchIdx += queryLen;
  3292. // Somewhat the same array as above, but use > instead of >= to get
  3293. // the end position right.
  3294. while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) {
  3295. iIndex += bidiTexts[i].str.length;
  3296. i++;
  3297. }
  3298. match.end = {
  3299. divIdx: i,
  3300. offset: matchIdx - iIndex
  3301. };
  3302. ret.push(match);
  3303. }
  3304. return ret;
  3305. },
  3306. renderMatches: function TextLayerBuilder_renderMatches(matches) {
  3307. // Early exit if there is nothing to render.
  3308. if (matches.length === 0) {
  3309. return;
  3310. }
  3311. var bidiTexts = this.textContent.items;
  3312. var textDivs = this.textDivs;
  3313. var prevEnd = null;
  3314. var isSelectedPage = (this.findController === null ?
  3315. false : (this.pageIdx === this.findController.selected.pageIdx));
  3316. var selectedMatchIdx = (this.findController === null ?
  3317. -1 : this.findController.selected.matchIdx);
  3318. var highlightAll = (this.findController === null ?
  3319. false : this.findController.state.highlightAll);
  3320. var infinity = {
  3321. divIdx: -1,
  3322. offset: undefined
  3323. };
  3324. function beginText(begin, className) {
  3325. var divIdx = begin.divIdx;
  3326. textDivs[divIdx].textContent = '';
  3327. appendTextToDiv(divIdx, 0, begin.offset, className);
  3328. }
  3329. function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
  3330. var div = textDivs[divIdx];
  3331. var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset);
  3332. var node = document.createTextNode(content);
  3333. if (className) {
  3334. var span = document.createElement('span');
  3335. span.className = className;
  3336. span.appendChild(node);
  3337. div.appendChild(span);
  3338. return;
  3339. }
  3340. div.appendChild(node);
  3341. }
  3342. var i0 = selectedMatchIdx, i1 = i0 + 1;
  3343. if (highlightAll) {
  3344. i0 = 0;
  3345. i1 = matches.length;
  3346. } else if (!isSelectedPage) {
  3347. // Not highlighting all and this isn't the selected page, so do nothing.
  3348. return;
  3349. }
  3350. for (var i = i0; i < i1; i++) {
  3351. var match = matches[i];
  3352. var begin = match.begin;
  3353. var end = match.end;
  3354. var isSelected = (isSelectedPage && i === selectedMatchIdx);
  3355. var highlightSuffix = (isSelected ? ' selected' : '');
  3356. if (isSelected && !this.isViewerInPresentationMode) {
  3357. scrollIntoView(textDivs[begin.divIdx],
  3358. { top: FIND_SCROLL_OFFSET_TOP,
  3359. left: FIND_SCROLL_OFFSET_LEFT });
  3360. }
  3361. // Match inside new div.
  3362. if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
  3363. // If there was a previous div, then add the text at the end.
  3364. if (prevEnd !== null) {
  3365. appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
  3366. }
  3367. // Clear the divs and set the content until the starting point.
  3368. beginText(begin);
  3369. } else {
  3370. appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
  3371. }
  3372. if (begin.divIdx === end.divIdx) {
  3373. appendTextToDiv(begin.divIdx, begin.offset, end.offset,
  3374. 'highlight' + highlightSuffix);
  3375. } else {
  3376. appendTextToDiv(begin.divIdx, begin.offset, infinity.offset,
  3377. 'highlight begin' + highlightSuffix);
  3378. for (var n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
  3379. textDivs[n0].className = 'highlight middle' + highlightSuffix;
  3380. }
  3381. beginText(end, 'highlight end' + highlightSuffix);
  3382. }
  3383. prevEnd = end;
  3384. }
  3385. if (prevEnd) {
  3386. appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
  3387. }
  3388. },
  3389. updateMatches: function TextLayerBuilder_updateMatches() {
  3390. // Only show matches when all rendering is done.
  3391. if (!this.renderingDone) {
  3392. return;
  3393. }
  3394. // Clear all matches.
  3395. var matches = this.matches;
  3396. var textDivs = this.textDivs;
  3397. var bidiTexts = this.textContent.items;
  3398. var clearedUntilDivIdx = -1;
  3399. // Clear all current matches.
  3400. for (var i = 0, len = matches.length; i < len; i++) {
  3401. var match = matches[i];
  3402. var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
  3403. for (var n = begin, end = match.end.divIdx; n <= end; n++) {
  3404. var div = textDivs[n];
  3405. div.textContent = bidiTexts[n].str;
  3406. div.className = '';
  3407. }
  3408. clearedUntilDivIdx = match.end.divIdx + 1;
  3409. }
  3410. if (this.findController === null || !this.findController.active) {
  3411. return;
  3412. }
  3413. // Convert the matches on the page controller into the match format
  3414. // used for the textLayer.
  3415. this.matches = this.convertMatches(this.findController === null ?
  3416. [] : (this.findController.pageMatches[this.pageIdx] || []));
  3417. this.renderMatches(this.matches);
  3418. }
  3419. };
  3420. return TextLayerBuilder;
  3421. })();
  3422. /**
  3423. * @typedef {Object} PDFViewerOptions
  3424. * @property {HTMLDivElement} container - The container for the viewer element.
  3425. * @property {HTMLDivElement} viewer - (optional) The viewer element.
  3426. * @property {IPDFLinkService} linkService - The navigation/linking service.
  3427. * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
  3428. * queue object.
  3429. */
  3430. /**
  3431. * Simple viewer control to display PDF content/pages.
  3432. * @class
  3433. * @implements {ILastScrollSource}
  3434. * @implements {IRenderableView}
  3435. */
  3436. var PDFViewer = (function pdfViewer() {
  3437. /**
  3438. * @constructs PDFViewer
  3439. * @param {PDFViewerOptions} options
  3440. */
  3441. function PDFViewer(options) {
  3442. this.container = options.container;
  3443. this.viewer = options.viewer || options.container.firstElementChild;
  3444. this.linkService = options.linkService || new SimpleLinkService(this);
  3445. this.defaultRenderingQueue = !options.renderingQueue;
  3446. if (this.defaultRenderingQueue) {
  3447. // Custom rendering queue is not specified, using default one
  3448. this.renderingQueue = new PDFRenderingQueue();
  3449. this.renderingQueue.setViewer(this);
  3450. } else {
  3451. this.renderingQueue = options.renderingQueue;
  3452. }
  3453. this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
  3454. this.lastScroll = 0;
  3455. this.updateInProgress = false;
  3456. this.presentationModeState = PresentationModeState.UNKNOWN;
  3457. this._resetView();
  3458. }
  3459. PDFViewer.prototype = /** @lends PDFViewer.prototype */{
  3460. get pagesCount() {
  3461. return this.pages.length;
  3462. },
  3463. getPageView: function (index) {
  3464. return this.pages[index];
  3465. },
  3466. get currentPageNumber() {
  3467. return this._currentPageNumber;
  3468. },
  3469. set currentPageNumber(val) {
  3470. if (!this.pdfDocument) {
  3471. this._currentPageNumber = val;
  3472. return;
  3473. }
  3474. var event = document.createEvent('UIEvents');
  3475. event.initUIEvent('pagechange', true, true, window, 0);
  3476. event.updateInProgress = this.updateInProgress;
  3477. if (!(0 < val && val <= this.pagesCount)) {
  3478. event.pageNumber = this._currentPageNumber;
  3479. event.previousPageNumber = val;
  3480. this.container.dispatchEvent(event);
  3481. return;
  3482. }
  3483. this.pages[val - 1].updateStats();
  3484. event.previousPageNumber = this._currentPageNumber;
  3485. this._currentPageNumber = val;
  3486. event.pageNumber = val;
  3487. this.container.dispatchEvent(event);
  3488. },
  3489. /**
  3490. * @returns {number}
  3491. */
  3492. get currentScale() {
  3493. return this._currentScale;
  3494. },
  3495. /**
  3496. * @param {number} val - Scale of the pages in percents.
  3497. */
  3498. set currentScale(val) {
  3499. if (isNaN(val)) {
  3500. throw new Error('Invalid numeric scale');
  3501. }
  3502. if (!this.pdfDocument) {
  3503. this._currentScale = val;
  3504. this._currentScaleValue = val.toString();
  3505. return;
  3506. }
  3507. this._setScale(val, false);
  3508. },
  3509. /**
  3510. * @returns {string}
  3511. */
  3512. get currentScaleValue() {
  3513. return this._currentScaleValue;
  3514. },
  3515. /**
  3516. * @param val - The scale of the pages (in percent or predefined value).
  3517. */
  3518. set currentScaleValue(val) {
  3519. if (!this.pdfDocument) {
  3520. this._currentScale = isNaN(val) ? UNKNOWN_SCALE : val;
  3521. this._currentScaleValue = val;
  3522. return;
  3523. }
  3524. this._setScale(val, false);
  3525. },
  3526. /**
  3527. * @returns {number}
  3528. */
  3529. get pagesRotation() {
  3530. return this._pagesRotation;
  3531. },
  3532. /**
  3533. * @param {number} rotation - The rotation of the pages (0, 90, 180, 270).
  3534. */
  3535. set pagesRotation(rotation) {
  3536. this._pagesRotation = rotation;
  3537. for (var i = 0, l = this.pages.length; i < l; i++) {
  3538. var page = this.pages[i];
  3539. page.update(page.scale, rotation);
  3540. }
  3541. this._setScale(this._currentScaleValue, true);
  3542. },
  3543. /**
  3544. * @param pdfDocument {PDFDocument}
  3545. */
  3546. setDocument: function (pdfDocument) {
  3547. if (this.pdfDocument) {
  3548. this._resetView();
  3549. }
  3550. this.pdfDocument = pdfDocument;
  3551. if (!pdfDocument) {
  3552. return;
  3553. }
  3554. var pagesCount = pdfDocument.numPages;
  3555. var pagesRefMap = this.pagesRefMap = {};
  3556. var self = this;
  3557. var resolvePagesPromise;
  3558. var pagesPromise = new Promise(function (resolve) {
  3559. resolvePagesPromise = resolve;
  3560. });
  3561. this.pagesPromise = pagesPromise;
  3562. pagesPromise.then(function () {
  3563. var event = document.createEvent('CustomEvent');
  3564. event.initCustomEvent('pagesloaded', true, true, {
  3565. pagesCount: pagesCount
  3566. });
  3567. self.container.dispatchEvent(event);
  3568. });
  3569. var isOnePageRenderedResolved = false;
  3570. var resolveOnePageRendered = null;
  3571. var onePageRendered = new Promise(function (resolve) {
  3572. resolveOnePageRendered = resolve;
  3573. });
  3574. this.onePageRendered = onePageRendered;
  3575. var bindOnAfterDraw = function (pageView) {
  3576. // when page is painted, using the image as thumbnail base
  3577. pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
  3578. if (!isOnePageRenderedResolved) {
  3579. isOnePageRenderedResolved = true;
  3580. resolveOnePageRendered();
  3581. }
  3582. var event = document.createEvent('CustomEvent');
  3583. event.initCustomEvent('pagerendered', true, true, {
  3584. pageNumber: pageView.id
  3585. });
  3586. self.container.dispatchEvent(event);
  3587. };
  3588. };
  3589. var firstPagePromise = pdfDocument.getPage(1);
  3590. this.firstPagePromise = firstPagePromise;
  3591. // Fetch a single page so we can get a viewport that will be the default
  3592. // viewport for all pages
  3593. return firstPagePromise.then(function(pdfPage) {
  3594. var scale = this._currentScale || 1.0;
  3595. var viewport = pdfPage.getViewport(scale * CSS_UNITS);
  3596. for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
  3597. var pageSource = new PDFPageSource(pdfDocument, pageNum);
  3598. var pageView = new PageView(this.viewer, pageNum, scale,
  3599. viewport.clone(), this.linkService,
  3600. this.renderingQueue, this.cache,
  3601. pageSource, this);
  3602. bindOnAfterDraw(pageView);
  3603. this.pages.push(pageView);
  3604. }
  3605. // Fetch all the pages since the viewport is needed before printing
  3606. // starts to create the correct size canvas. Wait until one page is
  3607. // rendered so we don't tie up too many resources early on.
  3608. onePageRendered.then(function () {
  3609. if (!PDFJS.disableAutoFetch) {
  3610. var getPagesLeft = pagesCount;
  3611. for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
  3612. pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
  3613. var pageView = self.pages[pageNum - 1];
  3614. if (!pageView.pdfPage) {
  3615. pageView.setPdfPage(pdfPage);
  3616. }
  3617. var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
  3618. pagesRefMap[refStr] = pageNum;
  3619. getPagesLeft--;
  3620. if (!getPagesLeft) {
  3621. resolvePagesPromise();
  3622. }
  3623. }.bind(null, pageNum));
  3624. }
  3625. } else {
  3626. // XXX: Printing is semi-broken with auto fetch disabled.
  3627. resolvePagesPromise();
  3628. }
  3629. });
  3630. var event = document.createEvent('CustomEvent');
  3631. event.initCustomEvent('pagesinit', true, true, null);
  3632. self.container.dispatchEvent(event);
  3633. if (this.defaultRenderingQueue) {
  3634. this.update();
  3635. }
  3636. }.bind(this));
  3637. },
  3638. _resetView: function () {
  3639. this.cache = new Cache(DEFAULT_CACHE_SIZE);
  3640. this.pages = [];
  3641. this._currentPageNumber = 1;
  3642. this._currentScale = UNKNOWN_SCALE;
  3643. this._currentScaleValue = null;
  3644. this.location = null;
  3645. this._pagesRotation = 0;
  3646. var container = this.viewer;
  3647. while (container.hasChildNodes()) {
  3648. container.removeChild(container.lastChild);
  3649. }
  3650. },
  3651. _scrollUpdate: function () {
  3652. this.lastScroll = Date.now();
  3653. if (this.pagesCount === 0) {
  3654. return;
  3655. }
  3656. this.update();
  3657. },
  3658. _setScaleUpdatePages: function pdfViewer_setScaleUpdatePages(
  3659. newScale, newValue, noScroll, preset) {
  3660. this._currentScaleValue = newValue;
  3661. if (newScale === this._currentScale) {
  3662. return;
  3663. }
  3664. for (var i = 0, ii = this.pages.length; i < ii; i++) {
  3665. this.pages[i].update(newScale);
  3666. }
  3667. this._currentScale = newScale;
  3668. if (!noScroll) {
  3669. var page = this._currentPageNumber, dest;
  3670. var inPresentationMode =
  3671. this.presentationModeState === PresentationModeState.CHANGING ||
  3672. this.presentationModeState === PresentationModeState.FULLSCREEN;
  3673. if (this.location && !inPresentationMode &&
  3674. !IGNORE_CURRENT_POSITION_ON_ZOOM) {
  3675. page = this.location.pageNumber;
  3676. dest = [null, { name: 'XYZ' }, this.location.left,
  3677. this.location.top, null];
  3678. }
  3679. this.scrollPageIntoView(page, dest);
  3680. }
  3681. var event = document.createEvent('UIEvents');
  3682. event.initUIEvent('scalechange', true, true, window, 0);
  3683. event.scale = newScale;
  3684. if (preset) {
  3685. event.presetValue = newValue;
  3686. }
  3687. this.container.dispatchEvent(event);
  3688. },
  3689. _setScale: function pdfViewer_setScale(value, noScroll) {
  3690. if (value === 'custom') {
  3691. return;
  3692. }
  3693. var scale = parseFloat(value);
  3694. if (scale > 0) {
  3695. this._setScaleUpdatePages(scale, value, noScroll, false);
  3696. } else {
  3697. var currentPage = this.pages[this._currentPageNumber - 1];
  3698. if (!currentPage) {
  3699. return;
  3700. }
  3701. var inPresentationMode =
  3702. this.presentationModeState === PresentationModeState.FULLSCREEN;
  3703. var hPadding = inPresentationMode ? 0 : SCROLLBAR_PADDING;
  3704. var vPadding = inPresentationMode ? 0 : VERTICAL_PADDING;
  3705. var pageWidthScale = (this.container.clientWidth - hPadding) /
  3706. currentPage.width * currentPage.scale;
  3707. var pageHeightScale = (this.container.clientHeight - vPadding) /
  3708. currentPage.height * currentPage.scale;
  3709. switch (value) {
  3710. case 'page-actual':
  3711. scale = 1;
  3712. break;
  3713. case 'page-width':
  3714. scale = pageWidthScale;
  3715. break;
  3716. case 'page-height':
  3717. scale = pageHeightScale;
  3718. break;
  3719. case 'page-fit':
  3720. scale = Math.min(pageWidthScale, pageHeightScale);
  3721. break;
  3722. case 'auto':
  3723. var isLandscape = (currentPage.width > currentPage.height);
  3724. // For pages in landscape mode, fit the page height to the viewer
  3725. // *unless* the page would thus become too wide to fit horizontally.
  3726. var horizontalScale = isLandscape ?
  3727. Math.min(pageHeightScale, pageWidthScale) : pageWidthScale;
  3728. scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
  3729. break;
  3730. default:
  3731. console.error('pdfViewSetScale: \'' + value +
  3732. '\' is an unknown zoom value.');
  3733. return;
  3734. }
  3735. this._setScaleUpdatePages(scale, value, noScroll, true);
  3736. }
  3737. },
  3738. /**
  3739. * Scrolls page into view.
  3740. * @param {number} pageNumber
  3741. * @param {Array} dest - (optional) original PDF destination array:
  3742. * <page-ref> </XYZ|FitXXX> <args..>
  3743. */
  3744. scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber,
  3745. dest) {
  3746. var pageView = this.pages[pageNumber - 1];
  3747. var pageViewDiv = pageView.el;
  3748. if (this.presentationModeState ===
  3749. PresentationModeState.FULLSCREEN) {
  3750. if (this.linkService.page !== pageView.id) {
  3751. // Avoid breaking getVisiblePages in presentation mode.
  3752. this.linkService.page = pageView.id;
  3753. return;
  3754. }
  3755. dest = null;
  3756. // Fixes the case when PDF has different page sizes.
  3757. this._setScale(this.currentScaleValue, true);
  3758. }
  3759. if (!dest) {
  3760. scrollIntoView(pageViewDiv);
  3761. return;
  3762. }
  3763. var x = 0, y = 0;
  3764. var width = 0, height = 0, widthScale, heightScale;
  3765. var changeOrientation = (pageView.rotation % 180 === 0 ? false : true);
  3766. var pageWidth = (changeOrientation ? pageView.height : pageView.width) /
  3767. pageView.scale / CSS_UNITS;
  3768. var pageHeight = (changeOrientation ? pageView.width : pageView.height) /
  3769. pageView.scale / CSS_UNITS;
  3770. var scale = 0;
  3771. switch (dest[1].name) {
  3772. case 'XYZ':
  3773. x = dest[2];
  3774. y = dest[3];
  3775. scale = dest[4];
  3776. // If x and/or y coordinates are not supplied, default to
  3777. // _top_ left of the page (not the obvious bottom left,
  3778. // since aligning the bottom of the intended page with the
  3779. // top of the window is rarely helpful).
  3780. x = x !== null ? x : 0;
  3781. y = y !== null ? y : pageHeight;
  3782. break;
  3783. case 'Fit':
  3784. case 'FitB':
  3785. scale = 'page-fit';
  3786. break;
  3787. case 'FitH':
  3788. case 'FitBH':
  3789. y = dest[2];
  3790. scale = 'page-width';
  3791. break;
  3792. case 'FitV':
  3793. case 'FitBV':
  3794. x = dest[2];
  3795. width = pageWidth;
  3796. height = pageHeight;
  3797. scale = 'page-height';
  3798. break;
  3799. case 'FitR':
  3800. x = dest[2];
  3801. y = dest[3];
  3802. width = dest[4] - x;
  3803. height = dest[5] - y;
  3804. var viewerContainer = this.container;
  3805. widthScale = (viewerContainer.clientWidth - SCROLLBAR_PADDING) /
  3806. width / CSS_UNITS;
  3807. heightScale = (viewerContainer.clientHeight - SCROLLBAR_PADDING) /
  3808. height / CSS_UNITS;
  3809. scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
  3810. break;
  3811. default:
  3812. return;
  3813. }
  3814. if (scale && scale !== this.currentScale) {
  3815. this.currentScaleValue = scale;
  3816. } else if (this.currentScale === UNKNOWN_SCALE) {
  3817. this.currentScaleValue = DEFAULT_SCALE;
  3818. }
  3819. if (scale === 'page-fit' && !dest[4]) {
  3820. scrollIntoView(pageViewDiv);
  3821. return;
  3822. }
  3823. var boundingRect = [
  3824. pageView.viewport.convertToViewportPoint(x, y),
  3825. pageView.viewport.convertToViewportPoint(x + width, y + height)
  3826. ];
  3827. var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
  3828. var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
  3829. scrollIntoView(pageViewDiv, { left: left, top: top });
  3830. },
  3831. _updateLocation: function (firstPage) {
  3832. var currentScale = this._currentScale;
  3833. var currentScaleValue = this._currentScaleValue;
  3834. var normalizedScaleValue =
  3835. parseFloat(currentScaleValue) === currentScale ?
  3836. Math.round(currentScale * 10000) / 100 : currentScaleValue;
  3837. var pageNumber = firstPage.id;
  3838. var pdfOpenParams = '#page=' + pageNumber;
  3839. pdfOpenParams += '&zoom=' + normalizedScaleValue;
  3840. var currentPageView = this.pages[pageNumber - 1];
  3841. var container = this.container;
  3842. var topLeft = currentPageView.getPagePoint(
  3843. (container.scrollLeft - firstPage.x),
  3844. (container.scrollTop - firstPage.y));
  3845. var intLeft = Math.round(topLeft[0]);
  3846. var intTop = Math.round(topLeft[1]);
  3847. pdfOpenParams += ',' + intLeft + ',' + intTop;
  3848. this.location = {
  3849. pageNumber: pageNumber,
  3850. scale: normalizedScaleValue,
  3851. top: intTop,
  3852. left: intLeft,
  3853. pdfOpenParams: pdfOpenParams
  3854. };
  3855. },
  3856. update: function () {
  3857. var visible = this._getVisiblePages();
  3858. var visiblePages = visible.views;
  3859. if (visiblePages.length === 0) {
  3860. return;
  3861. }
  3862. this.updateInProgress = true;
  3863. var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
  3864. 2 * visiblePages.length + 1);
  3865. this.cache.resize(suggestedCacheSize);
  3866. this.renderingQueue.renderHighestPriority(visible);
  3867. var currentId = this.currentPageNumber;
  3868. var firstPage = visible.first;
  3869. for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
  3870. i < ii; ++i) {
  3871. var page = visiblePages[i];
  3872. if (page.percent < 100) {
  3873. break;
  3874. }
  3875. if (page.id === currentId) {
  3876. stillFullyVisible = true;
  3877. break;
  3878. }
  3879. }
  3880. if (!stillFullyVisible) {
  3881. currentId = visiblePages[0].id;
  3882. }
  3883. if (this.presentationModeState !== PresentationModeState.FULLSCREEN) {
  3884. this.currentPageNumber = currentId;
  3885. }
  3886. this._updateLocation(firstPage);
  3887. this.updateInProgress = false;
  3888. var event = document.createEvent('UIEvents');
  3889. event.initUIEvent('updateviewarea', true, true, window, 0);
  3890. this.container.dispatchEvent(event);
  3891. },
  3892. containsElement: function (element) {
  3893. return this.container.contains(element);
  3894. },
  3895. focus: function () {
  3896. this.container.focus();
  3897. },
  3898. blur: function () {
  3899. this.container.blur();
  3900. },
  3901. get isHorizontalScrollbarEnabled() {
  3902. return (this.presentationModeState === PresentationModeState.FULLSCREEN ?
  3903. false : (this.container.scrollWidth > this.container.clientWidth));
  3904. },
  3905. _getVisiblePages: function () {
  3906. if (this.presentationModeState !== PresentationModeState.FULLSCREEN) {
  3907. return getVisibleElements(this.container, this.pages, true);
  3908. } else {
  3909. // The algorithm in getVisibleElements doesn't work in all browsers and
  3910. // configurations when presentation mode is active.
  3911. var visible = [];
  3912. var currentPage = this.pages[this._currentPageNumber - 1];
  3913. visible.push({ id: currentPage.id, view: currentPage });
  3914. return { first: currentPage, last: currentPage, views: visible };
  3915. }
  3916. },
  3917. cleanup: function () {
  3918. for (var i = 0, ii = this.pages.length; i < ii; i++) {
  3919. if (this.pages[i] &&
  3920. this.pages[i].renderingState !== RenderingStates.FINISHED) {
  3921. this.pages[i].reset();
  3922. }
  3923. }
  3924. },
  3925. forceRendering: function (currentlyVisiblePages) {
  3926. var visiblePages = currentlyVisiblePages || this._getVisiblePages();
  3927. var pageView = this.renderingQueue.getHighestPriority(visiblePages,
  3928. this.pages,
  3929. this.scroll.down);
  3930. if (pageView) {
  3931. this.renderingQueue.renderView(pageView);
  3932. return true;
  3933. }
  3934. return false;
  3935. },
  3936. getPageTextContent: function (pageIndex) {
  3937. return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
  3938. return page.getTextContent();
  3939. });
  3940. },
  3941. /**
  3942. * @param textLayerDiv {HTMLDivElement}
  3943. * @param pageIndex {number}
  3944. * @param viewport {PageViewport}
  3945. * @returns {TextLayerBuilder}
  3946. */
  3947. createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
  3948. var isViewerInPresentationMode =
  3949. this.presentationModeState === PresentationModeState.FULLSCREEN;
  3950. return new TextLayerBuilder({
  3951. textLayerDiv: textLayerDiv,
  3952. pageIndex: pageIndex,
  3953. viewport: viewport,
  3954. lastScrollSource: this,
  3955. isViewerInPresentationMode: isViewerInPresentationMode,
  3956. findController: this.findController
  3957. });
  3958. },
  3959. setFindController: function (findController) {
  3960. this.findController = findController;
  3961. },
  3962. };
  3963. return PDFViewer;
  3964. })();
  3965. var SimpleLinkService = (function SimpleLinkServiceClosure() {
  3966. function SimpleLinkService(pdfViewer) {
  3967. this.pdfViewer = pdfViewer;
  3968. }
  3969. SimpleLinkService.prototype = {
  3970. /**
  3971. * @returns {number}
  3972. */
  3973. get page() {
  3974. return this.pdfViewer.currentPageNumber;
  3975. },
  3976. /**
  3977. * @param {number} value
  3978. */
  3979. set page(value) {
  3980. this.pdfViewer.currentPageNumber = value;
  3981. },
  3982. /**
  3983. * @param dest - The PDF destination object.
  3984. */
  3985. navigateTo: function (dest) {},
  3986. /**
  3987. * @param dest - The PDF destination object.
  3988. * @returns {string} The hyperlink to the PDF object.
  3989. */
  3990. getDestinationHash: function (dest) {
  3991. return '#';
  3992. },
  3993. /**
  3994. * @param hash - The PDF parameters/hash.
  3995. * @returns {string} The hyperlink to the PDF object.
  3996. */
  3997. getAnchorUrl: function (hash) {
  3998. return '#';
  3999. },
  4000. /**
  4001. * @param {string} hash
  4002. */
  4003. setHash: function (hash) {},
  4004. /**
  4005. * @param {string} action
  4006. */
  4007. executeNamedAction: function (action) {},
  4008. };
  4009. return SimpleLinkService;
  4010. })();
  4011. /**
  4012. * PDFPage object source.
  4013. * @class
  4014. */
  4015. var PDFPageSource = (function PDFPageSourceClosure() {
  4016. /**
  4017. * @constructs
  4018. * @param {PDFDocument} pdfDocument
  4019. * @param {number} pageNumber
  4020. * @constructor
  4021. */
  4022. function PDFPageSource(pdfDocument, pageNumber) {
  4023. this.pdfDocument = pdfDocument;
  4024. this.pageNumber = pageNumber;
  4025. }
  4026. PDFPageSource.prototype = /** @lends PDFPageSource.prototype */ {
  4027. /**
  4028. * @returns {Promise<PDFPage>}
  4029. */
  4030. getPage: function () {
  4031. return this.pdfDocument.getPage(this.pageNumber);
  4032. }
  4033. };
  4034. return PDFPageSource;
  4035. })();
  4036. var PDFViewerApplication = {
  4037. initialBookmark: document.location.hash.substring(1),
  4038. initialized: false,
  4039. fellback: false,
  4040. pdfDocument: null,
  4041. sidebarOpen: false,
  4042. printing: false,
  4043. /** @type {PDFViewer} */
  4044. pdfViewer: null,
  4045. /** @type {PDFThumbnailViewer} */
  4046. pdfThumbnailViewer: null,
  4047. /** @type {PDFRenderingQueue} */
  4048. pdfRenderingQueue: null,
  4049. pageRotation: 0,
  4050. updateScaleControls: true,
  4051. isInitialViewSet: false,
  4052. animationStartedPromise: null,
  4053. mouseScrollTimeStamp: 0,
  4054. mouseScrollDelta: 0,
  4055. preferenceSidebarViewOnLoad: SidebarView.NONE,
  4056. preferencePdfBugEnabled: false,
  4057. isViewerEmbedded: (window.parent !== window),
  4058. url: '',
  4059. // called once when the document is loaded
  4060. initialize: function pdfViewInitialize() {
  4061. var pdfRenderingQueue = new PDFRenderingQueue();
  4062. pdfRenderingQueue.onIdle = this.cleanup.bind(this);
  4063. this.pdfRenderingQueue = pdfRenderingQueue;
  4064. var container = document.getElementById('viewerContainer');
  4065. var viewer = document.getElementById('viewer');
  4066. this.pdfViewer = new PDFViewer({
  4067. container: container,
  4068. viewer: viewer,
  4069. renderingQueue: pdfRenderingQueue,
  4070. linkService: this
  4071. });
  4072. pdfRenderingQueue.setViewer(this.pdfViewer);
  4073. var thumbnailContainer = document.getElementById('thumbnailView');
  4074. this.pdfThumbnailViewer = new PDFThumbnailViewer({
  4075. container: thumbnailContainer,
  4076. renderingQueue: pdfRenderingQueue,
  4077. linkService: this
  4078. });
  4079. pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
  4080. Preferences.initialize();
  4081. this.findController = new PDFFindController({
  4082. pdfViewer: this.pdfViewer,
  4083. integratedFind: this.supportsIntegratedFind
  4084. });
  4085. this.pdfViewer.setFindController(this.findController);
  4086. this.findBar = new PDFFindBar({
  4087. bar: document.getElementById('findbar'),
  4088. toggleButton: document.getElementById('viewFind'),
  4089. findField: document.getElementById('findInput'),
  4090. highlightAllCheckbox: document.getElementById('findHighlightAll'),
  4091. caseSensitiveCheckbox: document.getElementById('findMatchCase'),
  4092. findMsg: document.getElementById('findMsg'),
  4093. findStatusIcon: document.getElementById('findStatusIcon'),
  4094. findPreviousButton: document.getElementById('findPrevious'),
  4095. findNextButton: document.getElementById('findNext'),
  4096. findController: this.findController
  4097. });
  4098. this.findController.setFindBar(this.findBar);
  4099. HandTool.initialize({
  4100. container: container,
  4101. toggleHandTool: document.getElementById('toggleHandTool')
  4102. });
  4103. SecondaryToolbar.initialize({
  4104. toolbar: document.getElementById('secondaryToolbar'),
  4105. presentationMode: PresentationMode,
  4106. toggleButton: document.getElementById('secondaryToolbarToggle'),
  4107. presentationModeButton:
  4108. document.getElementById('secondaryPresentationMode'),
  4109. openFile: document.getElementById('secondaryOpenFile'),
  4110. print: document.getElementById('secondaryPrint'),
  4111. download: document.getElementById('secondaryDownload'),
  4112. viewBookmark: document.getElementById('secondaryViewBookmark'),
  4113. firstPage: document.getElementById('firstPage'),
  4114. lastPage: document.getElementById('lastPage'),
  4115. pageRotateCw: document.getElementById('pageRotateCw'),
  4116. pageRotateCcw: document.getElementById('pageRotateCcw'),
  4117. documentProperties: DocumentProperties,
  4118. documentPropertiesButton: document.getElementById('documentProperties')
  4119. });
  4120. PresentationMode.initialize({
  4121. container: container,
  4122. secondaryToolbar: SecondaryToolbar,
  4123. firstPage: document.getElementById('contextFirstPage'),
  4124. lastPage: document.getElementById('contextLastPage'),
  4125. pageRotateCw: document.getElementById('contextPageRotateCw'),
  4126. pageRotateCcw: document.getElementById('contextPageRotateCcw')
  4127. });
  4128. PasswordPrompt.initialize({
  4129. overlayName: 'passwordOverlay',
  4130. passwordField: document.getElementById('password'),
  4131. passwordText: document.getElementById('passwordText'),
  4132. passwordSubmit: document.getElementById('passwordSubmit'),
  4133. passwordCancel: document.getElementById('passwordCancel')
  4134. });
  4135. DocumentProperties.initialize({
  4136. overlayName: 'documentPropertiesOverlay',
  4137. closeButton: document.getElementById('documentPropertiesClose'),
  4138. fileNameField: document.getElementById('fileNameField'),
  4139. fileSizeField: document.getElementById('fileSizeField'),
  4140. titleField: document.getElementById('titleField'),
  4141. authorField: document.getElementById('authorField'),
  4142. subjectField: document.getElementById('subjectField'),
  4143. keywordsField: document.getElementById('keywordsField'),
  4144. creationDateField: document.getElementById('creationDateField'),
  4145. modificationDateField: document.getElementById('modificationDateField'),
  4146. creatorField: document.getElementById('creatorField'),
  4147. producerField: document.getElementById('producerField'),
  4148. versionField: document.getElementById('versionField'),
  4149. pageCountField: document.getElementById('pageCountField')
  4150. });
  4151. var self = this;
  4152. var initializedPromise = Promise.all([
  4153. Preferences.get('enableWebGL').then(function resolved(value) {
  4154. PDFJS.disableWebGL = !value;
  4155. }),
  4156. Preferences.get('sidebarViewOnLoad').then(function resolved(value) {
  4157. self.preferenceSidebarViewOnLoad = value;
  4158. }),
  4159. Preferences.get('pdfBugEnabled').then(function resolved(value) {
  4160. self.preferencePdfBugEnabled = value;
  4161. }),
  4162. Preferences.get('disableTextLayer').then(function resolved(value) {
  4163. if (PDFJS.disableTextLayer === true) {
  4164. return;
  4165. }
  4166. PDFJS.disableTextLayer = value;
  4167. }),
  4168. Preferences.get('disableRange').then(function resolved(value) {
  4169. if (PDFJS.disableRange === true) {
  4170. return;
  4171. }
  4172. PDFJS.disableRange = value;
  4173. }),
  4174. Preferences.get('disableAutoFetch').then(function resolved(value) {
  4175. PDFJS.disableAutoFetch = value;
  4176. }),
  4177. Preferences.get('disableFontFace').then(function resolved(value) {
  4178. if (PDFJS.disableFontFace === true) {
  4179. return;
  4180. }
  4181. PDFJS.disableFontFace = value;
  4182. }),
  4183. Preferences.get('useOnlyCssZoom').then(function resolved(value) {
  4184. PDFJS.useOnlyCssZoom = value;
  4185. })
  4186. // TODO move more preferences and other async stuff here
  4187. ]).catch(function (reason) { });
  4188. return initializedPromise.then(function () {
  4189. PDFViewerApplication.initialized = true;
  4190. });
  4191. },
  4192. zoomIn: function pdfViewZoomIn(ticks) {
  4193. var newScale = this.pdfViewer.currentScale;
  4194. do {
  4195. newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
  4196. newScale = Math.ceil(newScale * 10) / 10;
  4197. newScale = Math.min(MAX_SCALE, newScale);
  4198. } while (--ticks && newScale < MAX_SCALE);
  4199. this.setScale(newScale, true);
  4200. },
  4201. zoomOut: function pdfViewZoomOut(ticks) {
  4202. var newScale = this.pdfViewer.currentScale;
  4203. do {
  4204. newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
  4205. newScale = Math.floor(newScale * 10) / 10;
  4206. newScale = Math.max(MIN_SCALE, newScale);
  4207. } while (--ticks && newScale > MIN_SCALE);
  4208. this.setScale(newScale, true);
  4209. },
  4210. get currentScaleValue() {
  4211. return this.pdfViewer.currentScaleValue;
  4212. },
  4213. get pagesCount() {
  4214. return this.pdfDocument.numPages;
  4215. },
  4216. set page(val) {
  4217. this.pdfViewer.currentPageNumber = val;
  4218. },
  4219. get page() {
  4220. return this.pdfViewer.currentPageNumber;
  4221. },
  4222. get supportsPrinting() {
  4223. var canvas = document.createElement('canvas');
  4224. var value = 'mozPrintCallback' in canvas;
  4225. // shadow
  4226. Object.defineProperty(this, 'supportsPrinting', { value: value,
  4227. enumerable: true,
  4228. configurable: true,
  4229. writable: false });
  4230. return value;
  4231. },
  4232. get supportsFullscreen() {
  4233. var doc = document.documentElement;
  4234. var support = doc.requestFullscreen || doc.mozRequestFullScreen ||
  4235. doc.webkitRequestFullScreen || doc.msRequestFullscreen;
  4236. if (document.fullscreenEnabled === false ||
  4237. document.mozFullScreenEnabled === false ||
  4238. document.webkitFullscreenEnabled === false ||
  4239. document.msFullscreenEnabled === false) {
  4240. support = false;
  4241. }
  4242. Object.defineProperty(this, 'supportsFullscreen', { value: support,
  4243. enumerable: true,
  4244. configurable: true,
  4245. writable: false });
  4246. return support;
  4247. },
  4248. get supportsIntegratedFind() {
  4249. var support = false;
  4250. Object.defineProperty(this, 'supportsIntegratedFind', { value: support,
  4251. enumerable: true,
  4252. configurable: true,
  4253. writable: false });
  4254. return support;
  4255. },
  4256. get supportsDocumentFonts() {
  4257. var support = true;
  4258. Object.defineProperty(this, 'supportsDocumentFonts', { value: support,
  4259. enumerable: true,
  4260. configurable: true,
  4261. writable: false });
  4262. return support;
  4263. },
  4264. get supportsDocumentColors() {
  4265. var support = true;
  4266. Object.defineProperty(this, 'supportsDocumentColors', { value: support,
  4267. enumerable: true,
  4268. configurable: true,
  4269. writable: false });
  4270. return support;
  4271. },
  4272. get loadingBar() {
  4273. var bar = new ProgressBar('#loadingBar', {});
  4274. Object.defineProperty(this, 'loadingBar', { value: bar,
  4275. enumerable: true,
  4276. configurable: true,
  4277. writable: false });
  4278. return bar;
  4279. },
  4280. setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
  4281. this.url = url;
  4282. if(customTitle){
  4283. this.setTitle(customTitle);
  4284. }else{
  4285. try {
  4286. this.setTitle(decodeURIComponent(getFileName(url)) || url);
  4287. } catch (e) {
  4288. // decodeURIComponent may throw URIError,
  4289. // fall back to using the unprocessed url in that case
  4290. this.setTitle(url);
  4291. }
  4292. }
  4293. },
  4294. setTitle: function pdfViewSetTitle(title) {
  4295. document.title = title;
  4296. },
  4297. close: function pdfViewClose() {
  4298. var errorWrapper = document.getElementById('errorWrapper');
  4299. errorWrapper.setAttribute('hidden', 'true');
  4300. if (!this.pdfDocument) {
  4301. return;
  4302. }
  4303. this.pdfDocument.destroy();
  4304. this.pdfDocument = null;
  4305. this.pdfThumbnailViewer.setDocument(null);
  4306. this.pdfViewer.setDocument(null);
  4307. if (typeof PDFBug !== 'undefined') {
  4308. PDFBug.cleanup();
  4309. }
  4310. },
  4311. // TODO(mack): This function signature should really be pdfViewOpen(url, args)
  4312. open: function pdfViewOpen(file, scale, password,
  4313. pdfDataRangeTransport, args) {
  4314. if (this.pdfDocument) {
  4315. // Reload the preferences if a document was previously opened.
  4316. Preferences.reload();
  4317. }
  4318. this.close();
  4319. var parameters = {password: password};
  4320. if (typeof file === 'string') { // URL
  4321. this.setTitleUsingUrl(file);
  4322. parameters.url = file;
  4323. } else if (file && 'byteLength' in file) { // ArrayBuffer
  4324. parameters.data = file;
  4325. } else if (file.url && file.originalUrl) {
  4326. this.setTitleUsingUrl(file.originalUrl);
  4327. parameters.url = file.url;
  4328. }
  4329. if (args) {
  4330. for (var prop in args) {
  4331. parameters[prop] = args[prop];
  4332. }
  4333. }
  4334. var self = this;
  4335. self.loading = true;
  4336. self.downloadComplete = false;
  4337. var passwordNeeded = function passwordNeeded(updatePassword, reason) {
  4338. PasswordPrompt.updatePassword = updatePassword;
  4339. PasswordPrompt.reason = reason;
  4340. PasswordPrompt.open();
  4341. };
  4342. function getDocumentProgress(progressData) {
  4343. self.progress(progressData.loaded / progressData.total);
  4344. }
  4345. PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded,
  4346. getDocumentProgress).then(
  4347. function getDocumentCallback(pdfDocument) {
  4348. self.load(pdfDocument, scale);
  4349. self.loading = false;
  4350. },
  4351. function getDocumentError(exception) {
  4352. var message = exception && exception.message;
  4353. var loadingErrorMessage = mozL10n.get('loading_error', null,
  4354. 'An error occurred while loading the PDF.');
  4355. if (exception instanceof PDFJS.InvalidPDFException) {
  4356. // change error message also for other builds
  4357. loadingErrorMessage = mozL10n.get('invalid_file_error', null,
  4358. 'Invalid or corrupted PDF file.');
  4359. } else if (exception instanceof PDFJS.MissingPDFException) {
  4360. // special message for missing PDF's
  4361. loadingErrorMessage = mozL10n.get('missing_file_error', null,
  4362. 'Missing PDF file.');
  4363. } else if (exception instanceof PDFJS.UnexpectedResponseException) {
  4364. loadingErrorMessage = mozL10n.get('unexpected_response_error', null,
  4365. 'Unexpected server response.');
  4366. }
  4367. var moreInfo = {
  4368. message: message
  4369. };
  4370. self.error(loadingErrorMessage, moreInfo);
  4371. self.loading = false;
  4372. }
  4373. );
  4374. if (args && args.length) {
  4375. DocumentProperties.setFileSize(args.length);
  4376. }
  4377. },
  4378. download: function pdfViewDownload() {
  4379. function downloadByUrl() {
  4380. downloadManager.downloadUrl(url, filename);
  4381. }
  4382. var url = this.url.split('#')[0];
  4383. var filename = getPDFFileNameFromURL(url);
  4384. var downloadManager = new DownloadManager();
  4385. downloadManager.onerror = function (err) {
  4386. // This error won't really be helpful because it's likely the
  4387. // fallback won't work either (or is already open).
  4388. PDFViewerApplication.error('PDF failed to download.');
  4389. };
  4390. if (!this.pdfDocument) { // the PDF is not ready yet
  4391. downloadByUrl();
  4392. return;
  4393. }
  4394. if (!this.downloadComplete) { // the PDF is still downloading
  4395. downloadByUrl();
  4396. return;
  4397. }
  4398. this.pdfDocument.getData().then(
  4399. function getDataSuccess(data) {
  4400. var blob = PDFJS.createBlob(data, 'application/pdf');
  4401. downloadManager.download(blob, url, filename);
  4402. },
  4403. downloadByUrl // Error occurred try downloading with just the url.
  4404. ).then(null, downloadByUrl);
  4405. },
  4406. fallback: function pdfViewFallback(featureId) {
  4407. return;
  4408. },
  4409. navigateTo: function pdfViewNavigateTo(dest) {
  4410. var destString = '';
  4411. var self = this;
  4412. var goToDestination = function(destRef) {
  4413. self.pendingRefStr = null;
  4414. // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
  4415. var pageNumber = destRef instanceof Object ?
  4416. self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
  4417. (destRef + 1);
  4418. if (pageNumber) {
  4419. if (pageNumber > self.pagesCount) {
  4420. pageNumber = self.pagesCount;
  4421. }
  4422. self.pdfViewer.scrollPageIntoView(pageNumber, dest);
  4423. // Update the browsing history.
  4424. PDFHistory.push({ dest: dest, hash: destString, page: pageNumber });
  4425. } else {
  4426. self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
  4427. var pageNum = pageIndex + 1;
  4428. self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum;
  4429. goToDestination(destRef);
  4430. });
  4431. }
  4432. };
  4433. var destinationPromise;
  4434. if (typeof dest === 'string') {
  4435. destString = dest;
  4436. destinationPromise = this.pdfDocument.getDestination(dest);
  4437. } else {
  4438. destinationPromise = Promise.resolve(dest);
  4439. }
  4440. destinationPromise.then(function(destination) {
  4441. dest = destination;
  4442. if (!(destination instanceof Array)) {
  4443. return; // invalid destination
  4444. }
  4445. goToDestination(destination[0]);
  4446. });
  4447. },
  4448. executeNamedAction: function pdfViewExecuteNamedAction(action) {
  4449. // See PDF reference, table 8.45 - Named action
  4450. switch (action) {
  4451. case 'GoToPage':
  4452. document.getElementById('pageNumber').focus();
  4453. break;
  4454. case 'GoBack':
  4455. PDFHistory.back();
  4456. break;
  4457. case 'GoForward':
  4458. PDFHistory.forward();
  4459. break;
  4460. case 'Find':
  4461. if (!this.supportsIntegratedFind) {
  4462. this.findBar.toggle();
  4463. }
  4464. break;
  4465. case 'NextPage':
  4466. this.page++;
  4467. break;
  4468. case 'PrevPage':
  4469. this.page--;
  4470. break;
  4471. case 'LastPage':
  4472. this.page = this.pagesCount;
  4473. break;
  4474. case 'FirstPage':
  4475. this.page = 1;
  4476. break;
  4477. default:
  4478. break; // No action according to spec
  4479. }
  4480. },
  4481. getDestinationHash: function pdfViewGetDestinationHash(dest) {
  4482. if (typeof dest === 'string') {
  4483. return this.getAnchorUrl('#' + escape(dest));
  4484. }
  4485. if (dest instanceof Array) {
  4486. var destRef = dest[0]; // see navigateTo method for dest format
  4487. var pageNumber = destRef instanceof Object ?
  4488. this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
  4489. (destRef + 1);
  4490. if (pageNumber) {
  4491. var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
  4492. var destKind = dest[1];
  4493. if (typeof destKind === 'object' && 'name' in destKind &&
  4494. destKind.name === 'XYZ') {
  4495. var scale = (dest[4] || this.currentScaleValue);
  4496. var scaleNumber = parseFloat(scale);
  4497. if (scaleNumber) {
  4498. scale = scaleNumber * 100;
  4499. }
  4500. pdfOpenParams += '&zoom=' + scale;
  4501. if (dest[2] || dest[3]) {
  4502. pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
  4503. }
  4504. }
  4505. return pdfOpenParams;
  4506. }
  4507. }
  4508. return '';
  4509. },
  4510. /**
  4511. * Prefix the full url on anchor links to make sure that links are resolved
  4512. * relative to the current URL instead of the one defined in <base href>.
  4513. * @param {String} anchor The anchor hash, including the #.
  4514. */
  4515. getAnchorUrl: function getAnchorUrl(anchor) {
  4516. return anchor;
  4517. },
  4518. /**
  4519. * Show the error box.
  4520. * @param {String} message A message that is human readable.
  4521. * @param {Object} moreInfo (optional) Further information about the error
  4522. * that is more technical. Should have a 'message'
  4523. * and optionally a 'stack' property.
  4524. */
  4525. error: function pdfViewError(message, moreInfo) {
  4526. var moreInfoText = mozL10n.get('error_version_info',
  4527. {version: PDFJS.version || '?', build: PDFJS.build || '?'},
  4528. 'PDF.js v{{version}} (build: {{build}})') + '\n';
  4529. if (moreInfo) {
  4530. moreInfoText +=
  4531. mozL10n.get('error_message', {message: moreInfo.message},
  4532. 'Message: {{message}}');
  4533. if (moreInfo.stack) {
  4534. moreInfoText += '\n' +
  4535. mozL10n.get('error_stack', {stack: moreInfo.stack},
  4536. 'Stack: {{stack}}');
  4537. } else {
  4538. if (moreInfo.filename) {
  4539. moreInfoText += '\n' +
  4540. mozL10n.get('error_file', {file: moreInfo.filename},
  4541. 'File: {{file}}');
  4542. }
  4543. if (moreInfo.lineNumber) {
  4544. moreInfoText += '\n' +
  4545. mozL10n.get('error_line', {line: moreInfo.lineNumber},
  4546. 'Line: {{line}}');
  4547. }
  4548. }
  4549. }
  4550. var errorWrapper = document.getElementById('errorWrapper');
  4551. errorWrapper.removeAttribute('hidden');
  4552. var errorMessage = document.getElementById('errorMessage');
  4553. errorMessage.textContent = message;
  4554. var closeButton = document.getElementById('errorClose');
  4555. closeButton.onclick = function() {
  4556. errorWrapper.setAttribute('hidden', 'true');
  4557. };
  4558. var errorMoreInfo = document.getElementById('errorMoreInfo');
  4559. var moreInfoButton = document.getElementById('errorShowMore');
  4560. var lessInfoButton = document.getElementById('errorShowLess');
  4561. moreInfoButton.onclick = function() {
  4562. errorMoreInfo.removeAttribute('hidden');
  4563. moreInfoButton.setAttribute('hidden', 'true');
  4564. lessInfoButton.removeAttribute('hidden');
  4565. errorMoreInfo.style.height = errorMoreInfo.scrollHeight + 'px';
  4566. };
  4567. lessInfoButton.onclick = function() {
  4568. errorMoreInfo.setAttribute('hidden', 'true');
  4569. moreInfoButton.removeAttribute('hidden');
  4570. lessInfoButton.setAttribute('hidden', 'true');
  4571. };
  4572. moreInfoButton.oncontextmenu = noContextMenuHandler;
  4573. lessInfoButton.oncontextmenu = noContextMenuHandler;
  4574. closeButton.oncontextmenu = noContextMenuHandler;
  4575. moreInfoButton.removeAttribute('hidden');
  4576. lessInfoButton.setAttribute('hidden', 'true');
  4577. errorMoreInfo.value = moreInfoText;
  4578. },
  4579. progress: function pdfViewProgress(level) {
  4580. var percent = Math.round(level * 100);
  4581. // When we transition from full request to range requests, it's possible
  4582. // that we discard some of the loaded data. This can cause the loading
  4583. // bar to move backwards. So prevent this by only updating the bar if it
  4584. // increases.
  4585. if (percent > this.loadingBar.percent || isNaN(percent)) {
  4586. this.loadingBar.percent = percent;
  4587. }
  4588. },
  4589. load: function pdfViewLoad(pdfDocument, scale) {
  4590. var self = this;
  4591. scale = scale || UNKNOWN_SCALE;
  4592. this.findController.reset();
  4593. this.pdfDocument = pdfDocument;
  4594. DocumentProperties.url = this.url;
  4595. DocumentProperties.pdfDocument = pdfDocument;
  4596. DocumentProperties.resolveDataAvailable();
  4597. var downloadedPromise = pdfDocument.getDownloadInfo().then(function() {
  4598. self.downloadComplete = true;
  4599. self.loadingBar.hide();
  4600. var outerContainer = document.getElementById('outerContainer');
  4601. outerContainer.classList.remove('loadingInProgress');
  4602. });
  4603. var pagesCount = pdfDocument.numPages;
  4604. document.getElementById('numPages').textContent =
  4605. mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
  4606. document.getElementById('pageNumber').max = pagesCount;
  4607. var id = this.documentFingerprint = pdfDocument.fingerprint;
  4608. var store = this.store = new ViewHistory(id);
  4609. var pdfViewer = this.pdfViewer;
  4610. pdfViewer.currentScale = scale;
  4611. pdfViewer.setDocument(pdfDocument);
  4612. var firstPagePromise = pdfViewer.firstPagePromise;
  4613. var pagesPromise = pdfViewer.pagesPromise;
  4614. var onePageRendered = pdfViewer.onePageRendered;
  4615. this.pageRotation = 0;
  4616. this.isInitialViewSet = false;
  4617. this.pagesRefMap = pdfViewer.pagesRefMap;
  4618. this.pdfThumbnailViewer.setDocument(pdfDocument);
  4619. firstPagePromise.then(function(pdfPage) {
  4620. downloadedPromise.then(function () {
  4621. var event = document.createEvent('CustomEvent');
  4622. event.initCustomEvent('documentload', true, true, {});
  4623. window.dispatchEvent(event);
  4624. });
  4625. self.loadingBar.setWidth(document.getElementById('viewer'));
  4626. self.findController.resolveFirstPage();
  4627. if (!PDFJS.disableHistory && !self.isViewerEmbedded) {
  4628. // The browsing history is only enabled when the viewer is standalone,
  4629. // i.e. not when it is embedded in a web page.
  4630. PDFHistory.initialize(self.documentFingerprint, self);
  4631. }
  4632. });
  4633. // Fetch the necessary preference values.
  4634. var showPreviousViewOnLoad;
  4635. var showPreviousViewOnLoadPromise =
  4636. Preferences.get('showPreviousViewOnLoad').then(function (prefValue) {
  4637. showPreviousViewOnLoad = prefValue;
  4638. });
  4639. var defaultZoomValue;
  4640. var defaultZoomValuePromise =
  4641. Preferences.get('defaultZoomValue').then(function (prefValue) {
  4642. defaultZoomValue = prefValue;
  4643. });
  4644. var storePromise = store.initializedPromise;
  4645. Promise.all([firstPagePromise, storePromise, showPreviousViewOnLoadPromise,
  4646. defaultZoomValuePromise]).then(function resolved() {
  4647. var storedHash = null;
  4648. if (showPreviousViewOnLoad && store.get('exists', false)) {
  4649. var pageNum = store.get('page', '1');
  4650. var zoom = defaultZoomValue ||
  4651. store.get('zoom', self.pdfViewer.currentScale);
  4652. var left = store.get('scrollLeft', '0');
  4653. var top = store.get('scrollTop', '0');
  4654. storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
  4655. left + ',' + top;
  4656. } else if (defaultZoomValue) {
  4657. storedHash = 'page=1&zoom=' + defaultZoomValue;
  4658. }
  4659. self.setInitialView(storedHash, scale);
  4660. // Make all navigation keys work on document load,
  4661. // unless the viewer is embedded in a web page.
  4662. if (!self.isViewerEmbedded) {
  4663. self.pdfViewer.focus();
  4664. }
  4665. }, function rejected(reason) {
  4666. console.error(reason);
  4667. firstPagePromise.then(function () {
  4668. self.setInitialView(null, scale);
  4669. });
  4670. });
  4671. pagesPromise.then(function() {
  4672. if (self.supportsPrinting) {
  4673. pdfDocument.getJavaScript().then(function(javaScript) {
  4674. if (javaScript.length) {
  4675. console.warn('Warning: JavaScript is not supported');
  4676. self.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript);
  4677. }
  4678. // Hack to support auto printing.
  4679. var regex = /\bprint\s*\(/g;
  4680. for (var i = 0, ii = javaScript.length; i < ii; i++) {
  4681. var js = javaScript[i];
  4682. if (js && regex.test(js)) {
  4683. setTimeout(function() {
  4684. window.print();
  4685. });
  4686. return;
  4687. }
  4688. }
  4689. });
  4690. }
  4691. });
  4692. // outline depends on pagesRefMap
  4693. var promises = [pagesPromise, this.animationStartedPromise];
  4694. Promise.all(promises).then(function() {
  4695. pdfDocument.getOutline().then(function(outline) {
  4696. var outlineView = document.getElementById('outlineView');
  4697. self.outline = new DocumentOutlineView({
  4698. outline: outline,
  4699. outlineView: outlineView,
  4700. linkService: self
  4701. });
  4702. document.getElementById('viewOutline').disabled = !outline;
  4703. if (!outline && !outlineView.classList.contains('hidden')) {
  4704. self.switchSidebarView('thumbs');
  4705. }
  4706. if (outline &&
  4707. self.preferenceSidebarViewOnLoad === SidebarView.OUTLINE) {
  4708. self.switchSidebarView('outline', true);
  4709. }
  4710. });
  4711. pdfDocument.getAttachments().then(function(attachments) {
  4712. var attachmentsView = document.getElementById('attachmentsView');
  4713. self.attachments = new DocumentAttachmentsView({
  4714. attachments: attachments,
  4715. attachmentsView: attachmentsView
  4716. });
  4717. document.getElementById('viewAttachments').disabled = !attachments;
  4718. if (!attachments && !attachmentsView.classList.contains('hidden')) {
  4719. self.switchSidebarView('thumbs');
  4720. }
  4721. if (attachments &&
  4722. self.preferenceSidebarViewOnLoad === SidebarView.ATTACHMENTS) {
  4723. self.switchSidebarView('attachments', true);
  4724. }
  4725. });
  4726. });
  4727. if (self.preferenceSidebarViewOnLoad === SidebarView.THUMBS) {
  4728. Promise.all([firstPagePromise, onePageRendered]).then(function () {
  4729. self.switchSidebarView('thumbs', true);
  4730. });
  4731. }
  4732. pdfDocument.getMetadata().then(function(data) {
  4733. var info = data.info, metadata = data.metadata;
  4734. self.documentInfo = info;
  4735. self.metadata = metadata;
  4736. // Provides some basic debug information
  4737. console.log('PDF ' + pdfDocument.fingerprint + ' [' +
  4738. info.PDFFormatVersion + ' ' + (info.Producer || '-').trim() +
  4739. ' / ' + (info.Creator || '-').trim() + ']' +
  4740. ' (PDF.js: ' + (PDFJS.version || '-') +
  4741. (!PDFJS.disableWebGL ? ' [WebGL]' : '') + ')');
  4742. var pdfTitle;
  4743. if (metadata && metadata.has('dc:title')) {
  4744. var title = metadata.get('dc:title');
  4745. // Ghostscript sometimes return 'Untitled', sets the title to 'Untitled'
  4746. if (title !== 'Untitled') {
  4747. pdfTitle = title;
  4748. }
  4749. }
  4750. if (!pdfTitle && info && info['Title']) {
  4751. pdfTitle = info['Title'];
  4752. }
  4753. if (pdfTitle) {
  4754. self.setTitle(pdfTitle + ' - ' + document.title);
  4755. }
  4756. if (info.IsAcroFormPresent) {
  4757. console.warn('Warning: AcroForm/XFA is not supported');
  4758. self.fallback(PDFJS.UNSUPPORTED_FEATURES.forms);
  4759. }
  4760. });
  4761. },
  4762. setInitialView: function pdfViewSetInitialView(storedHash, scale) {
  4763. this.isInitialViewSet = true;
  4764. // When opening a new file (when one is already loaded in the viewer):
  4765. // Reset 'currentPageNumber', since otherwise the page's scale will be wrong
  4766. // if 'currentPageNumber' is larger than the number of pages in the file.
  4767. document.getElementById('pageNumber').value =
  4768. this.pdfViewer.currentPageNumber = 1;
  4769. if (PDFHistory.initialDestination) {
  4770. this.navigateTo(PDFHistory.initialDestination);
  4771. PDFHistory.initialDestination = null;
  4772. } else if (this.initialBookmark) {
  4773. this.setHash(this.initialBookmark);
  4774. PDFHistory.push({ hash: this.initialBookmark }, !!this.initialBookmark);
  4775. this.initialBookmark = null;
  4776. } else if (storedHash) {
  4777. this.setHash(storedHash);
  4778. } else if (scale) {
  4779. this.setScale(scale, true);
  4780. this.page = 1;
  4781. }
  4782. if (this.pdfViewer.currentScale === UNKNOWN_SCALE) {
  4783. // Scale was not initialized: invalid bookmark or scale was not specified.
  4784. // Setting the default one.
  4785. this.setScale(DEFAULT_SCALE, true);
  4786. }
  4787. },
  4788. cleanup: function pdfViewCleanup() {
  4789. this.pdfViewer.cleanup();
  4790. this.pdfThumbnailViewer.cleanup();
  4791. this.pdfDocument.cleanup();
  4792. },
  4793. forceRendering: function pdfViewForceRendering() {
  4794. this.pdfRenderingQueue.printing = this.printing;
  4795. this.pdfRenderingQueue.isThumbnailViewEnabled = this.sidebarOpen;
  4796. this.pdfRenderingQueue.renderHighestPriority();
  4797. },
  4798. setHash: function pdfViewSetHash(hash) {
  4799. if (!this.isInitialViewSet) {
  4800. this.initialBookmark = hash;
  4801. return;
  4802. }
  4803. var validFitZoomValues = ['Fit','FitB','FitH','FitBH',
  4804. 'FitV','FitBV','FitR'];
  4805. if (!hash) {
  4806. return;
  4807. }
  4808. if (hash.indexOf('=') >= 0) {
  4809. var params = this.parseQueryString(hash);
  4810. // borrowing syntax from "Parameters for Opening PDF Files"
  4811. if ('nameddest' in params) {
  4812. PDFHistory.updateNextHashParam(params.nameddest);
  4813. this.navigateTo(params.nameddest);
  4814. return;
  4815. }
  4816. var pageNumber, dest;
  4817. if ('page' in params) {
  4818. pageNumber = (params.page | 0) || 1;
  4819. }
  4820. if ('zoom' in params) {
  4821. var zoomArgs = params.zoom.split(','); // scale,left,top
  4822. // building destination array
  4823. // If the zoom value, it has to get divided by 100. If it is a string,
  4824. // it should stay as it is.
  4825. var zoomArg = zoomArgs[0];
  4826. var zoomArgNumber = parseFloat(zoomArg);
  4827. var destName = 'XYZ';
  4828. if (zoomArgNumber) {
  4829. zoomArg = zoomArgNumber / 100;
  4830. } else if (validFitZoomValues.indexOf(zoomArg) >= 0) {
  4831. destName = zoomArg;
  4832. }
  4833. dest = [null, { name: destName },
  4834. zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
  4835. zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
  4836. zoomArg];
  4837. }
  4838. if (dest) {
  4839. this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
  4840. } else if (pageNumber) {
  4841. this.page = pageNumber; // simple page
  4842. }
  4843. if ('pagemode' in params) {
  4844. if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
  4845. params.pagemode === 'attachments') {
  4846. this.switchSidebarView((params.pagemode === 'bookmarks' ?
  4847. 'outline' : params.pagemode), true);
  4848. } else if (params.pagemode === 'none' && this.sidebarOpen) {
  4849. document.getElementById('sidebarToggle').click();
  4850. }
  4851. }
  4852. } else if (/^\d+$/.test(hash)) { // page number
  4853. this.page = hash;
  4854. } else { // named destination
  4855. PDFHistory.updateNextHashParam(unescape(hash));
  4856. this.navigateTo(unescape(hash));
  4857. }
  4858. },
  4859. switchSidebarView: function pdfViewSwitchSidebarView(view, openSidebar) {
  4860. if (openSidebar && !this.sidebarOpen) {
  4861. document.getElementById('sidebarToggle').click();
  4862. }
  4863. var thumbsView = document.getElementById('thumbnailView');
  4864. var outlineView = document.getElementById('outlineView');
  4865. var attachmentsView = document.getElementById('attachmentsView');
  4866. var thumbsButton = document.getElementById('viewThumbnail');
  4867. var outlineButton = document.getElementById('viewOutline');
  4868. var attachmentsButton = document.getElementById('viewAttachments');
  4869. switch (view) {
  4870. case 'thumbs':
  4871. var wasAnotherViewVisible = thumbsView.classList.contains('hidden');
  4872. thumbsButton.classList.add('toggled');
  4873. outlineButton.classList.remove('toggled');
  4874. attachmentsButton.classList.remove('toggled');
  4875. thumbsView.classList.remove('hidden');
  4876. outlineView.classList.add('hidden');
  4877. attachmentsView.classList.add('hidden');
  4878. this.forceRendering();
  4879. if (wasAnotherViewVisible) {
  4880. this.pdfThumbnailViewer.ensureThumbnailVisible(this.page);
  4881. }
  4882. break;
  4883. case 'outline':
  4884. thumbsButton.classList.remove('toggled');
  4885. outlineButton.classList.add('toggled');
  4886. attachmentsButton.classList.remove('toggled');
  4887. thumbsView.classList.add('hidden');
  4888. outlineView.classList.remove('hidden');
  4889. attachmentsView.classList.add('hidden');
  4890. if (outlineButton.getAttribute('disabled')) {
  4891. return;
  4892. }
  4893. break;
  4894. case 'attachments':
  4895. thumbsButton.classList.remove('toggled');
  4896. outlineButton.classList.remove('toggled');
  4897. attachmentsButton.classList.add('toggled');
  4898. thumbsView.classList.add('hidden');
  4899. outlineView.classList.add('hidden');
  4900. attachmentsView.classList.remove('hidden');
  4901. if (attachmentsButton.getAttribute('disabled')) {
  4902. return;
  4903. }
  4904. break;
  4905. }
  4906. },
  4907. // Helper function to parse query string (e.g. ?param1=value&parm2=...).
  4908. parseQueryString: function pdfViewParseQueryString(query) {
  4909. var parts = query.split('&');
  4910. var params = {};
  4911. for (var i = 0, ii = parts.length; i < ii; ++i) {
  4912. var param = parts[i].split('=');
  4913. var key = param[0].toLowerCase();
  4914. var value = param.length > 1 ? param[1] : null;
  4915. params[decodeURIComponent(key)] = decodeURIComponent(value);
  4916. }
  4917. return params;
  4918. },
  4919. beforePrint: function pdfViewSetupBeforePrint() {
  4920. if (!this.supportsPrinting) {
  4921. var printMessage = mozL10n.get('printing_not_supported', null,
  4922. 'Warning: Printing is not fully supported by this browser.');
  4923. this.error(printMessage);
  4924. return;
  4925. }
  4926. var alertNotReady = false;
  4927. var i, ii;
  4928. if (!this.pagesCount) {
  4929. alertNotReady = true;
  4930. } else {
  4931. for (i = 0, ii = this.pagesCount; i < ii; ++i) {
  4932. if (!this.pdfViewer.getPageView(i).pdfPage) {
  4933. alertNotReady = true;
  4934. break;
  4935. }
  4936. }
  4937. }
  4938. if (alertNotReady) {
  4939. var notReadyMessage = mozL10n.get('printing_not_ready', null,
  4940. 'Warning: The PDF is not fully loaded for printing.');
  4941. window.alert(notReadyMessage);
  4942. return;
  4943. }
  4944. this.printing = true;
  4945. this.forceRendering();
  4946. var body = document.querySelector('body');
  4947. body.setAttribute('data-mozPrintCallback', true);
  4948. for (i = 0, ii = this.pagesCount; i < ii; ++i) {
  4949. this.pdfViewer.getPageView(i).beforePrint();
  4950. }
  4951. },
  4952. afterPrint: function pdfViewSetupAfterPrint() {
  4953. var div = document.getElementById('printContainer');
  4954. while (div.hasChildNodes()) {
  4955. div.removeChild(div.lastChild);
  4956. }
  4957. this.printing = false;
  4958. this.forceRendering();
  4959. },
  4960. setScale: function (value, resetAutoSettings) {
  4961. this.updateScaleControls = !!resetAutoSettings;
  4962. this.pdfViewer.currentScaleValue = value;
  4963. this.updateScaleControls = true;
  4964. },
  4965. rotatePages: function pdfViewRotatePages(delta) {
  4966. var pageNumber = this.page;
  4967. this.pageRotation = (this.pageRotation + 360 + delta) % 360;
  4968. this.pdfViewer.pagesRotation = this.pageRotation;
  4969. this.pdfThumbnailViewer.pagesRotation = this.pageRotation;
  4970. this.forceRendering();
  4971. this.pdfViewer.scrollPageIntoView(pageNumber);
  4972. },
  4973. /**
  4974. * This function flips the page in presentation mode if the user scrolls up
  4975. * or down with large enough motion and prevents page flipping too often.
  4976. *
  4977. * @this {PDFView}
  4978. * @param {number} mouseScrollDelta The delta value from the mouse event.
  4979. */
  4980. mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) {
  4981. var MOUSE_SCROLL_COOLDOWN_TIME = 50;
  4982. var currentTime = (new Date()).getTime();
  4983. var storedTime = this.mouseScrollTimeStamp;
  4984. // In case one page has already been flipped there is a cooldown time
  4985. // which has to expire before next page can be scrolled on to.
  4986. if (currentTime > storedTime &&
  4987. currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
  4988. return;
  4989. }
  4990. // In case the user decides to scroll to the opposite direction than before
  4991. // clear the accumulated delta.
  4992. if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) ||
  4993. (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) {
  4994. this.clearMouseScrollState();
  4995. }
  4996. this.mouseScrollDelta += mouseScrollDelta;
  4997. var PAGE_FLIP_THRESHOLD = 120;
  4998. if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) {
  4999. var PageFlipDirection = {
  5000. UP: -1,
  5001. DOWN: 1
  5002. };
  5003. // In presentation mode scroll one page at a time.
  5004. var pageFlipDirection = (this.mouseScrollDelta > 0) ?
  5005. PageFlipDirection.UP :
  5006. PageFlipDirection.DOWN;
  5007. this.clearMouseScrollState();
  5008. var currentPage = this.page;
  5009. // In case we are already on the first or the last page there is no need
  5010. // to do anything.
  5011. if ((currentPage === 1 && pageFlipDirection === PageFlipDirection.UP) ||
  5012. (currentPage === this.pagesCount &&
  5013. pageFlipDirection === PageFlipDirection.DOWN)) {
  5014. return;
  5015. }
  5016. this.page += pageFlipDirection;
  5017. this.mouseScrollTimeStamp = currentTime;
  5018. }
  5019. },
  5020. /**
  5021. * This function clears the member attributes used with mouse scrolling in
  5022. * presentation mode.
  5023. *
  5024. * @this {PDFView}
  5025. */
  5026. clearMouseScrollState: function pdfViewClearMouseScrollState() {
  5027. this.mouseScrollTimeStamp = 0;
  5028. this.mouseScrollDelta = 0;
  5029. }
  5030. };
  5031. window.PDFView = PDFViewerApplication; // obsolete name, using it as an alias
  5032. var THUMBNAIL_SCROLL_MARGIN = -19;
  5033. /**
  5034. * @constructor
  5035. * @param container
  5036. * @param id
  5037. * @param defaultViewport
  5038. * @param linkService
  5039. * @param renderingQueue
  5040. * @param pageSource
  5041. *
  5042. * @implements {IRenderableView}
  5043. */
  5044. var ThumbnailView = function thumbnailView(container, id, defaultViewport,
  5045. linkService, renderingQueue,
  5046. pageSource) {
  5047. var anchor = document.createElement('a');
  5048. anchor.href = linkService.getAnchorUrl('#page=' + id);
  5049. anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
  5050. anchor.onclick = function stopNavigation() {
  5051. linkService.page = id;
  5052. return false;
  5053. };
  5054. this.pdfPage = undefined;
  5055. this.viewport = defaultViewport;
  5056. this.pdfPageRotate = defaultViewport.rotation;
  5057. this.rotation = 0;
  5058. this.pageWidth = this.viewport.width;
  5059. this.pageHeight = this.viewport.height;
  5060. this.pageRatio = this.pageWidth / this.pageHeight;
  5061. this.id = id;
  5062. this.renderingId = 'thumbnail' + id;
  5063. this.canvasWidth = 98;
  5064. this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
  5065. this.scale = (this.canvasWidth / this.pageWidth);
  5066. var div = this.el = document.createElement('div');
  5067. div.id = 'thumbnailContainer' + id;
  5068. div.className = 'thumbnail';
  5069. if (id === 1) {
  5070. // Highlight the thumbnail of the first page when no page number is
  5071. // specified (or exists in cache) when the document is loaded.
  5072. div.classList.add('selected');
  5073. }
  5074. var ring = document.createElement('div');
  5075. ring.className = 'thumbnailSelectionRing';
  5076. ring.style.width = this.canvasWidth + 'px';
  5077. ring.style.height = this.canvasHeight + 'px';
  5078. div.appendChild(ring);
  5079. anchor.appendChild(div);
  5080. container.appendChild(anchor);
  5081. this.hasImage = false;
  5082. this.renderingState = RenderingStates.INITIAL;
  5083. this.renderingQueue = renderingQueue;
  5084. this.pageSource = pageSource;
  5085. this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) {
  5086. this.pdfPage = pdfPage;
  5087. this.pdfPageRotate = pdfPage.rotate;
  5088. var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
  5089. this.viewport = pdfPage.getViewport(1, totalRotation);
  5090. this.update();
  5091. };
  5092. this.update = function thumbnailViewUpdate(rotation) {
  5093. if (rotation !== undefined) {
  5094. this.rotation = rotation;
  5095. }
  5096. var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
  5097. this.viewport = this.viewport.clone({
  5098. scale: 1,
  5099. rotation: totalRotation
  5100. });
  5101. this.pageWidth = this.viewport.width;
  5102. this.pageHeight = this.viewport.height;
  5103. this.pageRatio = this.pageWidth / this.pageHeight;
  5104. this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
  5105. this.scale = (this.canvasWidth / this.pageWidth);
  5106. div.removeAttribute('data-loaded');
  5107. ring.textContent = '';
  5108. ring.style.width = this.canvasWidth + 'px';
  5109. ring.style.height = this.canvasHeight + 'px';
  5110. this.hasImage = false;
  5111. this.renderingState = RenderingStates.INITIAL;
  5112. this.resume = null;
  5113. };
  5114. this.getPageDrawContext = function thumbnailViewGetPageDrawContext() {
  5115. var canvas = document.createElement('canvas');
  5116. canvas.id = 'thumbnail' + id;
  5117. canvas.width = this.canvasWidth;
  5118. canvas.height = this.canvasHeight;
  5119. canvas.className = 'thumbnailImage';
  5120. canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
  5121. {page: id}, 'Thumbnail of Page {{page}}'));
  5122. div.setAttribute('data-loaded', true);
  5123. ring.appendChild(canvas);
  5124. var ctx = canvas.getContext('2d');
  5125. ctx.save();
  5126. ctx.fillStyle = 'rgb(255, 255, 255)';
  5127. ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
  5128. ctx.restore();
  5129. return ctx;
  5130. };
  5131. this.drawingRequired = function thumbnailViewDrawingRequired() {
  5132. return !this.hasImage;
  5133. };
  5134. this.draw = function thumbnailViewDraw(callback) {
  5135. if (!this.pdfPage) {
  5136. var promise = this.pageSource.getPage(this.id);
  5137. promise.then(function(pdfPage) {
  5138. this.setPdfPage(pdfPage);
  5139. this.draw(callback);
  5140. }.bind(this));
  5141. return;
  5142. }
  5143. if (this.renderingState !== RenderingStates.INITIAL) {
  5144. console.error('Must be in new state before drawing');
  5145. }
  5146. this.renderingState = RenderingStates.RUNNING;
  5147. if (this.hasImage) {
  5148. callback();
  5149. return;
  5150. }
  5151. var self = this;
  5152. var ctx = this.getPageDrawContext();
  5153. var drawViewport = this.viewport.clone({ scale: this.scale });
  5154. var renderContext = {
  5155. canvasContext: ctx,
  5156. viewport: drawViewport,
  5157. continueCallback: function(cont) {
  5158. if (!self.renderingQueue.isHighestPriority(self)) {
  5159. self.renderingState = RenderingStates.PAUSED;
  5160. self.resume = function() {
  5161. self.renderingState = RenderingStates.RUNNING;
  5162. cont();
  5163. };
  5164. return;
  5165. }
  5166. cont();
  5167. }
  5168. };
  5169. this.pdfPage.render(renderContext).promise.then(
  5170. function pdfPageRenderCallback() {
  5171. self.renderingState = RenderingStates.FINISHED;
  5172. callback();
  5173. },
  5174. function pdfPageRenderError(error) {
  5175. self.renderingState = RenderingStates.FINISHED;
  5176. callback();
  5177. }
  5178. );
  5179. this.hasImage = true;
  5180. };
  5181. function getTempCanvas(width, height) {
  5182. var tempCanvas = ThumbnailView.tempImageCache;
  5183. if (!tempCanvas) {
  5184. tempCanvas = document.createElement('canvas');
  5185. ThumbnailView.tempImageCache = tempCanvas;
  5186. }
  5187. tempCanvas.width = width;
  5188. tempCanvas.height = height;
  5189. return tempCanvas;
  5190. }
  5191. this.setImage = function thumbnailViewSetImage(img) {
  5192. if (!this.pdfPage) {
  5193. var promise = this.pageSource.getPage();
  5194. promise.then(function(pdfPage) {
  5195. this.setPdfPage(pdfPage);
  5196. this.setImage(img);
  5197. }.bind(this));
  5198. return;
  5199. }
  5200. if (this.hasImage || !img) {
  5201. return;
  5202. }
  5203. this.renderingState = RenderingStates.FINISHED;
  5204. var ctx = this.getPageDrawContext();
  5205. var reducedImage = img;
  5206. var reducedWidth = img.width;
  5207. var reducedHeight = img.height;
  5208. // drawImage does an awful job of rescaling the image, doing it gradually
  5209. var MAX_SCALE_FACTOR = 2.0;
  5210. if (Math.max(img.width / ctx.canvas.width,
  5211. img.height / ctx.canvas.height) > MAX_SCALE_FACTOR) {
  5212. reducedWidth >>= 1;
  5213. reducedHeight >>= 1;
  5214. reducedImage = getTempCanvas(reducedWidth, reducedHeight);
  5215. var reducedImageCtx = reducedImage.getContext('2d');
  5216. reducedImageCtx.drawImage(img, 0, 0, img.width, img.height,
  5217. 0, 0, reducedWidth, reducedHeight);
  5218. while (Math.max(reducedWidth / ctx.canvas.width,
  5219. reducedHeight / ctx.canvas.height) > MAX_SCALE_FACTOR) {
  5220. reducedImageCtx.drawImage(reducedImage,
  5221. 0, 0, reducedWidth, reducedHeight,
  5222. 0, 0, reducedWidth >> 1, reducedHeight >> 1);
  5223. reducedWidth >>= 1;
  5224. reducedHeight >>= 1;
  5225. }
  5226. }
  5227. ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight,
  5228. 0, 0, ctx.canvas.width, ctx.canvas.height);
  5229. this.hasImage = true;
  5230. };
  5231. };
  5232. ThumbnailView.tempImageCache = null;
  5233. /**
  5234. * @typedef {Object} PDFThumbnailViewerOptions
  5235. * @property {HTMLDivElement} container - The container for the thumbs elements.
  5236. * @property {IPDFLinkService} linkService - The navigation/linking service.
  5237. * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
  5238. */
  5239. /**
  5240. * Simple viewer control to display thumbs for pages.
  5241. * @class
  5242. */
  5243. var PDFThumbnailViewer = (function pdfThumbnailViewer() {
  5244. /**
  5245. * @constructs
  5246. * @param {PDFThumbnailViewerOptions} options
  5247. */
  5248. function PDFThumbnailViewer(options) {
  5249. this.container = options.container;
  5250. this.renderingQueue = options.renderingQueue;
  5251. this.linkService = options.linkService;
  5252. this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this));
  5253. this._resetView();
  5254. }
  5255. PDFThumbnailViewer.prototype = {
  5256. _scrollUpdated: function PDFThumbnailViewer_scrollUpdated() {
  5257. this.renderingQueue.renderHighestPriority();
  5258. },
  5259. getThumbnail: function PDFThumbnailViewer_getThumbnail(index) {
  5260. return this.thumbnails[index];
  5261. },
  5262. _getVisibleThumbs: function PDFThumbnailViewer_getVisibleThumbs() {
  5263. return getVisibleElements(this.container, this.thumbnails);
  5264. },
  5265. scrollThumbnailIntoView: function (page) {
  5266. var selected = document.querySelector('.thumbnail.selected');
  5267. if (selected) {
  5268. selected.classList.remove('selected');
  5269. }
  5270. var thumbnail = document.getElementById('thumbnailContainer' + page);
  5271. thumbnail.classList.add('selected');
  5272. var visibleThumbs = this._getVisibleThumbs();
  5273. var numVisibleThumbs = visibleThumbs.views.length;
  5274. // If the thumbnail isn't currently visible, scroll it into view.
  5275. if (numVisibleThumbs > 0) {
  5276. var first = visibleThumbs.first.id;
  5277. // Account for only one thumbnail being visible.
  5278. var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first);
  5279. if (page <= first || page >= last) {
  5280. scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN });
  5281. }
  5282. }
  5283. },
  5284. get pagesRotation() {
  5285. return this._pagesRotation;
  5286. },
  5287. set pagesRotation(rotation) {
  5288. this._pagesRotation = rotation;
  5289. for (var i = 0, l = this.thumbnails.length; i < l; i++) {
  5290. var thumb = this.thumbnails[i];
  5291. thumb.update(rotation);
  5292. }
  5293. },
  5294. cleanup: function PDFThumbnailViewer_cleanup() {
  5295. ThumbnailView.tempImageCache = null;
  5296. },
  5297. _resetView: function () {
  5298. this.thumbnails = [];
  5299. this._pagesRotation = 0;
  5300. },
  5301. setDocument: function (pdfDocument) {
  5302. if (this.pdfDocument) {
  5303. // cleanup of the elements and views
  5304. var thumbsView = this.container;
  5305. while (thumbsView.hasChildNodes()) {
  5306. thumbsView.removeChild(thumbsView.lastChild);
  5307. }
  5308. this._resetView();
  5309. }
  5310. this.pdfDocument = pdfDocument;
  5311. if (!pdfDocument) {
  5312. return Promise.resolve();
  5313. }
  5314. return pdfDocument.getPage(1).then(function (firstPage) {
  5315. var pagesCount = pdfDocument.numPages;
  5316. var viewport = firstPage.getViewport(1.0);
  5317. for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
  5318. var pageSource = new PDFPageSource(pdfDocument, pageNum);
  5319. var thumbnail = new ThumbnailView(this.container, pageNum,
  5320. viewport.clone(), this.linkService,
  5321. this.renderingQueue, pageSource);
  5322. this.thumbnails.push(thumbnail);
  5323. }
  5324. }.bind(this));
  5325. },
  5326. ensureThumbnailVisible:
  5327. function PDFThumbnailViewer_ensureThumbnailVisible(page) {
  5328. // Ensure that the thumbnail of the current page is visible
  5329. // when switching from another view.
  5330. scrollIntoView(document.getElementById('thumbnailContainer' + page));
  5331. },
  5332. forceRendering: function () {
  5333. var visibleThumbs = this._getVisibleThumbs();
  5334. var thumbView = this.renderingQueue.getHighestPriority(visibleThumbs,
  5335. this.thumbnails,
  5336. this.scroll.down);
  5337. if (thumbView) {
  5338. this.renderingQueue.renderView(thumbView);
  5339. return true;
  5340. }
  5341. return false;
  5342. }
  5343. };
  5344. return PDFThumbnailViewer;
  5345. })();
  5346. var DocumentOutlineView = function documentOutlineView(options) {
  5347. var outline = options.outline;
  5348. var outlineView = options.outlineView;
  5349. while (outlineView.firstChild) {
  5350. outlineView.removeChild(outlineView.firstChild);
  5351. }
  5352. if (!outline) {
  5353. return;
  5354. }
  5355. var linkService = options.linkService;
  5356. function bindItemLink(domObj, item) {
  5357. domObj.href = linkService.getDestinationHash(item.dest);
  5358. domObj.onclick = function documentOutlineViewOnclick(e) {
  5359. linkService.navigateTo(item.dest);
  5360. return false;
  5361. };
  5362. }
  5363. var queue = [{parent: outlineView, items: outline}];
  5364. while (queue.length > 0) {
  5365. var levelData = queue.shift();
  5366. var i, n = levelData.items.length;
  5367. for (i = 0; i < n; i++) {
  5368. var item = levelData.items[i];
  5369. var div = document.createElement('div');
  5370. div.className = 'outlineItem';
  5371. var a = document.createElement('a');
  5372. bindItemLink(a, item);
  5373. a.textContent = item.title;
  5374. div.appendChild(a);
  5375. if (item.items.length > 0) {
  5376. var itemsDiv = document.createElement('div');
  5377. itemsDiv.className = 'outlineItems';
  5378. div.appendChild(itemsDiv);
  5379. queue.push({parent: itemsDiv, items: item.items});
  5380. }
  5381. levelData.parent.appendChild(div);
  5382. }
  5383. }
  5384. };
  5385. var DocumentAttachmentsView = function documentAttachmentsView(options) {
  5386. var attachments = options.attachments;
  5387. var attachmentsView = options.attachmentsView;
  5388. while (attachmentsView.firstChild) {
  5389. attachmentsView.removeChild(attachmentsView.firstChild);
  5390. }
  5391. if (!attachments) {
  5392. return;
  5393. }
  5394. function bindItemLink(domObj, item) {
  5395. domObj.onclick = function documentAttachmentsViewOnclick(e) {
  5396. var downloadManager = new DownloadManager();
  5397. downloadManager.downloadData(item.content, getFileName(item.filename),
  5398. '');
  5399. return false;
  5400. };
  5401. }
  5402. var names = Object.keys(attachments).sort(function(a,b) {
  5403. return a.toLowerCase().localeCompare(b.toLowerCase());
  5404. });
  5405. for (var i = 0, ii = names.length; i < ii; i++) {
  5406. var item = attachments[names[i]];
  5407. var div = document.createElement('div');
  5408. div.className = 'attachmentsItem';
  5409. var button = document.createElement('button');
  5410. bindItemLink(button, item);
  5411. button.textContent = getFileName(item.filename);
  5412. div.appendChild(button);
  5413. attachmentsView.appendChild(div);
  5414. }
  5415. };
  5416. function webViewerLoad(evt) {
  5417. PDFViewerApplication.initialize().then(webViewerInitialized);
  5418. }
  5419. function webViewerInitialized() {
  5420. var queryString = document.location.search.substring(1);
  5421. console.log(queryString);
  5422. var params = PDFViewerApplication.parseQueryString(queryString);
  5423. console.log(params);
  5424. var file = 'file' in params ? params.file : DEFAULT_URL;
  5425. console.log(file);
  5426. var fileInput = document.createElement('input');
  5427. fileInput.id = 'fileInput';
  5428. fileInput.className = 'fileInput';
  5429. fileInput.setAttribute('type', 'file');
  5430. fileInput.oncontextmenu = noContextMenuHandler;
  5431. document.body.appendChild(fileInput);
  5432. if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
  5433. document.getElementById('openFile').setAttribute('hidden', 'true');
  5434. document.getElementById('secondaryOpenFile').setAttribute('hidden', 'true');
  5435. } else {
  5436. document.getElementById('fileInput').value = null;
  5437. }
  5438. var locale = PDFJS.locale || navigator.language;
  5439. if (PDFViewerApplication.preferencePdfBugEnabled) {
  5440. // Special debugging flags in the hash section of the URL.
  5441. var hash = document.location.hash.substring(1);
  5442. var hashParams = PDFViewerApplication.parseQueryString(hash);
  5443. if ('disableworker' in hashParams) {
  5444. PDFJS.disableWorker = (hashParams['disableworker'] === 'true');
  5445. }
  5446. if ('disablerange' in hashParams) {
  5447. PDFJS.disableRange = (hashParams['disablerange'] === 'true');
  5448. }
  5449. if ('disablestream' in hashParams) {
  5450. PDFJS.disableStream = (hashParams['disablestream'] === 'true');
  5451. }
  5452. if ('disableautofetch' in hashParams) {
  5453. PDFJS.disableAutoFetch = (hashParams['disableautofetch'] === 'true');
  5454. }
  5455. if ('disablefontface' in hashParams) {
  5456. PDFJS.disableFontFace = (hashParams['disablefontface'] === 'true');
  5457. }
  5458. if ('disablehistory' in hashParams) {
  5459. PDFJS.disableHistory = (hashParams['disablehistory'] === 'true');
  5460. }
  5461. if ('webgl' in hashParams) {
  5462. PDFJS.disableWebGL = (hashParams['webgl'] !== 'true');
  5463. }
  5464. if ('useonlycsszoom' in hashParams) {
  5465. PDFJS.useOnlyCssZoom = (hashParams['useonlycsszoom'] === 'true');
  5466. }
  5467. if ('verbosity' in hashParams) {
  5468. PDFJS.verbosity = hashParams['verbosity'] | 0;
  5469. }
  5470. if ('ignorecurrentpositiononzoom' in hashParams) {
  5471. IGNORE_CURRENT_POSITION_ON_ZOOM =
  5472. (hashParams['ignorecurrentpositiononzoom'] === 'true');
  5473. }
  5474. if ('locale' in hashParams) {
  5475. locale = hashParams['locale'];
  5476. }
  5477. if ('textlayer' in hashParams) {
  5478. switch (hashParams['textlayer']) {
  5479. case 'off':
  5480. PDFJS.disableTextLayer = true;
  5481. break;
  5482. case 'visible':
  5483. case 'shadow':
  5484. case 'hover':
  5485. var viewer = document.getElementById('viewer');
  5486. viewer.classList.add('textLayer-' + hashParams['textlayer']);
  5487. break;
  5488. }
  5489. }
  5490. if ('pdfbug' in hashParams) {
  5491. PDFJS.pdfBug = true;
  5492. var pdfBug = hashParams['pdfbug'];
  5493. var enabled = pdfBug.split(',');
  5494. PDFBug.enable(enabled);
  5495. PDFBug.init();
  5496. }
  5497. }
  5498. mozL10n.setLanguage(locale);
  5499. if (!PDFViewerApplication.supportsPrinting) {
  5500. document.getElementById('print').classList.add('hidden');
  5501. document.getElementById('secondaryPrint').classList.add('hidden');
  5502. }
  5503. if (!PDFViewerApplication.supportsFullscreen) {
  5504. document.getElementById('presentationMode').classList.add('hidden');
  5505. document.getElementById('secondaryPresentationMode').
  5506. classList.add('hidden');
  5507. }
  5508. if (PDFViewerApplication.supportsIntegratedFind) {
  5509. document.getElementById('viewFind').classList.add('hidden');
  5510. }
  5511. // Listen for unsupported features to trigger the fallback UI.
  5512. PDFJS.UnsupportedManager.listen(
  5513. PDFViewerApplication.fallback.bind(PDFViewerApplication));
  5514. // Suppress context menus for some controls
  5515. document.getElementById('scaleSelect').oncontextmenu = noContextMenuHandler;
  5516. var mainContainer = document.getElementById('mainContainer');
  5517. var outerContainer = document.getElementById('outerContainer');
  5518. mainContainer.addEventListener('transitionend', function(e) {
  5519. if (e.target === mainContainer) {
  5520. var event = document.createEvent('UIEvents');
  5521. event.initUIEvent('resize', false, false, window, 0);
  5522. window.dispatchEvent(event);
  5523. outerContainer.classList.remove('sidebarMoving');
  5524. }
  5525. }, true);
  5526. document.getElementById('sidebarToggle').addEventListener('click',
  5527. function() {
  5528. this.classList.toggle('toggled');
  5529. outerContainer.classList.add('sidebarMoving');
  5530. outerContainer.classList.toggle('sidebarOpen');
  5531. PDFViewerApplication.sidebarOpen =
  5532. outerContainer.classList.contains('sidebarOpen');
  5533. PDFViewerApplication.forceRendering();
  5534. });
  5535. document.getElementById('viewThumbnail').addEventListener('click',
  5536. function() {
  5537. PDFViewerApplication.switchSidebarView('thumbs');
  5538. });
  5539. document.getElementById('viewOutline').addEventListener('click',
  5540. function() {
  5541. PDFViewerApplication.switchSidebarView('outline');
  5542. });
  5543. document.getElementById('viewAttachments').addEventListener('click',
  5544. function() {
  5545. PDFViewerApplication.switchSidebarView('attachments');
  5546. });
  5547. document.getElementById('previous').addEventListener('click',
  5548. function() {
  5549. PDFViewerApplication.page--;
  5550. });
  5551. document.getElementById('next').addEventListener('click',
  5552. function() {
  5553. PDFViewerApplication.page++;
  5554. });
  5555. document.getElementById('zoomIn').addEventListener('click',
  5556. function() {
  5557. PDFViewerApplication.zoomIn();
  5558. });
  5559. document.getElementById('zoomOut').addEventListener('click',
  5560. function() {
  5561. PDFViewerApplication.zoomOut();
  5562. });
  5563. document.getElementById('pageNumber').addEventListener('click', function() {
  5564. this.select();
  5565. });
  5566. document.getElementById('pageNumber').addEventListener('change', function() {
  5567. // Handle the user inputting a floating point number.
  5568. PDFViewerApplication.page = (this.value | 0);
  5569. if (this.value !== (this.value | 0).toString()) {
  5570. this.value = PDFViewerApplication.page;
  5571. }
  5572. });
  5573. document.getElementById('scaleSelect').addEventListener('change',
  5574. function() {
  5575. PDFViewerApplication.setScale(this.value, false);
  5576. });
  5577. document.getElementById('presentationMode').addEventListener('click',
  5578. SecondaryToolbar.presentationModeClick.bind(SecondaryToolbar));
  5579. document.getElementById('openFile').addEventListener('click',
  5580. SecondaryToolbar.openFileClick.bind(SecondaryToolbar));
  5581. document.getElementById('print').addEventListener('click',
  5582. SecondaryToolbar.printClick.bind(SecondaryToolbar));
  5583. document.getElementById('download').addEventListener('click',
  5584. SecondaryToolbar.downloadClick.bind(SecondaryToolbar));
  5585. if (file && file.lastIndexOf('file:', 0) === 0) {
  5586. // file:-scheme. Load the contents in the main thread because QtWebKit
  5587. // cannot load file:-URLs in a Web Worker. file:-URLs are usually loaded
  5588. // very quickly, so there is no need to set up progress event listeners.
  5589. PDFViewerApplication.setTitleUsingUrl(file);
  5590. var xhr = new XMLHttpRequest();
  5591. xhr.onload = function() {
  5592. PDFViewerApplication.open(new Uint8Array(xhr.response), 0);
  5593. };
  5594. try {
  5595. xhr.open('GET', file);
  5596. xhr.responseType = 'arraybuffer';
  5597. xhr.send();
  5598. } catch (e) {
  5599. PDFViewerApplication.error(mozL10n.get('loading_error', null,
  5600. 'An error occurred while loading the PDF.'), e);
  5601. }
  5602. return;
  5603. }
  5604. if (file) {
  5605. PDFViewerApplication.open(file, 0);
  5606. }
  5607. }
  5608. document.addEventListener('DOMContentLoaded', webViewerLoad, true);
  5609. document.addEventListener('pagerendered', function (e) {
  5610. var pageIndex = e.detail.pageNumber - 1;
  5611. var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex);
  5612. var thumbnailView = PDFViewerApplication.pdfThumbnailViewer.
  5613. getThumbnail(pageIndex);
  5614. thumbnailView.setImage(pageView.canvas);
  5615. if (pageView.error) {
  5616. PDFViewerApplication.error(mozL10n.get('rendering_error', null,
  5617. 'An error occurred while rendering the page.'), pageView.error);
  5618. }
  5619. // If the page is still visible when it has finished rendering,
  5620. // ensure that the page number input loading indicator is hidden.
  5621. if ((pageIndex + 1) === PDFViewerApplication.page) {
  5622. var pageNumberInput = document.getElementById('pageNumber');
  5623. pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
  5624. }
  5625. }, true);
  5626. window.addEventListener('presentationmodechanged', function (e) {
  5627. var active = e.detail.active;
  5628. var switchInProgress = e.detail.switchInProgress;
  5629. PDFViewerApplication.pdfViewer.presentationModeState =
  5630. switchInProgress ? PresentationModeState.CHANGING :
  5631. active ? PresentationModeState.FULLSCREEN : PresentationModeState.NORMAL;
  5632. });
  5633. function updateViewarea() {
  5634. if (!PDFViewerApplication.initialized) {
  5635. return;
  5636. }
  5637. PDFViewerApplication.pdfViewer.update();
  5638. }
  5639. window.addEventListener('updateviewarea', function () {
  5640. if (!PDFViewerApplication.initialized) {
  5641. return;
  5642. }
  5643. var location = PDFViewerApplication.pdfViewer.location;
  5644. PDFViewerApplication.store.initializedPromise.then(function() {
  5645. PDFViewerApplication.store.setMultiple({
  5646. 'exists': true,
  5647. 'page': location.pageNumber,
  5648. 'zoom': location.scale,
  5649. 'scrollLeft': location.left,
  5650. 'scrollTop': location.top
  5651. }).catch(function() {
  5652. // unable to write to storage
  5653. });
  5654. });
  5655. var href = PDFViewerApplication.getAnchorUrl(location.pdfOpenParams);
  5656. document.getElementById('viewBookmark').href = href;
  5657. document.getElementById('secondaryViewBookmark').href = href;
  5658. // Update the current bookmark in the browsing history.
  5659. PDFHistory.updateCurrentBookmark(location.pdfOpenParams, location.pageNumber);
  5660. // Show/hide the loading indicator in the page number input element.
  5661. var pageNumberInput = document.getElementById('pageNumber');
  5662. var currentPage =
  5663. PDFViewerApplication.pdfViewer.getPageView(PDFViewerApplication.page - 1);
  5664. if (currentPage.renderingState === RenderingStates.FINISHED) {
  5665. pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
  5666. } else {
  5667. pageNumberInput.classList.add(PAGE_NUMBER_LOADING_INDICATOR);
  5668. }
  5669. }, true);
  5670. window.addEventListener('resize', function webViewerResize(evt) {
  5671. if (PDFViewerApplication.initialized &&
  5672. (document.getElementById('pageWidthOption').selected ||
  5673. document.getElementById('pageFitOption').selected ||
  5674. document.getElementById('pageAutoOption').selected)) {
  5675. var selectedScale = document.getElementById('scaleSelect').value;
  5676. PDFViewerApplication.setScale(selectedScale, false);
  5677. }
  5678. updateViewarea();
  5679. // Set the 'max-height' CSS property of the secondary toolbar.
  5680. SecondaryToolbar.setMaxHeight(document.getElementById('viewerContainer'));
  5681. });
  5682. window.addEventListener('hashchange', function webViewerHashchange(evt) {
  5683. if (PDFHistory.isHashChangeUnlocked) {
  5684. PDFViewerApplication.setHash(document.location.hash.substring(1));
  5685. }
  5686. });
  5687. window.addEventListener('change', function webViewerChange(evt) {
  5688. var files = evt.target.files;
  5689. if (!files || files.length === 0) {
  5690. return;
  5691. }
  5692. var file = files[0];
  5693. if (!PDFJS.disableCreateObjectURL &&
  5694. typeof URL !== 'undefined' && URL.createObjectURL) {
  5695. PDFViewerApplication.open(URL.createObjectURL(file), 0);
  5696. } else {
  5697. // Read the local file into a Uint8Array.
  5698. var fileReader = new FileReader();
  5699. fileReader.onload = function webViewerChangeFileReaderOnload(evt) {
  5700. var buffer = evt.target.result;
  5701. var uint8Array = new Uint8Array(buffer);
  5702. PDFViewerApplication.open(uint8Array, 0);
  5703. };
  5704. fileReader.readAsArrayBuffer(file);
  5705. }
  5706. PDFViewerApplication.setTitleUsingUrl(file.name);
  5707. // URL does not reflect proper document location - hiding some icons.
  5708. document.getElementById('viewBookmark').setAttribute('hidden', 'true');
  5709. document.getElementById('secondaryViewBookmark').
  5710. setAttribute('hidden', 'true');
  5711. document.getElementById('download').setAttribute('hidden', 'true');
  5712. document.getElementById('secondaryDownload').setAttribute('hidden', 'true');
  5713. }, true);
  5714. function selectScaleOption(value) {
  5715. var options = document.getElementById('scaleSelect').options;
  5716. var predefinedValueFound = false;
  5717. for (var i = 0; i < options.length; i++) {
  5718. var option = options[i];
  5719. if (option.value !== value) {
  5720. option.selected = false;
  5721. continue;
  5722. }
  5723. option.selected = true;
  5724. predefinedValueFound = true;
  5725. }
  5726. return predefinedValueFound;
  5727. }
  5728. window.addEventListener('localized', function localized(evt) {
  5729. document.getElementsByTagName('html')[0].dir = mozL10n.getDirection();
  5730. PDFViewerApplication.animationStartedPromise.then(function() {
  5731. // Adjust the width of the zoom box to fit the content.
  5732. // Note: If the window is narrow enough that the zoom box is not visible,
  5733. // we temporarily show it to be able to adjust its width.
  5734. var container = document.getElementById('scaleSelectContainer');
  5735. if (container.clientWidth === 0) {
  5736. container.setAttribute('style', 'display: inherit;');
  5737. }
  5738. if (container.clientWidth > 0) {
  5739. var select = document.getElementById('scaleSelect');
  5740. select.setAttribute('style', 'min-width: inherit;');
  5741. var width = select.clientWidth + SCALE_SELECT_CONTAINER_PADDING;
  5742. select.setAttribute('style', 'min-width: ' +
  5743. (width + SCALE_SELECT_PADDING) + 'px;');
  5744. container.setAttribute('style', 'min-width: ' + width + 'px; ' +
  5745. 'max-width: ' + width + 'px;');
  5746. }
  5747. // Set the 'max-height' CSS property of the secondary toolbar.
  5748. SecondaryToolbar.setMaxHeight(document.getElementById('viewerContainer'));
  5749. });
  5750. }, true);
  5751. window.addEventListener('scalechange', function scalechange(evt) {
  5752. document.getElementById('zoomOut').disabled = (evt.scale === MIN_SCALE);
  5753. document.getElementById('zoomIn').disabled = (evt.scale === MAX_SCALE);
  5754. var customScaleOption = document.getElementById('customScaleOption');
  5755. customScaleOption.selected = false;
  5756. if (!PDFViewerApplication.updateScaleControls &&
  5757. (document.getElementById('pageWidthOption').selected ||
  5758. document.getElementById('pageFitOption').selected ||
  5759. document.getElementById('pageAutoOption').selected)) {
  5760. updateViewarea();
  5761. return;
  5762. }
  5763. if (evt.presetValue) {
  5764. selectScaleOption(evt.presetValue);
  5765. updateViewarea();
  5766. return;
  5767. }
  5768. var predefinedValueFound = selectScaleOption('' + evt.scale);
  5769. if (!predefinedValueFound) {
  5770. customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%';
  5771. customScaleOption.selected = true;
  5772. }
  5773. updateViewarea();
  5774. }, true);
  5775. window.addEventListener('pagechange', function pagechange(evt) {
  5776. var page = evt.pageNumber;
  5777. if (evt.previousPageNumber !== page) {
  5778. document.getElementById('pageNumber').value = page;
  5779. PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(page);
  5780. }
  5781. var numPages = PDFViewerApplication.pagesCount;
  5782. document.getElementById('previous').disabled = (page <= 1);
  5783. document.getElementById('next').disabled = (page >= numPages);
  5784. document.getElementById('firstPage').disabled = (page <= 1);
  5785. document.getElementById('lastPage').disabled = (page >= numPages);
  5786. // checking if the this.page was called from the updateViewarea function
  5787. if (evt.updateInProgress) {
  5788. return;
  5789. }
  5790. // Avoid scrolling the first page during loading
  5791. if (this.loading && page === 1) {
  5792. return;
  5793. }
  5794. PDFViewerApplication.pdfViewer.scrollPageIntoView(page);
  5795. }, true);
  5796. function handleMouseWheel(evt) {
  5797. var MOUSE_WHEEL_DELTA_FACTOR = 40;
  5798. var ticks = (evt.type === 'DOMMouseScroll') ? -evt.detail :
  5799. evt.wheelDelta / MOUSE_WHEEL_DELTA_FACTOR;
  5800. var direction = (ticks < 0) ? 'zoomOut' : 'zoomIn';
  5801. if (PresentationMode.active) {
  5802. evt.preventDefault();
  5803. PDFViewerApplication.mouseScroll(ticks * MOUSE_WHEEL_DELTA_FACTOR);
  5804. } else if (evt.ctrlKey) { // Only zoom the pages, not the entire viewer
  5805. evt.preventDefault();
  5806. PDFViewerApplication[direction](Math.abs(ticks));
  5807. }
  5808. }
  5809. window.addEventListener('DOMMouseScroll', handleMouseWheel);
  5810. window.addEventListener('mousewheel', handleMouseWheel);
  5811. window.addEventListener('click', function click(evt) {
  5812. if (!PresentationMode.active) {
  5813. if (SecondaryToolbar.opened &&
  5814. PDFViewerApplication.pdfViewer.containsElement(evt.target)) {
  5815. SecondaryToolbar.close();
  5816. }
  5817. } else if (evt.button === 0) {
  5818. // Necessary since preventDefault() in 'mousedown' won't stop
  5819. // the event propagation in all circumstances in presentation mode.
  5820. evt.preventDefault();
  5821. }
  5822. }, false);
  5823. window.addEventListener('keydown', function keydown(evt) {
  5824. if (OverlayManager.active) {
  5825. return;
  5826. }
  5827. var handled = false;
  5828. var cmd = (evt.ctrlKey ? 1 : 0) |
  5829. (evt.altKey ? 2 : 0) |
  5830. (evt.shiftKey ? 4 : 0) |
  5831. (evt.metaKey ? 8 : 0);
  5832. // First, handle the key bindings that are independent whether an input
  5833. // control is selected or not.
  5834. if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
  5835. // either CTRL or META key with optional SHIFT.
  5836. var pdfViewer = PDFViewerApplication.pdfViewer;
  5837. var inPresentationMode =
  5838. pdfViewer.presentationModeState === PresentationModeState.CHANGING ||
  5839. pdfViewer.presentationModeState === PresentationModeState.FULLSCREEN;
  5840. switch (evt.keyCode) {
  5841. case 70: // f
  5842. if (!PDFViewerApplication.supportsIntegratedFind) {
  5843. PDFViewerApplication.findBar.open();
  5844. handled = true;
  5845. }
  5846. break;
  5847. case 71: // g
  5848. if (!PDFViewerApplication.supportsIntegratedFind) {
  5849. PDFViewerApplication.findBar.dispatchEvent('again',
  5850. cmd === 5 || cmd === 12);
  5851. handled = true;
  5852. }
  5853. break;
  5854. case 61: // FF/Mac '='
  5855. case 107: // FF '+' and '='
  5856. case 187: // Chrome '+'
  5857. case 171: // FF with German keyboard
  5858. if (!inPresentationMode) {
  5859. PDFViewerApplication.zoomIn();
  5860. }
  5861. handled = true;
  5862. break;
  5863. case 173: // FF/Mac '-'
  5864. case 109: // FF '-'
  5865. case 189: // Chrome '-'
  5866. if (!inPresentationMode) {
  5867. PDFViewerApplication.zoomOut();
  5868. }
  5869. handled = true;
  5870. break;
  5871. case 48: // '0'
  5872. case 96: // '0' on Numpad of Swedish keyboard
  5873. if (!inPresentationMode) {
  5874. // keeping it unhandled (to restore page zoom to 100%)
  5875. setTimeout(function () {
  5876. // ... and resetting the scale after browser adjusts its scale
  5877. PDFViewerApplication.setScale(DEFAULT_SCALE, true);
  5878. });
  5879. handled = false;
  5880. }
  5881. break;
  5882. }
  5883. }
  5884. // CTRL or META without shift
  5885. if (cmd === 1 || cmd === 8) {
  5886. switch (evt.keyCode) {
  5887. case 83: // s
  5888. PDFViewerApplication.download();
  5889. handled = true;
  5890. break;
  5891. }
  5892. }
  5893. // CTRL+ALT or Option+Command
  5894. if (cmd === 3 || cmd === 10) {
  5895. switch (evt.keyCode) {
  5896. case 80: // p
  5897. SecondaryToolbar.presentationModeClick();
  5898. handled = true;
  5899. break;
  5900. case 71: // g
  5901. // focuses input#pageNumber field
  5902. document.getElementById('pageNumber').select();
  5903. handled = true;
  5904. break;
  5905. }
  5906. }
  5907. if (handled) {
  5908. evt.preventDefault();
  5909. return;
  5910. }
  5911. // Some shortcuts should not get handled if a control/input element
  5912. // is selected.
  5913. var curElement = document.activeElement || document.querySelector(':focus');
  5914. var curElementTagName = curElement && curElement.tagName.toUpperCase();
  5915. if (curElementTagName === 'INPUT' ||
  5916. curElementTagName === 'TEXTAREA' ||
  5917. curElementTagName === 'SELECT') {
  5918. // Make sure that the secondary toolbar is closed when Escape is pressed.
  5919. if (evt.keyCode !== 27) { // 'Esc'
  5920. return;
  5921. }
  5922. }
  5923. if (cmd === 0) { // no control key pressed at all.
  5924. switch (evt.keyCode) {
  5925. case 38: // up arrow
  5926. case 33: // pg up
  5927. case 8: // backspace
  5928. if (!PresentationMode.active &&
  5929. PDFViewerApplication.currentScaleValue !== 'page-fit') {
  5930. break;
  5931. }
  5932. /* in presentation mode */
  5933. /* falls through */
  5934. case 37: // left arrow
  5935. // horizontal scrolling using arrow keys
  5936. if (PDFViewerApplication.pdfViewer.isHorizontalScrollbarEnabled) {
  5937. break;
  5938. }
  5939. /* falls through */
  5940. case 75: // 'k'
  5941. case 80: // 'p'
  5942. PDFViewerApplication.page--;
  5943. handled = true;
  5944. break;
  5945. case 27: // esc key
  5946. if (SecondaryToolbar.opened) {
  5947. SecondaryToolbar.close();
  5948. handled = true;
  5949. }
  5950. if (!PDFViewerApplication.supportsIntegratedFind &&
  5951. PDFViewerApplication.findBar.opened) {
  5952. PDFViewerApplication.findBar.close();
  5953. handled = true;
  5954. }
  5955. break;
  5956. case 40: // down arrow
  5957. case 34: // pg down
  5958. case 32: // spacebar
  5959. if (!PresentationMode.active &&
  5960. PDFViewerApplication.currentScaleValue !== 'page-fit') {
  5961. break;
  5962. }
  5963. /* falls through */
  5964. case 39: // right arrow
  5965. // horizontal scrolling using arrow keys
  5966. if (PDFViewerApplication.pdfViewer.isHorizontalScrollbarEnabled) {
  5967. break;
  5968. }
  5969. /* falls through */
  5970. case 74: // 'j'
  5971. case 78: // 'n'
  5972. PDFViewerApplication.page++;
  5973. handled = true;
  5974. break;
  5975. case 36: // home
  5976. if (PresentationMode.active || PDFViewerApplication.page > 1) {
  5977. PDFViewerApplication.page = 1;
  5978. handled = true;
  5979. }
  5980. break;
  5981. case 35: // end
  5982. if (PresentationMode.active || (PDFViewerApplication.pdfDocument &&
  5983. PDFViewerApplication.page < PDFViewerApplication.pagesCount)) {
  5984. PDFViewerApplication.page = PDFViewerApplication.pagesCount;
  5985. handled = true;
  5986. }
  5987. break;
  5988. case 72: // 'h'
  5989. if (!PresentationMode.active) {
  5990. HandTool.toggle();
  5991. }
  5992. break;
  5993. case 82: // 'r'
  5994. PDFViewerApplication.rotatePages(90);
  5995. break;
  5996. }
  5997. }
  5998. if (cmd === 4) { // shift-key
  5999. switch (evt.keyCode) {
  6000. case 32: // spacebar
  6001. if (!PresentationMode.active &&
  6002. PDFViewerApplication.currentScaleValue !== 'page-fit') {
  6003. break;
  6004. }
  6005. PDFViewerApplication.page--;
  6006. handled = true;
  6007. break;
  6008. case 82: // 'r'
  6009. PDFViewerApplication.rotatePages(-90);
  6010. break;
  6011. }
  6012. }
  6013. if (!handled && !PresentationMode.active) {
  6014. // 33=Page Up 34=Page Down 35=End 36=Home
  6015. // 37=Left 38=Up 39=Right 40=Down
  6016. if (evt.keyCode >= 33 && evt.keyCode <= 40 &&
  6017. !PDFViewerApplication.pdfViewer.containsElement(curElement)) {
  6018. // The page container is not focused, but a page navigation key has been
  6019. // pressed. Change the focus to the viewer container to make sure that
  6020. // navigation by keyboard works as expected.
  6021. PDFViewerApplication.pdfViewer.focus();
  6022. }
  6023. // 32=Spacebar
  6024. if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') {
  6025. if (!PDFViewerApplication.pdfViewer.containsElement(curElement)) {
  6026. PDFViewerApplication.pdfViewer.focus();
  6027. }
  6028. }
  6029. }
  6030. if (cmd === 2) { // alt-key
  6031. switch (evt.keyCode) {
  6032. case 37: // left arrow
  6033. if (PresentationMode.active) {
  6034. PDFHistory.back();
  6035. handled = true;
  6036. }
  6037. break;
  6038. case 39: // right arrow
  6039. if (PresentationMode.active) {
  6040. PDFHistory.forward();
  6041. handled = true;
  6042. }
  6043. break;
  6044. }
  6045. }
  6046. if (handled) {
  6047. evt.preventDefault();
  6048. PDFViewerApplication.clearMouseScrollState();
  6049. }
  6050. });
  6051. window.addEventListener('beforeprint', function beforePrint(evt) {
  6052. PDFViewerApplication.beforePrint();
  6053. });
  6054. window.addEventListener('afterprint', function afterPrint(evt) {
  6055. PDFViewerApplication.afterPrint();
  6056. });
  6057. (function animationStartedClosure() {
  6058. // The offsetParent is not set until the pdf.js iframe or object is visible.
  6059. // Waiting for first animation.
  6060. PDFViewerApplication.animationStartedPromise = new Promise(
  6061. function (resolve) {
  6062. window.requestAnimationFrame(resolve);
  6063. });
  6064. })();