pdf_layer_viewer.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /* Copyright 2020 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. import { BaseTreeViewer } from "./base_tree_viewer.js";
  16. /**
  17. * @typedef {Object} PDFLayerViewerOptions
  18. * @property {HTMLDivElement} container - The viewer element.
  19. * @property {EventBus} eventBus - The application event bus.
  20. * @property {IL10n} l10n - Localization service.
  21. */
  22. /**
  23. * @typedef {Object} PDFLayerViewerRenderParameters
  24. * @property {OptionalContentConfig|null} optionalContentConfig - An
  25. * {OptionalContentConfig} instance.
  26. * @property {PDFDocument} pdfDocument - A {PDFDocument} instance.
  27. */
  28. class PDFLayerViewer extends BaseTreeViewer {
  29. constructor(options) {
  30. super(options);
  31. this.l10n = options.l10n;
  32. this.eventBus._on("optionalcontentconfigchanged", evt => {
  33. this.#updateLayers(evt.promise);
  34. });
  35. this.eventBus._on("resetlayers", () => {
  36. this.#updateLayers();
  37. });
  38. this.eventBus._on("togglelayerstree", this._toggleAllTreeItems.bind(this));
  39. }
  40. reset() {
  41. super.reset();
  42. this._optionalContentConfig = null;
  43. this._optionalContentHash = null;
  44. }
  45. /**
  46. * @private
  47. */
  48. _dispatchEvent(layersCount) {
  49. this.eventBus.dispatch("layersloaded", {
  50. source: this,
  51. layersCount,
  52. });
  53. }
  54. /**
  55. * @private
  56. */
  57. _bindLink(element, { groupId, input }) {
  58. const setVisibility = () => {
  59. this._optionalContentConfig.setVisibility(groupId, input.checked);
  60. this._optionalContentHash = this._optionalContentConfig.getHash();
  61. this.eventBus.dispatch("optionalcontentconfig", {
  62. source: this,
  63. promise: Promise.resolve(this._optionalContentConfig),
  64. });
  65. };
  66. element.onclick = evt => {
  67. if (evt.target === input) {
  68. setVisibility();
  69. return true;
  70. } else if (evt.target !== element) {
  71. return true; // The target is the "label", which is handled above.
  72. }
  73. input.checked = !input.checked;
  74. setVisibility();
  75. return false;
  76. };
  77. }
  78. /**
  79. * @private
  80. */
  81. async _setNestedName(element, { name = null }) {
  82. if (typeof name === "string") {
  83. element.textContent = this._normalizeTextContent(name);
  84. return;
  85. }
  86. element.textContent = await this.l10n.get("additional_layers");
  87. element.style.fontStyle = "italic";
  88. }
  89. /**
  90. * @private
  91. */
  92. _addToggleButton(div, { name = null }) {
  93. super._addToggleButton(div, /* hidden = */ name === null);
  94. }
  95. /**
  96. * @private
  97. */
  98. _toggleAllTreeItems() {
  99. if (!this._optionalContentConfig) {
  100. return;
  101. }
  102. super._toggleAllTreeItems();
  103. }
  104. /**
  105. * @param {PDFLayerViewerRenderParameters} params
  106. */
  107. render({ optionalContentConfig, pdfDocument }) {
  108. if (this._optionalContentConfig) {
  109. this.reset();
  110. }
  111. this._optionalContentConfig = optionalContentConfig || null;
  112. this._pdfDocument = pdfDocument || null;
  113. const groups = optionalContentConfig?.getOrder();
  114. if (!groups) {
  115. this._dispatchEvent(/* layersCount = */ 0);
  116. return;
  117. }
  118. this._optionalContentHash = optionalContentConfig.getHash();
  119. const fragment = document.createDocumentFragment(),
  120. queue = [{ parent: fragment, groups }];
  121. let layersCount = 0,
  122. hasAnyNesting = false;
  123. while (queue.length > 0) {
  124. const levelData = queue.shift();
  125. for (const groupId of levelData.groups) {
  126. const div = document.createElement("div");
  127. div.className = "treeItem";
  128. const element = document.createElement("a");
  129. div.append(element);
  130. if (typeof groupId === "object") {
  131. hasAnyNesting = true;
  132. this._addToggleButton(div, groupId);
  133. this._setNestedName(element, groupId);
  134. const itemsDiv = document.createElement("div");
  135. itemsDiv.className = "treeItems";
  136. div.append(itemsDiv);
  137. queue.push({ parent: itemsDiv, groups: groupId.order });
  138. } else {
  139. const group = optionalContentConfig.getGroup(groupId);
  140. const input = document.createElement("input");
  141. this._bindLink(element, { groupId, input });
  142. input.type = "checkbox";
  143. input.checked = group.visible;
  144. const label = document.createElement("label");
  145. label.textContent = this._normalizeTextContent(group.name);
  146. label.append(input);
  147. element.append(label);
  148. layersCount++;
  149. }
  150. levelData.parent.append(div);
  151. }
  152. }
  153. this._finishRendering(fragment, layersCount, hasAnyNesting);
  154. }
  155. async #updateLayers(promise = null) {
  156. if (!this._optionalContentConfig) {
  157. return;
  158. }
  159. const pdfDocument = this._pdfDocument;
  160. const optionalContentConfig = await (promise ||
  161. pdfDocument.getOptionalContentConfig());
  162. if (pdfDocument !== this._pdfDocument) {
  163. return; // The document was closed while the optional content resolved.
  164. }
  165. if (promise) {
  166. if (optionalContentConfig.getHash() === this._optionalContentHash) {
  167. return; // The optional content didn't change, hence no need to reset the UI.
  168. }
  169. } else {
  170. this.eventBus.dispatch("optionalcontentconfig", {
  171. source: this,
  172. promise: Promise.resolve(optionalContentConfig),
  173. });
  174. }
  175. // Reset the sidebarView to the new state.
  176. this.render({
  177. optionalContentConfig,
  178. pdfDocument: this._pdfDocument,
  179. });
  180. }
  181. }
  182. export { PDFLayerViewer };