UNPKG

12.7 kBJavaScriptView Raw
1/**
2 * @licstart The following is the entire license notice for the
3 * JavaScript code in this page
4 *
5 * Copyright 2022 Mozilla Foundation
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 * @licend The above is the entire license notice for the
20 * JavaScript code in this page
21 */
22"use strict";
23
24Object.defineProperty(exports, "__esModule", {
25 value: true
26});
27exports.SimpleLinkService = exports.PDFLinkService = exports.LinkTarget = void 0;
28
29var _ui_utils = require("./ui_utils.js");
30
31const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
32const LinkTarget = {
33 NONE: 0,
34 SELF: 1,
35 BLANK: 2,
36 PARENT: 3,
37 TOP: 4
38};
39exports.LinkTarget = LinkTarget;
40
41function addLinkAttributes(link, {
42 url,
43 target,
44 rel,
45 enabled = true
46} = {}) {
47 if (!url || typeof url !== "string") {
48 throw new Error('A valid "url" parameter must provided.');
49 }
50
51 const urlNullRemoved = (0, _ui_utils.removeNullCharacters)(url);
52
53 if (enabled) {
54 link.href = link.title = urlNullRemoved;
55 } else {
56 link.href = "";
57 link.title = `Disabled: ${urlNullRemoved}`;
58
59 link.onclick = () => {
60 return false;
61 };
62 }
63
64 let targetStr = "";
65
66 switch (target) {
67 case LinkTarget.NONE:
68 break;
69
70 case LinkTarget.SELF:
71 targetStr = "_self";
72 break;
73
74 case LinkTarget.BLANK:
75 targetStr = "_blank";
76 break;
77
78 case LinkTarget.PARENT:
79 targetStr = "_parent";
80 break;
81
82 case LinkTarget.TOP:
83 targetStr = "_top";
84 break;
85 }
86
87 link.target = targetStr;
88 link.rel = typeof rel === "string" ? rel : DEFAULT_LINK_REL;
89}
90
91class PDFLinkService {
92 #pagesRefCache = new Map();
93
94 constructor({
95 eventBus,
96 externalLinkTarget = null,
97 externalLinkRel = null,
98 ignoreDestinationZoom = false
99 } = {}) {
100 this.eventBus = eventBus;
101 this.externalLinkTarget = externalLinkTarget;
102 this.externalLinkRel = externalLinkRel;
103 this.externalLinkEnabled = true;
104 this._ignoreDestinationZoom = ignoreDestinationZoom;
105 this.baseUrl = null;
106 this.pdfDocument = null;
107 this.pdfViewer = null;
108 this.pdfHistory = null;
109 }
110
111 setDocument(pdfDocument, baseUrl = null) {
112 this.baseUrl = baseUrl;
113 this.pdfDocument = pdfDocument;
114 this.#pagesRefCache.clear();
115 }
116
117 setViewer(pdfViewer) {
118 this.pdfViewer = pdfViewer;
119 }
120
121 setHistory(pdfHistory) {
122 this.pdfHistory = pdfHistory;
123 }
124
125 get pagesCount() {
126 return this.pdfDocument ? this.pdfDocument.numPages : 0;
127 }
128
129 get page() {
130 return this.pdfViewer.currentPageNumber;
131 }
132
133 set page(value) {
134 this.pdfViewer.currentPageNumber = value;
135 }
136
137 get rotation() {
138 return this.pdfViewer.pagesRotation;
139 }
140
141 set rotation(value) {
142 this.pdfViewer.pagesRotation = value;
143 }
144
145 #goToDestinationHelper(rawDest, namedDest = null, explicitDest) {
146 const destRef = explicitDest[0];
147 let pageNumber;
148
149 if (typeof destRef === "object" && destRef !== null) {
150 pageNumber = this._cachedPageNumber(destRef);
151
152 if (!pageNumber) {
153 this.pdfDocument.getPageIndex(destRef).then(pageIndex => {
154 this.cachePageRef(pageIndex + 1, destRef);
155 this.#goToDestinationHelper(rawDest, namedDest, explicitDest);
156 }).catch(() => {
157 console.error(`PDFLinkService.#goToDestinationHelper: "${destRef}" is not ` + `a valid page reference, for dest="${rawDest}".`);
158 });
159 return;
160 }
161 } else if (Number.isInteger(destRef)) {
162 pageNumber = destRef + 1;
163 } else {
164 console.error(`PDFLinkService.#goToDestinationHelper: "${destRef}" is not ` + `a valid destination reference, for dest="${rawDest}".`);
165 return;
166 }
167
168 if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
169 console.error(`PDFLinkService.#goToDestinationHelper: "${pageNumber}" is not ` + `a valid page number, for dest="${rawDest}".`);
170 return;
171 }
172
173 if (this.pdfHistory) {
174 this.pdfHistory.pushCurrentPosition();
175 this.pdfHistory.push({
176 namedDest,
177 explicitDest,
178 pageNumber
179 });
180 }
181
182 this.pdfViewer.scrollPageIntoView({
183 pageNumber,
184 destArray: explicitDest,
185 ignoreDestinationZoom: this._ignoreDestinationZoom
186 });
187 }
188
189 async goToDestination(dest) {
190 if (!this.pdfDocument) {
191 return;
192 }
193
194 let namedDest, explicitDest;
195
196 if (typeof dest === "string") {
197 namedDest = dest;
198 explicitDest = await this.pdfDocument.getDestination(dest);
199 } else {
200 namedDest = null;
201 explicitDest = await dest;
202 }
203
204 if (!Array.isArray(explicitDest)) {
205 console.error(`PDFLinkService.goToDestination: "${explicitDest}" is not ` + `a valid destination array, for dest="${dest}".`);
206 return;
207 }
208
209 this.#goToDestinationHelper(dest, namedDest, explicitDest);
210 }
211
212 goToPage(val) {
213 if (!this.pdfDocument) {
214 return;
215 }
216
217 const pageNumber = typeof val === "string" && this.pdfViewer.pageLabelToPageNumber(val) || val | 0;
218
219 if (!(Number.isInteger(pageNumber) && pageNumber > 0 && pageNumber <= this.pagesCount)) {
220 console.error(`PDFLinkService.goToPage: "${val}" is not a valid page.`);
221 return;
222 }
223
224 if (this.pdfHistory) {
225 this.pdfHistory.pushCurrentPosition();
226 this.pdfHistory.pushPage(pageNumber);
227 }
228
229 this.pdfViewer.scrollPageIntoView({
230 pageNumber
231 });
232 }
233
234 addLinkAttributes(link, url, newWindow = false) {
235 addLinkAttributes(link, {
236 url,
237 target: newWindow ? LinkTarget.BLANK : this.externalLinkTarget,
238 rel: this.externalLinkRel,
239 enabled: this.externalLinkEnabled
240 });
241 }
242
243 getDestinationHash(dest) {
244 if (typeof dest === "string") {
245 if (dest.length > 0) {
246 return this.getAnchorUrl("#" + escape(dest));
247 }
248 } else if (Array.isArray(dest)) {
249 const str = JSON.stringify(dest);
250
251 if (str.length > 0) {
252 return this.getAnchorUrl("#" + escape(str));
253 }
254 }
255
256 return this.getAnchorUrl("");
257 }
258
259 getAnchorUrl(anchor) {
260 return (this.baseUrl || "") + anchor;
261 }
262
263 setHash(hash) {
264 if (!this.pdfDocument) {
265 return;
266 }
267
268 let pageNumber, dest;
269
270 if (hash.includes("=")) {
271 const params = (0, _ui_utils.parseQueryString)(hash);
272
273 if (params.has("search")) {
274 this.eventBus.dispatch("findfromurlhash", {
275 source: this,
276 query: params.get("search").replace(/"/g, ""),
277 phraseSearch: params.get("phrase") === "true"
278 });
279 }
280
281 if (params.has("page")) {
282 pageNumber = params.get("page") | 0 || 1;
283 }
284
285 if (params.has("zoom")) {
286 const zoomArgs = params.get("zoom").split(",");
287 const zoomArg = zoomArgs[0];
288 const zoomArgNumber = parseFloat(zoomArg);
289
290 if (!zoomArg.includes("Fit")) {
291 dest = [null, {
292 name: "XYZ"
293 }, zoomArgs.length > 1 ? zoomArgs[1] | 0 : null, zoomArgs.length > 2 ? zoomArgs[2] | 0 : null, zoomArgNumber ? zoomArgNumber / 100 : zoomArg];
294 } else {
295 if (zoomArg === "Fit" || zoomArg === "FitB") {
296 dest = [null, {
297 name: zoomArg
298 }];
299 } else if (zoomArg === "FitH" || zoomArg === "FitBH" || zoomArg === "FitV" || zoomArg === "FitBV") {
300 dest = [null, {
301 name: zoomArg
302 }, zoomArgs.length > 1 ? zoomArgs[1] | 0 : null];
303 } else if (zoomArg === "FitR") {
304 if (zoomArgs.length !== 5) {
305 console.error('PDFLinkService.setHash: Not enough parameters for "FitR".');
306 } else {
307 dest = [null, {
308 name: zoomArg
309 }, zoomArgs[1] | 0, zoomArgs[2] | 0, zoomArgs[3] | 0, zoomArgs[4] | 0];
310 }
311 } else {
312 console.error(`PDFLinkService.setHash: "${zoomArg}" is not a valid zoom value.`);
313 }
314 }
315 }
316
317 if (dest) {
318 this.pdfViewer.scrollPageIntoView({
319 pageNumber: pageNumber || this.page,
320 destArray: dest,
321 allowNegativeOffset: true
322 });
323 } else if (pageNumber) {
324 this.page = pageNumber;
325 }
326
327 if (params.has("pagemode")) {
328 this.eventBus.dispatch("pagemode", {
329 source: this,
330 mode: params.get("pagemode")
331 });
332 }
333
334 if (params.has("nameddest")) {
335 this.goToDestination(params.get("nameddest"));
336 }
337 } else {
338 dest = unescape(hash);
339
340 try {
341 dest = JSON.parse(dest);
342
343 if (!Array.isArray(dest)) {
344 dest = dest.toString();
345 }
346 } catch (ex) {}
347
348 if (typeof dest === "string" || PDFLinkService.#isValidExplicitDestination(dest)) {
349 this.goToDestination(dest);
350 return;
351 }
352
353 console.error(`PDFLinkService.setHash: "${unescape(hash)}" is not a valid destination.`);
354 }
355 }
356
357 executeNamedAction(action) {
358 switch (action) {
359 case "GoBack":
360 this.pdfHistory?.back();
361 break;
362
363 case "GoForward":
364 this.pdfHistory?.forward();
365 break;
366
367 case "NextPage":
368 this.pdfViewer.nextPage();
369 break;
370
371 case "PrevPage":
372 this.pdfViewer.previousPage();
373 break;
374
375 case "LastPage":
376 this.page = this.pagesCount;
377 break;
378
379 case "FirstPage":
380 this.page = 1;
381 break;
382
383 default:
384 break;
385 }
386
387 this.eventBus.dispatch("namedaction", {
388 source: this,
389 action
390 });
391 }
392
393 cachePageRef(pageNum, pageRef) {
394 if (!pageRef) {
395 return;
396 }
397
398 const refStr = pageRef.gen === 0 ? `${pageRef.num}R` : `${pageRef.num}R${pageRef.gen}`;
399 this.#pagesRefCache.set(refStr, pageNum);
400 }
401
402 _cachedPageNumber(pageRef) {
403 if (!pageRef) {
404 return null;
405 }
406
407 const refStr = pageRef.gen === 0 ? `${pageRef.num}R` : `${pageRef.num}R${pageRef.gen}`;
408 return this.#pagesRefCache.get(refStr) || null;
409 }
410
411 isPageVisible(pageNumber) {
412 return this.pdfViewer.isPageVisible(pageNumber);
413 }
414
415 isPageCached(pageNumber) {
416 return this.pdfViewer.isPageCached(pageNumber);
417 }
418
419 static #isValidExplicitDestination(dest) {
420 if (!Array.isArray(dest)) {
421 return false;
422 }
423
424 const destLength = dest.length;
425
426 if (destLength < 2) {
427 return false;
428 }
429
430 const page = dest[0];
431
432 if (!(typeof page === "object" && Number.isInteger(page.num) && Number.isInteger(page.gen)) && !(Number.isInteger(page) && page >= 0)) {
433 return false;
434 }
435
436 const zoom = dest[1];
437
438 if (!(typeof zoom === "object" && typeof zoom.name === "string")) {
439 return false;
440 }
441
442 let allowNull = true;
443
444 switch (zoom.name) {
445 case "XYZ":
446 if (destLength !== 5) {
447 return false;
448 }
449
450 break;
451
452 case "Fit":
453 case "FitB":
454 return destLength === 2;
455
456 case "FitH":
457 case "FitBH":
458 case "FitV":
459 case "FitBV":
460 if (destLength !== 3) {
461 return false;
462 }
463
464 break;
465
466 case "FitR":
467 if (destLength !== 6) {
468 return false;
469 }
470
471 allowNull = false;
472 break;
473
474 default:
475 return false;
476 }
477
478 for (let i = 2; i < destLength; i++) {
479 const param = dest[i];
480
481 if (!(typeof param === "number" || allowNull && param === null)) {
482 return false;
483 }
484 }
485
486 return true;
487 }
488
489}
490
491exports.PDFLinkService = PDFLinkService;
492
493class SimpleLinkService {
494 constructor() {
495 this.externalLinkEnabled = true;
496 }
497
498 get pagesCount() {
499 return 0;
500 }
501
502 get page() {
503 return 0;
504 }
505
506 set page(value) {}
507
508 get rotation() {
509 return 0;
510 }
511
512 set rotation(value) {}
513
514 async goToDestination(dest) {}
515
516 goToPage(val) {}
517
518 addLinkAttributes(link, url, newWindow = false) {
519 addLinkAttributes(link, {
520 url,
521 enabled: this.externalLinkEnabled
522 });
523 }
524
525 getDestinationHash(dest) {
526 return "#";
527 }
528
529 getAnchorUrl(hash) {
530 return "#";
531 }
532
533 setHash(hash) {}
534
535 executeNamedAction(action) {}
536
537 cachePageRef(pageNum, pageRef) {}
538
539 isPageVisible(pageNumber) {
540 return true;
541 }
542
543 isPageCached(pageNumber) {
544 return true;
545 }
546
547}
548
549exports.SimpleLinkService = SimpleLinkService;
\No newline at end of file