2
0

pdf_outline_viewer.js 8.5 KB

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