UNPKG

8.73 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.PDFOutlineViewer = void 0;
28
29var _base_tree_viewer = require("./base_tree_viewer.js");
30
31var _pdf = require("../pdf");
32
33var _ui_utils = require("./ui_utils.js");
34
35class PDFOutlineViewer extends _base_tree_viewer.BaseTreeViewer {
36 constructor(options) {
37 super(options);
38 this.linkService = options.linkService;
39
40 this.eventBus._on("toggleoutlinetree", this._toggleAllTreeItems.bind(this));
41
42 this.eventBus._on("currentoutlineitem", this._currentOutlineItem.bind(this));
43
44 this.eventBus._on("pagechanging", evt => {
45 this._currentPageNumber = evt.pageNumber;
46 });
47
48 this.eventBus._on("pagesloaded", evt => {
49 this._isPagesLoaded = !!evt.pagesCount;
50
51 if (this._currentOutlineItemCapability && !this._currentOutlineItemCapability.settled) {
52 this._currentOutlineItemCapability.resolve(this._isPagesLoaded);
53 }
54 });
55
56 this.eventBus._on("sidebarviewchanged", evt => {
57 this._sidebarView = evt.view;
58 });
59 }
60
61 reset() {
62 super.reset();
63 this._outline = null;
64 this._pageNumberToDestHashCapability = null;
65 this._currentPageNumber = 1;
66 this._isPagesLoaded = null;
67
68 if (this._currentOutlineItemCapability && !this._currentOutlineItemCapability.settled) {
69 this._currentOutlineItemCapability.resolve(false);
70 }
71
72 this._currentOutlineItemCapability = null;
73 }
74
75 _dispatchEvent(outlineCount) {
76 this._currentOutlineItemCapability = (0, _pdf.createPromiseCapability)();
77
78 if (outlineCount === 0 || this._pdfDocument?.loadingParams.disableAutoFetch) {
79 this._currentOutlineItemCapability.resolve(false);
80 } else if (this._isPagesLoaded !== null) {
81 this._currentOutlineItemCapability.resolve(this._isPagesLoaded);
82 }
83
84 this.eventBus.dispatch("outlineloaded", {
85 source: this,
86 outlineCount,
87 currentOutlineItemPromise: this._currentOutlineItemCapability.promise
88 });
89 }
90
91 _bindLink(element, {
92 url,
93 newWindow,
94 dest
95 }) {
96 const {
97 linkService
98 } = this;
99
100 if (url) {
101 linkService.addLinkAttributes(element, url, newWindow);
102 return;
103 }
104
105 element.href = linkService.getDestinationHash(dest);
106
107 element.onclick = evt => {
108 this._updateCurrentTreeItem(evt.target.parentNode);
109
110 if (dest) {
111 linkService.goToDestination(dest);
112 }
113
114 return false;
115 };
116 }
117
118 _setStyles(element, {
119 bold,
120 italic
121 }) {
122 if (bold) {
123 element.style.fontWeight = "bold";
124 }
125
126 if (italic) {
127 element.style.fontStyle = "italic";
128 }
129 }
130
131 _addToggleButton(div, {
132 count,
133 items
134 }) {
135 let hidden = false;
136
137 if (count < 0) {
138 let totalCount = items.length;
139
140 if (totalCount > 0) {
141 const queue = [...items];
142
143 while (queue.length > 0) {
144 const {
145 count: nestedCount,
146 items: nestedItems
147 } = queue.shift();
148
149 if (nestedCount > 0 && nestedItems.length > 0) {
150 totalCount += nestedItems.length;
151 queue.push(...nestedItems);
152 }
153 }
154 }
155
156 if (Math.abs(count) === totalCount) {
157 hidden = true;
158 }
159 }
160
161 super._addToggleButton(div, hidden);
162 }
163
164 _toggleAllTreeItems() {
165 if (!this._outline) {
166 return;
167 }
168
169 super._toggleAllTreeItems();
170 }
171
172 render({
173 outline,
174 pdfDocument
175 }) {
176 if (this._outline) {
177 this.reset();
178 }
179
180 this._outline = outline || null;
181 this._pdfDocument = pdfDocument || null;
182
183 if (!outline) {
184 this._dispatchEvent(0);
185
186 return;
187 }
188
189 const fragment = document.createDocumentFragment();
190 const queue = [{
191 parent: fragment,
192 items: outline
193 }];
194 let outlineCount = 0,
195 hasAnyNesting = false;
196
197 while (queue.length > 0) {
198 const levelData = queue.shift();
199
200 for (const item of levelData.items) {
201 const div = document.createElement("div");
202 div.className = "treeItem";
203 const element = document.createElement("a");
204
205 this._bindLink(element, item);
206
207 this._setStyles(element, item);
208
209 element.textContent = this._normalizeTextContent(item.title);
210 div.append(element);
211
212 if (item.items.length > 0) {
213 hasAnyNesting = true;
214
215 this._addToggleButton(div, item);
216
217 const itemsDiv = document.createElement("div");
218 itemsDiv.className = "treeItems";
219 div.append(itemsDiv);
220 queue.push({
221 parent: itemsDiv,
222 items: item.items
223 });
224 }
225
226 levelData.parent.append(div);
227 outlineCount++;
228 }
229 }
230
231 this._finishRendering(fragment, outlineCount, hasAnyNesting);
232 }
233
234 async _currentOutlineItem() {
235 if (!this._isPagesLoaded) {
236 throw new Error("_currentOutlineItem: All pages have not been loaded.");
237 }
238
239 if (!this._outline || !this._pdfDocument) {
240 return;
241 }
242
243 const pageNumberToDestHash = await this._getPageNumberToDestHash(this._pdfDocument);
244
245 if (!pageNumberToDestHash) {
246 return;
247 }
248
249 this._updateCurrentTreeItem(null);
250
251 if (this._sidebarView !== _ui_utils.SidebarView.OUTLINE) {
252 return;
253 }
254
255 for (let i = this._currentPageNumber; i > 0; i--) {
256 const destHash = pageNumberToDestHash.get(i);
257
258 if (!destHash) {
259 continue;
260 }
261
262 const linkElement = this.container.querySelector(`a[href="${destHash}"]`);
263
264 if (!linkElement) {
265 continue;
266 }
267
268 this._scrollToCurrentTreeItem(linkElement.parentNode);
269
270 break;
271 }
272 }
273
274 async _getPageNumberToDestHash(pdfDocument) {
275 if (this._pageNumberToDestHashCapability) {
276 return this._pageNumberToDestHashCapability.promise;
277 }
278
279 this._pageNumberToDestHashCapability = (0, _pdf.createPromiseCapability)();
280 const pageNumberToDestHash = new Map(),
281 pageNumberNesting = new Map();
282 const queue = [{
283 nesting: 0,
284 items: this._outline
285 }];
286
287 while (queue.length > 0) {
288 const levelData = queue.shift(),
289 currentNesting = levelData.nesting;
290
291 for (const {
292 dest,
293 items
294 } of levelData.items) {
295 let explicitDest, pageNumber;
296
297 if (typeof dest === "string") {
298 explicitDest = await pdfDocument.getDestination(dest);
299
300 if (pdfDocument !== this._pdfDocument) {
301 return null;
302 }
303 } else {
304 explicitDest = dest;
305 }
306
307 if (Array.isArray(explicitDest)) {
308 const [destRef] = explicitDest;
309
310 if (typeof destRef === "object" && destRef !== null) {
311 pageNumber = this.linkService._cachedPageNumber(destRef);
312
313 if (!pageNumber) {
314 try {
315 pageNumber = (await pdfDocument.getPageIndex(destRef)) + 1;
316
317 if (pdfDocument !== this._pdfDocument) {
318 return null;
319 }
320
321 this.linkService.cachePageRef(pageNumber, destRef);
322 } catch (ex) {}
323 }
324 } else if (Number.isInteger(destRef)) {
325 pageNumber = destRef + 1;
326 }
327
328 if (Number.isInteger(pageNumber) && (!pageNumberToDestHash.has(pageNumber) || currentNesting > pageNumberNesting.get(pageNumber))) {
329 const destHash = this.linkService.getDestinationHash(dest);
330 pageNumberToDestHash.set(pageNumber, destHash);
331 pageNumberNesting.set(pageNumber, currentNesting);
332 }
333 }
334
335 if (items.length > 0) {
336 queue.push({
337 nesting: currentNesting + 1,
338 items
339 });
340 }
341 }
342 }
343
344 this._pageNumberToDestHashCapability.resolve(pageNumberToDestHash.size > 0 ? pageNumberToDestHash : null);
345
346 return this._pageNumberToDestHashCapability.promise;
347 }
348
349}
350
351exports.PDFOutlineViewer = PDFOutlineViewer;
\No newline at end of file