pdf_outline_viewer.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * Javascript code in this page
  4. *
  5. * Copyright 2021 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 _pdf = require("../pdf");
  28. var _base_tree_viewer = require("./base_tree_viewer.js");
  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 = false;
  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) {
  65. this._currentOutlineItemCapability.resolve(true);
  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. (0, _pdf.addLinkAttributes)(element, {
  83. url,
  84. target: newWindow ? _pdf.LinkTarget.BLANK : linkService.externalLinkTarget,
  85. rel: linkService.externalLinkRel,
  86. enabled: linkService.externalLinkEnabled
  87. });
  88. return;
  89. }
  90. element.href = linkService.getDestinationHash(dest);
  91. element.onclick = evt => {
  92. this._updateCurrentTreeItem(evt.target.parentNode);
  93. if (dest) {
  94. linkService.goToDestination(dest);
  95. }
  96. return false;
  97. };
  98. }
  99. _setStyles(element, {
  100. bold,
  101. italic
  102. }) {
  103. if (bold) {
  104. element.style.fontWeight = "bold";
  105. }
  106. if (italic) {
  107. element.style.fontStyle = "italic";
  108. }
  109. }
  110. _addToggleButton(div, {
  111. count,
  112. items
  113. }) {
  114. let hidden = false;
  115. if (count < 0) {
  116. let totalCount = items.length;
  117. if (totalCount > 0) {
  118. const queue = [...items];
  119. while (queue.length > 0) {
  120. const {
  121. count: nestedCount,
  122. items: nestedItems
  123. } = queue.shift();
  124. if (nestedCount > 0 && nestedItems.length > 0) {
  125. totalCount += nestedItems.length;
  126. queue.push(...nestedItems);
  127. }
  128. }
  129. }
  130. if (Math.abs(count) === totalCount) {
  131. hidden = true;
  132. }
  133. }
  134. super._addToggleButton(div, hidden);
  135. }
  136. _toggleAllTreeItems() {
  137. if (!this._outline) {
  138. return;
  139. }
  140. super._toggleAllTreeItems();
  141. }
  142. render({
  143. outline,
  144. pdfDocument
  145. }) {
  146. if (this._outline) {
  147. this.reset();
  148. }
  149. this._outline = outline || null;
  150. this._pdfDocument = pdfDocument || null;
  151. if (!outline) {
  152. this._dispatchEvent(0);
  153. return;
  154. }
  155. const fragment = document.createDocumentFragment();
  156. const queue = [{
  157. parent: fragment,
  158. items: outline
  159. }];
  160. let outlineCount = 0,
  161. hasAnyNesting = false;
  162. while (queue.length > 0) {
  163. const levelData = queue.shift();
  164. for (const item of levelData.items) {
  165. const div = document.createElement("div");
  166. div.className = "treeItem";
  167. const element = document.createElement("a");
  168. this._bindLink(element, item);
  169. this._setStyles(element, item);
  170. element.textContent = this._normalizeTextContent(item.title);
  171. div.appendChild(element);
  172. if (item.items.length > 0) {
  173. hasAnyNesting = true;
  174. this._addToggleButton(div, item);
  175. const itemsDiv = document.createElement("div");
  176. itemsDiv.className = "treeItems";
  177. div.appendChild(itemsDiv);
  178. queue.push({
  179. parent: itemsDiv,
  180. items: item.items
  181. });
  182. }
  183. levelData.parent.appendChild(div);
  184. outlineCount++;
  185. }
  186. }
  187. this._finishRendering(fragment, outlineCount, hasAnyNesting);
  188. }
  189. async _currentOutlineItem() {
  190. if (!this._isPagesLoaded) {
  191. throw new Error("_currentOutlineItem: All pages have not been loaded.");
  192. }
  193. if (!this._outline || !this._pdfDocument) {
  194. return;
  195. }
  196. const pageNumberToDestHash = await this._getPageNumberToDestHash(this._pdfDocument);
  197. if (!pageNumberToDestHash) {
  198. return;
  199. }
  200. this._updateCurrentTreeItem(null);
  201. if (this._sidebarView !== _ui_utils.SidebarView.OUTLINE) {
  202. return;
  203. }
  204. for (let i = this._currentPageNumber; i > 0; i--) {
  205. const destHash = pageNumberToDestHash.get(i);
  206. if (!destHash) {
  207. continue;
  208. }
  209. const linkElement = this.container.querySelector(`a[href="${destHash}"]`);
  210. if (!linkElement) {
  211. continue;
  212. }
  213. this._scrollToCurrentTreeItem(linkElement.parentNode);
  214. break;
  215. }
  216. }
  217. async _getPageNumberToDestHash(pdfDocument) {
  218. if (this._pageNumberToDestHashCapability) {
  219. return this._pageNumberToDestHashCapability.promise;
  220. }
  221. this._pageNumberToDestHashCapability = (0, _pdf.createPromiseCapability)();
  222. const pageNumberToDestHash = new Map(),
  223. pageNumberNesting = new Map();
  224. const queue = [{
  225. nesting: 0,
  226. items: this._outline
  227. }];
  228. while (queue.length > 0) {
  229. const levelData = queue.shift(),
  230. currentNesting = levelData.nesting;
  231. for (const {
  232. dest,
  233. items
  234. } of levelData.items) {
  235. let explicitDest, pageNumber;
  236. if (typeof dest === "string") {
  237. explicitDest = await pdfDocument.getDestination(dest);
  238. if (pdfDocument !== this._pdfDocument) {
  239. return null;
  240. }
  241. } else {
  242. explicitDest = dest;
  243. }
  244. if (Array.isArray(explicitDest)) {
  245. const [destRef] = explicitDest;
  246. if (typeof destRef === "object" && destRef !== null) {
  247. pageNumber = this.linkService._cachedPageNumber(destRef);
  248. if (!pageNumber) {
  249. try {
  250. pageNumber = (await pdfDocument.getPageIndex(destRef)) + 1;
  251. if (pdfDocument !== this._pdfDocument) {
  252. return null;
  253. }
  254. this.linkService.cachePageRef(pageNumber, destRef);
  255. } catch (ex) {}
  256. }
  257. } else if (Number.isInteger(destRef)) {
  258. pageNumber = destRef + 1;
  259. }
  260. if (Number.isInteger(pageNumber) && (!pageNumberToDestHash.has(pageNumber) || currentNesting > pageNumberNesting.get(pageNumber))) {
  261. const destHash = this.linkService.getDestinationHash(dest);
  262. pageNumberToDestHash.set(pageNumber, destHash);
  263. pageNumberNesting.set(pageNumber, currentNesting);
  264. }
  265. }
  266. if (items.length > 0) {
  267. queue.push({
  268. nesting: currentNesting + 1,
  269. items
  270. });
  271. }
  272. }
  273. }
  274. this._pageNumberToDestHashCapability.resolve(pageNumberToDestHash.size > 0 ? pageNumberToDestHash : null);
  275. return this._pageNumberToDestHashCapability.promise;
  276. }
  277. }
  278. exports.PDFOutlineViewer = PDFOutlineViewer;