2
0

pdf_outline_viewer.js 8.0 KB

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