pdf_outline_viewer.js 9.2 KB

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