Skip to content

Commit

Permalink
Merge pull request #12777 from Snuffleupagus/currentOutlineItem
Browse files Browse the repository at this point in the history
Add support for finding/highlighting the outlineItem, corresponding to the currently visible page, in the sidebar (issue 7557, bug 1253820, bug 1499050)
  • Loading branch information
timvandermeij authored Jan 9, 2021
2 parents c0a6d6c + 492a817 commit 7f199e7
Show file tree
Hide file tree
Showing 13 changed files with 302 additions and 41 deletions.
2 changes: 2 additions & 0 deletions l10n/en-US/viewer.properties
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ layers.title=Show Layers (double-click to reset all layers to the default state)
layers_label=Layers
thumbs.title=Show Thumbnails
thumbs_label=Thumbnails
current_outline_item.title=Find Current Outline Item
current_outline_item_label=Current Outline Item
findbar.title=Find in Document
findbar_label=Find

Expand Down
2 changes: 2 additions & 0 deletions l10n/nl/viewer.properties
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ layers.title=Lagen tonen (dubbelklik om alle lagen naar de standaardstatus terug
layers_label=Lagen
thumbs.title=Miniaturen tonen
thumbs_label=Miniaturen
current_outline_item.title=Huidige positie in documentoverzicht selecteren
current_outline_item_label=Huidige positie in documentoverzicht
findbar.title=Zoeken in document
findbar_label=Zoeken

Expand Down
2 changes: 2 additions & 0 deletions l10n/sv-SE/viewer.properties
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ layers.title=Visa lager (dubbelklicka för att återställa alla lager till stan
layers_label=Lager
thumbs.title=Visa miniatyrer
thumbs_label=Miniatyrer
current_outline_item.title=Hitta aktuell position i dokumentdispositionen
current_outline_item_label=Aktuell position i dokumentdisposition
findbar.title=Sök i dokument
findbar_label=Sök

Expand Down
5 changes: 3 additions & 2 deletions web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
ProgressBar,
RendererType,
ScrollMode,
SidebarView,
SpreadMode,
TextLayerMode,
} from "./ui_utils.js";
Expand All @@ -56,7 +57,6 @@ import {
} from "pdfjs-lib";
import { CursorTool, PDFCursorTools } from "./pdf_cursor_tools.js";
import { PDFRenderingQueue, RenderingStates } from "./pdf_rendering_queue.js";
import { PDFSidebar, SidebarView } from "./pdf_sidebar.js";
import { OverlayManager } from "./overlay_manager.js";
import { PasswordPrompt } from "./password_prompt.js";
import { PDFAttachmentViewer } from "./pdf_attachment_viewer.js";
Expand All @@ -68,6 +68,7 @@ import { PDFLayerViewer } from "./pdf_layer_viewer.js";
import { PDFLinkService } from "./pdf_link_service.js";
import { PDFOutlineViewer } from "./pdf_outline_viewer.js";
import { PDFPresentationMode } from "./pdf_presentation_mode.js";
import { PDFSidebar } from "./pdf_sidebar.js";
import { PDFSidebarResizer } from "./pdf_sidebar_resizer.js";
import { PDFThumbnailViewer } from "./pdf_thumbnail_viewer.js";
import { PDFViewer } from "./pdf_viewer.js";
Expand Down Expand Up @@ -1419,7 +1420,7 @@ const PDFViewerApplication = {

onePageRendered.then(() => {
pdfDocument.getOutline().then(outline => {
this.pdfOutlineViewer.render({ outline });
this.pdfOutlineViewer.render({ outline, pdfDocument });
});
pdfDocument.getAttachments().then(attachments => {
this.pdfAttachmentViewer.render({ attachments });
Expand Down
45 changes: 45 additions & 0 deletions web/base_tree_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

import { removeNullCharacters } from "pdfjs-lib";

const TREEITEM_OFFSET_TOP = -100; // px
const TREEITEM_SELECTED_CLASS = "selected";

class BaseTreeViewer {
constructor(options) {
if (this.constructor === BaseTreeViewer) {
Expand All @@ -27,7 +30,9 @@ class BaseTreeViewer {
}

reset() {
this._pdfDocument = null;
this._lastToggleIsShow = true;
this._currentTreeItem = null;

// Remove the tree from the DOM.
this.container.textContent = "";
Expand Down Expand Up @@ -120,6 +125,46 @@ class BaseTreeViewer {
render(params) {
throw new Error("Not implemented: render");
}

/**
* @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
);
}
}

export { BaseTreeViewer };
1 change: 1 addition & 0 deletions web/images/toolbarButton-currentOutlineItem.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion web/pdf_layer_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ class PDFLayerViewer extends BaseTreeViewer {
reset() {
super.reset();
this._optionalContentConfig = null;
this._pdfDocument = null;
}

/**
Expand Down
149 changes: 146 additions & 3 deletions web/pdf_outline_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@
* limitations under the License.
*/

import { addLinkAttributes, LinkTarget } from "pdfjs-lib";
import {
addLinkAttributes,
createPromiseCapability,
LinkTarget,
} from "pdfjs-lib";
import { BaseTreeViewer } from "./base_tree_viewer.js";
import { SidebarView } from "./ui_utils.js";

/**
* @typedef {Object} PDFOutlineViewerOptions
Expand All @@ -26,6 +31,7 @@ import { BaseTreeViewer } from "./base_tree_viewer.js";
/**
* @typedef {Object} PDFOutlineViewerRenderParameters
* @property {Array|null} outline - An array of outline objects.
* @property {PDFDocument} pdfDocument - A {PDFDocument} instance.
*/

class PDFOutlineViewer extends BaseTreeViewer {
Expand All @@ -37,11 +43,29 @@ class PDFOutlineViewer extends BaseTreeViewer {
this.linkService = options.linkService;

this.eventBus._on("toggleoutlinetree", this._toggleAllTreeItems.bind(this));
this.eventBus._on(
"currentoutlineitem",
this._currentOutlineItem.bind(this)
);

this.eventBus._on("pagechanging", evt => {
this._currentPageNumber = evt.pageNumber;
});
this.eventBus._on("pagesloaded", evt => {
this._isPagesLoaded = !!evt.pagesCount;
});
this.eventBus._on("sidebarviewchanged", evt => {
this._sidebarView = evt.view;
});
}

reset() {
super.reset();
this._outline = null;

this._pageNumberToDestHashCapability = null;
this._currentPageNumber = 1;
this._isPagesLoaded = false;
}

/**
Expand All @@ -51,6 +75,8 @@ class PDFOutlineViewer extends BaseTreeViewer {
this.eventBus.dispatch("outlineloaded", {
source: this,
outlineCount,
enableCurrentOutlineItemButton:
outlineCount > 0 && !this._pdfDocument?.loadingParams.disableAutoFetch,
});
}

Expand All @@ -71,7 +97,9 @@ class PDFOutlineViewer extends BaseTreeViewer {
}

element.href = linkService.getDestinationHash(dest);
element.onclick = () => {
element.onclick = evt => {
this._updateCurrentTreeItem(evt.target.parentNode);

if (dest) {
linkService.goToDestination(dest);
}
Expand Down Expand Up @@ -128,11 +156,12 @@ class PDFOutlineViewer extends BaseTreeViewer {
/**
* @param {PDFOutlineViewerRenderParameters} params
*/
render({ outline }) {
render({ outline, pdfDocument }) {
if (this._outline) {
this.reset();
}
this._outline = outline || null;
this._pdfDocument = pdfDocument || null;

if (!outline) {
this._dispatchEvent(/* outlineCount = */ 0);
Expand Down Expand Up @@ -174,6 +203,120 @@ class PDFOutlineViewer extends BaseTreeViewer {

this._finishRendering(fragment, outlineCount, hasAnyNesting);
}

/**
* Find/highlight the current outline item, corresponding to the active page.
* @private
*/
async _currentOutlineItem() {
if (!this._isPagesLoaded) {
throw new Error("_currentOutlineItem: All pages have not been loaded.");
}
if (!this._outline || !this._pdfDocument) {
return;
}

const pageNumberToDestHash = await this._getPageNumberToDestHash(
this._pdfDocument
);
if (!pageNumberToDestHash) {
return;
}
this._updateCurrentTreeItem(/* treeItem = */ null);

if (this._sidebarView !== SidebarView.OUTLINE) {
return; // The outline view is no longer visible, hence do nothing.
}
// When there is no destination on the current page, always check the
// previous ones in (reverse) order.
for (let i = this._currentPageNumber; i > 0; i--) {
const destHash = pageNumberToDestHash.get(i);
if (!destHash) {
continue;
}
const linkElement = this.container.querySelector(`a[href="${destHash}"]`);
if (!linkElement) {
continue;
}
this._scrollToCurrentTreeItem(linkElement.parentNode);
break;
}
}

/**
* To (significantly) simplify the overall implementation, we will only
* consider *one* destination per page when finding/highlighting the current
* outline item (similar to e.g. Adobe Reader); more specifically, we choose
* the *first* outline item at the *lowest* level of the outline tree.
* @private
*/
async _getPageNumberToDestHash(pdfDocument) {
if (this._pageNumberToDestHashCapability) {
return this._pageNumberToDestHashCapability.promise;
}
this._pageNumberToDestHashCapability = createPromiseCapability();

const pageNumberToDestHash = new Map(),
pageNumberNesting = new Map();
const queue = [{ nesting: 0, items: this._outline }];
while (queue.length > 0) {
const levelData = queue.shift(),
currentNesting = levelData.nesting;
for (const { dest, items } of levelData.items) {
let explicitDest, pageNumber;
if (typeof dest === "string") {
explicitDest = await pdfDocument.getDestination(dest);

if (pdfDocument !== this._pdfDocument) {
return null; // The document was closed while the data resolved.
}
} else {
explicitDest = dest;
}
if (Array.isArray(explicitDest)) {
const [destRef] = explicitDest;

if (typeof destRef === "object") {
pageNumber = this.linkService._cachedPageNumber(destRef);

if (!pageNumber) {
try {
pageNumber = (await pdfDocument.getPageIndex(destRef)) + 1;

if (pdfDocument !== this._pdfDocument) {
return null; // The document was closed while the data resolved.
}
this.linkService.cachePageRef(pageNumber, destRef);
} catch (ex) {
// Invalid page reference, ignore it and continue parsing.
}
}
} else if (Number.isInteger(destRef)) {
pageNumber = destRef + 1;
}

if (
Number.isInteger(pageNumber) &&
(!pageNumberToDestHash.has(pageNumber) ||
currentNesting > pageNumberNesting.get(pageNumber))
) {
const destHash = this.linkService.getDestinationHash(dest);
pageNumberToDestHash.set(pageNumber, destHash);
pageNumberNesting.set(pageNumber, currentNesting);
}
}

if (items.length > 0) {
queue.push({ nesting: currentNesting + 1, items });
}
}
}

this._pageNumberToDestHashCapability.resolve(
pageNumberToDestHash.size > 0 ? pageNumberToDestHash : null
);
return this._pageNumberToDestHashCapability.promise;
}
}

export { PDFOutlineViewer };
Loading

0 comments on commit 7f199e7

Please sign in to comment.