2020-08-04 19:40:59 +09:00
|
|
|
/* Copyright 2020 Mozilla Foundation
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2022-01-03 22:11:12 +09:00
|
|
|
import { removeNullCharacters } from "./ui_utils.js";
|
2020-08-04 19:40:59 +09:00
|
|
|
|
Add support for finding/highlighting the outlineItem, corresponding to the currently visible page, in the sidebar (issue 7557, bug 1253820, bug 1499050)
This implementation is inspired by the behaviour in (recent versions of) Adobe Reader, since it leads to reasonably simple and straightforward code as far as I'm concerned.
*Specifically:* We'll only consider *one* destination per page when finding/highlighting the current outline item, which is similar to e.g. Adobe Reader, and we choose the *first* outline item at the *lowest* level of the outline tree.
Given that this functionality requires not only parsing of the `outline`, but looking up *all* of the destinations in the document, this feature can when initialized have a non-trivial performance overhead for larger PDF documents.
In an attempt to reduce the performance impact, the following steps are taken here:
- The "find current outline item"-functionality will only be enabled once *one* page has rendered and *all* the pages have been loaded[1], to prevent it interfering with data regular fetching/parsing early on during document loading and viewer initialization.
- With the exception of a couple of small and simple `eventBus`-listeners, in `PDFOutlineViewer`, this new functionality is initialized *lazily* the first time that the user clicks on the `currentOutlineItem`-button.
- The entire "find current outline item"-functionality is disabled when `disableAutoFetch = true` is set, since it can easily lead to the setting becoming essentially pointless[2] by triggering *a lot* of data fetching from a relatively minor viewer-feature.
- Fetch the destinations *individually*, since that's generally more efficient than using `PDFDocumentProxy.getDestinations` to fetch them all at once. Despite making the overall parsing code *more* asynchronous, and leading to a lot more main/worker-thread message passing, in practice this seems faster for larger documents.
Finally, we'll now always highlight an outline item that the user manually clicked on, since only highlighting when the new "find current outline item"-functionality is used seemed inconsistent.
---
[1] Keep in mind that the `outline` itself already isn't fetched/parsed until at least *one* page has been rendered in the viewer.
[2] And also quite slow, since it can take a fair amount of time to fetch all of the necessary `destinations` data when `disableAutoFetch = true` is set.
2020-12-25 20:57:43 +09:00
|
|
|
const TREEITEM_OFFSET_TOP = -100; // px
|
|
|
|
const TREEITEM_SELECTED_CLASS = "selected";
|
|
|
|
|
2020-08-04 19:40:59 +09:00
|
|
|
class BaseTreeViewer {
|
|
|
|
constructor(options) {
|
|
|
|
if (this.constructor === BaseTreeViewer) {
|
|
|
|
throw new Error("Cannot initialize BaseTreeViewer.");
|
|
|
|
}
|
|
|
|
this.container = options.container;
|
|
|
|
this.eventBus = options.eventBus;
|
|
|
|
|
|
|
|
this.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
reset() {
|
Add support for finding/highlighting the outlineItem, corresponding to the currently visible page, in the sidebar (issue 7557, bug 1253820, bug 1499050)
This implementation is inspired by the behaviour in (recent versions of) Adobe Reader, since it leads to reasonably simple and straightforward code as far as I'm concerned.
*Specifically:* We'll only consider *one* destination per page when finding/highlighting the current outline item, which is similar to e.g. Adobe Reader, and we choose the *first* outline item at the *lowest* level of the outline tree.
Given that this functionality requires not only parsing of the `outline`, but looking up *all* of the destinations in the document, this feature can when initialized have a non-trivial performance overhead for larger PDF documents.
In an attempt to reduce the performance impact, the following steps are taken here:
- The "find current outline item"-functionality will only be enabled once *one* page has rendered and *all* the pages have been loaded[1], to prevent it interfering with data regular fetching/parsing early on during document loading and viewer initialization.
- With the exception of a couple of small and simple `eventBus`-listeners, in `PDFOutlineViewer`, this new functionality is initialized *lazily* the first time that the user clicks on the `currentOutlineItem`-button.
- The entire "find current outline item"-functionality is disabled when `disableAutoFetch = true` is set, since it can easily lead to the setting becoming essentially pointless[2] by triggering *a lot* of data fetching from a relatively minor viewer-feature.
- Fetch the destinations *individually*, since that's generally more efficient than using `PDFDocumentProxy.getDestinations` to fetch them all at once. Despite making the overall parsing code *more* asynchronous, and leading to a lot more main/worker-thread message passing, in practice this seems faster for larger documents.
Finally, we'll now always highlight an outline item that the user manually clicked on, since only highlighting when the new "find current outline item"-functionality is used seemed inconsistent.
---
[1] Keep in mind that the `outline` itself already isn't fetched/parsed until at least *one* page has been rendered in the viewer.
[2] And also quite slow, since it can take a fair amount of time to fetch all of the necessary `destinations` data when `disableAutoFetch = true` is set.
2020-12-25 20:57:43 +09:00
|
|
|
this._pdfDocument = null;
|
2020-08-04 19:40:59 +09:00
|
|
|
this._lastToggleIsShow = true;
|
Add support for finding/highlighting the outlineItem, corresponding to the currently visible page, in the sidebar (issue 7557, bug 1253820, bug 1499050)
This implementation is inspired by the behaviour in (recent versions of) Adobe Reader, since it leads to reasonably simple and straightforward code as far as I'm concerned.
*Specifically:* We'll only consider *one* destination per page when finding/highlighting the current outline item, which is similar to e.g. Adobe Reader, and we choose the *first* outline item at the *lowest* level of the outline tree.
Given that this functionality requires not only parsing of the `outline`, but looking up *all* of the destinations in the document, this feature can when initialized have a non-trivial performance overhead for larger PDF documents.
In an attempt to reduce the performance impact, the following steps are taken here:
- The "find current outline item"-functionality will only be enabled once *one* page has rendered and *all* the pages have been loaded[1], to prevent it interfering with data regular fetching/parsing early on during document loading and viewer initialization.
- With the exception of a couple of small and simple `eventBus`-listeners, in `PDFOutlineViewer`, this new functionality is initialized *lazily* the first time that the user clicks on the `currentOutlineItem`-button.
- The entire "find current outline item"-functionality is disabled when `disableAutoFetch = true` is set, since it can easily lead to the setting becoming essentially pointless[2] by triggering *a lot* of data fetching from a relatively minor viewer-feature.
- Fetch the destinations *individually*, since that's generally more efficient than using `PDFDocumentProxy.getDestinations` to fetch them all at once. Despite making the overall parsing code *more* asynchronous, and leading to a lot more main/worker-thread message passing, in practice this seems faster for larger documents.
Finally, we'll now always highlight an outline item that the user manually clicked on, since only highlighting when the new "find current outline item"-functionality is used seemed inconsistent.
---
[1] Keep in mind that the `outline` itself already isn't fetched/parsed until at least *one* page has been rendered in the viewer.
[2] And also quite slow, since it can take a fair amount of time to fetch all of the necessary `destinations` data when `disableAutoFetch = true` is set.
2020-12-25 20:57:43 +09:00
|
|
|
this._currentTreeItem = null;
|
2020-08-04 19:40:59 +09:00
|
|
|
|
|
|
|
// Remove the tree from the DOM.
|
|
|
|
this.container.textContent = "";
|
|
|
|
// Ensure that the left (right in RTL locales) margin is always reset,
|
|
|
|
// to prevent incorrect tree alignment if a new document is opened.
|
|
|
|
this.container.classList.remove("treeWithDeepNesting");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_dispatchEvent(count) {
|
|
|
|
throw new Error("Not implemented: _dispatchEvent");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_bindLink(element, params) {
|
|
|
|
throw new Error("Not implemented: _bindLink");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_normalizeTextContent(str) {
|
2021-11-13 03:41:32 +09:00
|
|
|
// Chars in range [0x01-0x1F] will be replaced with a white space
|
|
|
|
// and 0x00 by "".
|
|
|
|
return (
|
|
|
|
removeNullCharacters(str, /* replaceInvisible */ true) ||
|
|
|
|
/* en dash = */ "\u2013"
|
|
|
|
);
|
2020-08-04 19:40:59 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepend a button before a tree item which allows the user to collapse or
|
|
|
|
* expand all tree items at that level; see `_toggleTreeItem`.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_addToggleButton(div, hidden = false) {
|
|
|
|
const toggler = document.createElement("div");
|
|
|
|
toggler.className = "treeItemToggler";
|
|
|
|
if (hidden) {
|
|
|
|
toggler.classList.add("treeItemsHidden");
|
|
|
|
}
|
|
|
|
toggler.onclick = evt => {
|
|
|
|
evt.stopPropagation();
|
|
|
|
toggler.classList.toggle("treeItemsHidden");
|
|
|
|
|
|
|
|
if (evt.shiftKey) {
|
|
|
|
const shouldShowAll = !toggler.classList.contains("treeItemsHidden");
|
|
|
|
this._toggleTreeItem(div, shouldShowAll);
|
|
|
|
}
|
|
|
|
};
|
2022-06-13 06:15:56 +09:00
|
|
|
div.prepend(toggler);
|
2020-08-04 19:40:59 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Collapse or expand the subtree of a tree item.
|
|
|
|
*
|
|
|
|
* @param {Element} root - the root of the item (sub)tree.
|
|
|
|
* @param {boolean} show - whether to show the item (sub)tree. If false,
|
|
|
|
* the item subtree rooted at `root` will be collapsed.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_toggleTreeItem(root, show = false) {
|
|
|
|
this._lastToggleIsShow = show;
|
|
|
|
for (const toggler of root.querySelectorAll(".treeItemToggler")) {
|
|
|
|
toggler.classList.toggle("treeItemsHidden", !show);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Collapse or expand all subtrees of the `container`.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_toggleAllTreeItems() {
|
|
|
|
this._toggleTreeItem(this.container, !this._lastToggleIsShow);
|
|
|
|
}
|
|
|
|
|
2020-12-28 22:42:47 +09:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_finishRendering(fragment, count, hasAnyNesting = false) {
|
|
|
|
if (hasAnyNesting) {
|
|
|
|
this.container.classList.add("treeWithDeepNesting");
|
|
|
|
|
|
|
|
this._lastToggleIsShow = !fragment.querySelector(".treeItemsHidden");
|
|
|
|
}
|
2022-06-12 19:20:25 +09:00
|
|
|
this.container.append(fragment);
|
2020-12-28 22:42:47 +09:00
|
|
|
|
|
|
|
this._dispatchEvent(count);
|
|
|
|
}
|
|
|
|
|
2020-08-04 19:40:59 +09:00
|
|
|
render(params) {
|
|
|
|
throw new Error("Not implemented: render");
|
|
|
|
}
|
Add support for finding/highlighting the outlineItem, corresponding to the currently visible page, in the sidebar (issue 7557, bug 1253820, bug 1499050)
This implementation is inspired by the behaviour in (recent versions of) Adobe Reader, since it leads to reasonably simple and straightforward code as far as I'm concerned.
*Specifically:* We'll only consider *one* destination per page when finding/highlighting the current outline item, which is similar to e.g. Adobe Reader, and we choose the *first* outline item at the *lowest* level of the outline tree.
Given that this functionality requires not only parsing of the `outline`, but looking up *all* of the destinations in the document, this feature can when initialized have a non-trivial performance overhead for larger PDF documents.
In an attempt to reduce the performance impact, the following steps are taken here:
- The "find current outline item"-functionality will only be enabled once *one* page has rendered and *all* the pages have been loaded[1], to prevent it interfering with data regular fetching/parsing early on during document loading and viewer initialization.
- With the exception of a couple of small and simple `eventBus`-listeners, in `PDFOutlineViewer`, this new functionality is initialized *lazily* the first time that the user clicks on the `currentOutlineItem`-button.
- The entire "find current outline item"-functionality is disabled when `disableAutoFetch = true` is set, since it can easily lead to the setting becoming essentially pointless[2] by triggering *a lot* of data fetching from a relatively minor viewer-feature.
- Fetch the destinations *individually*, since that's generally more efficient than using `PDFDocumentProxy.getDestinations` to fetch them all at once. Despite making the overall parsing code *more* asynchronous, and leading to a lot more main/worker-thread message passing, in practice this seems faster for larger documents.
Finally, we'll now always highlight an outline item that the user manually clicked on, since only highlighting when the new "find current outline item"-functionality is used seemed inconsistent.
---
[1] Keep in mind that the `outline` itself already isn't fetched/parsed until at least *one* page has been rendered in the viewer.
[2] And also quite slow, since it can take a fair amount of time to fetch all of the necessary `destinations` data when `disableAutoFetch = true` is set.
2020-12-25 20:57:43 +09:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_updateCurrentTreeItem(treeItem = null) {
|
|
|
|
if (this._currentTreeItem) {
|
|
|
|
// Ensure that the current treeItem-selection is always removed.
|
|
|
|
this._currentTreeItem.classList.remove(TREEITEM_SELECTED_CLASS);
|
|
|
|
this._currentTreeItem = null;
|
|
|
|
}
|
|
|
|
if (treeItem) {
|
|
|
|
treeItem.classList.add(TREEITEM_SELECTED_CLASS);
|
|
|
|
this._currentTreeItem = treeItem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_scrollToCurrentTreeItem(treeItem) {
|
|
|
|
if (!treeItem) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Ensure that the treeItem is *fully* expanded, such that it will first of
|
|
|
|
// all be visible and secondly that scrolling it into view works correctly.
|
|
|
|
let currentNode = treeItem.parentNode;
|
|
|
|
while (currentNode && currentNode !== this.container) {
|
|
|
|
if (currentNode.classList.contains("treeItem")) {
|
|
|
|
const toggler = currentNode.firstElementChild;
|
|
|
|
toggler?.classList.remove("treeItemsHidden");
|
|
|
|
}
|
|
|
|
currentNode = currentNode.parentNode;
|
|
|
|
}
|
|
|
|
this._updateCurrentTreeItem(treeItem);
|
|
|
|
|
|
|
|
this.container.scrollTo(
|
|
|
|
treeItem.offsetLeft,
|
|
|
|
treeItem.offsetTop + TREEITEM_OFFSET_TOP
|
|
|
|
);
|
|
|
|
}
|
2020-08-04 19:40:59 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
export { BaseTreeViewer };
|