Merge pull request #12170 from Snuffleupagus/optional-content-viewer-2
[api-minor] Add support for toggling of Optional Content in the viewer (issue 12096)
This commit is contained in:
commit
aa27e7fb8d
@ -137,17 +137,20 @@ print_progress_close=Cancel
|
||||
# (the _label strings are alt text for the buttons, the .title strings are
|
||||
# tooltips)
|
||||
toggle_sidebar.title=Toggle Sidebar
|
||||
toggle_sidebar_notification.title=Toggle Sidebar (document contains outline/attachments)
|
||||
toggle_sidebar_notification2.title=Toggle Sidebar (document contains outline/attachments/layers)
|
||||
toggle_sidebar_label=Toggle Sidebar
|
||||
document_outline.title=Show Document Outline (double-click to expand/collapse all items)
|
||||
document_outline_label=Document Outline
|
||||
attachments.title=Show Attachments
|
||||
attachments_label=Attachments
|
||||
layers.title=Show Layers (double-click to reset all layers to the default state)
|
||||
layers_label=Layers
|
||||
thumbs.title=Show Thumbnails
|
||||
thumbs_label=Thumbnails
|
||||
findbar.title=Find in Document
|
||||
findbar_label=Find
|
||||
|
||||
additional_layers=Additional Layers
|
||||
# LOCALIZATION NOTE (page_canvas): "{{page}}" will be replaced by the page number.
|
||||
page_canvas=Page {{page}}
|
||||
# Thumbnails panel item (tooltip and alt text for images)
|
||||
|
@ -137,17 +137,20 @@ print_progress_close=Avbryt
|
||||
# (the _label strings are alt text for the buttons, the .title strings are
|
||||
# tooltips)
|
||||
toggle_sidebar.title=Visa/dölj sidofält
|
||||
toggle_sidebar_notification.title=Visa/dölj sidofält (dokument innehåller översikt/bilagor)
|
||||
toggle_sidebar_notification2.title=Visa/dölj sidofält (dokument innehåller översikt/bilagor/lager)
|
||||
toggle_sidebar_label=Visa/dölj sidofält
|
||||
document_outline.title=Visa dokumentdisposition (dubbelklicka för att expandera/komprimera alla objekt)
|
||||
document_outline_label=Dokumentöversikt
|
||||
attachments.title=Visa Bilagor
|
||||
attachments_label=Bilagor
|
||||
layers.title=Visa lager (dubbelklicka för att återställa alla lager till ursrungligt läge)
|
||||
layers_label=Lager
|
||||
thumbs.title=Visa miniatyrer
|
||||
thumbs_label=Miniatyrer
|
||||
findbar.title=Sök i dokument
|
||||
findbar_label=Sök
|
||||
|
||||
additional_layers=Ytterligare lager
|
||||
# LOCALIZATION NOTE (page_canvas): "{{page}}" will be replaced by the page number.
|
||||
page_canvas=Sida {{page}}
|
||||
# Thumbnails panel item (tooltip and alt text for images)
|
||||
|
@ -355,6 +355,67 @@ class Catalog {
|
||||
return onParsed;
|
||||
}
|
||||
|
||||
function parseOrder(refs, nestedLevels = 0) {
|
||||
if (!Array.isArray(refs)) {
|
||||
return null;
|
||||
}
|
||||
const order = [];
|
||||
|
||||
for (const value of refs) {
|
||||
if (isRef(value) && contentGroupRefs.includes(value)) {
|
||||
parsedOrderRefs.put(value); // Handle "hidden" groups, see below.
|
||||
|
||||
order.push(value.toString());
|
||||
continue;
|
||||
}
|
||||
// Handle nested /Order arrays (see e.g. issue 9462 and bug 1240641).
|
||||
const nestedOrder = parseNestedOrder(value, nestedLevels);
|
||||
if (nestedOrder) {
|
||||
order.push(nestedOrder);
|
||||
}
|
||||
}
|
||||
|
||||
if (nestedLevels > 0) {
|
||||
return order;
|
||||
}
|
||||
const hiddenGroups = [];
|
||||
for (const groupRef of contentGroupRefs) {
|
||||
if (parsedOrderRefs.has(groupRef)) {
|
||||
continue;
|
||||
}
|
||||
hiddenGroups.push(groupRef.toString());
|
||||
}
|
||||
if (hiddenGroups.length) {
|
||||
order.push({ name: null, order: hiddenGroups });
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
function parseNestedOrder(ref, nestedLevels) {
|
||||
if (++nestedLevels > MAX_NESTED_LEVELS) {
|
||||
warn("parseNestedOrder - reached MAX_NESTED_LEVELS.");
|
||||
return null;
|
||||
}
|
||||
const value = xref.fetchIfRef(ref);
|
||||
if (!Array.isArray(value)) {
|
||||
return null;
|
||||
}
|
||||
const nestedName = xref.fetchIfRef(value[0]);
|
||||
if (typeof nestedName !== "string") {
|
||||
return null;
|
||||
}
|
||||
const nestedOrder = parseOrder(value.slice(1), nestedLevels);
|
||||
if (!nestedOrder || !nestedOrder.length) {
|
||||
return null;
|
||||
}
|
||||
return { name: stringToPDFString(nestedName), order: nestedOrder };
|
||||
}
|
||||
|
||||
const xref = this.xref,
|
||||
parsedOrderRefs = new RefSet(),
|
||||
MAX_NESTED_LEVELS = 10;
|
||||
|
||||
return {
|
||||
name: isString(config.get("Name"))
|
||||
? stringToPDFString(config.get("Name"))
|
||||
@ -367,6 +428,8 @@ class Catalog {
|
||||
: null,
|
||||
on: parseOnOff(config.get("ON")),
|
||||
off: parseOnOff(config.get("OFF")),
|
||||
order: parseOrder(config.get("Order")),
|
||||
groups: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -778,9 +778,9 @@ class PDFDocumentProxy {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<OptionalContentConfig | null>} A promise that is resolved
|
||||
* with an {@link OptionalContentConfig} that has all the optional content
|
||||
* groups, or `null` if the document does not have any.
|
||||
* @returns {Promise<OptionalContentConfig>} A promise that is resolved with
|
||||
* an {@link OptionalContentConfig} that contains all the optional content
|
||||
* groups (assuming that the document has any).
|
||||
*/
|
||||
getOptionalContentConfig() {
|
||||
return this._transport.getOptionalContentConfig();
|
||||
|
@ -26,42 +26,44 @@ class OptionalContentConfig {
|
||||
constructor(data) {
|
||||
this.name = null;
|
||||
this.creator = null;
|
||||
this.groups = new Map();
|
||||
this._order = null;
|
||||
this._groups = new Map();
|
||||
|
||||
if (data === null) {
|
||||
return;
|
||||
}
|
||||
this.name = data.name;
|
||||
this.creator = data.creator;
|
||||
this._order = data.order;
|
||||
for (const group of data.groups) {
|
||||
this.groups.set(
|
||||
this._groups.set(
|
||||
group.id,
|
||||
new OptionalContentGroup(group.name, group.intent)
|
||||
);
|
||||
}
|
||||
|
||||
if (data.baseState === "OFF") {
|
||||
for (const group of this.groups) {
|
||||
for (const group of this._groups) {
|
||||
group.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const on of data.on) {
|
||||
this.groups.get(on).visible = true;
|
||||
this._groups.get(on).visible = true;
|
||||
}
|
||||
|
||||
for (const off of data.off) {
|
||||
this.groups.get(off).visible = false;
|
||||
this._groups.get(off).visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
isVisible(group) {
|
||||
if (group.type === "OCG") {
|
||||
if (!this.groups.has(group.id)) {
|
||||
if (!this._groups.has(group.id)) {
|
||||
warn(`Optional content group not found: ${group.id}`);
|
||||
return true;
|
||||
}
|
||||
return this.groups.get(group.id).visible;
|
||||
return this._groups.get(group.id).visible;
|
||||
} else if (group.type === "OCMD") {
|
||||
// Per the spec, the expression should be preferred if available. Until
|
||||
// we implement this, just fallback to using the group policy for now.
|
||||
@ -71,44 +73,44 @@ class OptionalContentConfig {
|
||||
if (!group.policy || group.policy === "AnyOn") {
|
||||
// Default
|
||||
for (const id of group.ids) {
|
||||
if (!this.groups.has(id)) {
|
||||
if (!this._groups.has(id)) {
|
||||
warn(`Optional content group not found: ${id}`);
|
||||
return true;
|
||||
}
|
||||
if (this.groups.get(id).visible) {
|
||||
if (this._groups.get(id).visible) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (group.policy === "AllOn") {
|
||||
for (const id of group.ids) {
|
||||
if (!this.groups.has(id)) {
|
||||
if (!this._groups.has(id)) {
|
||||
warn(`Optional content group not found: ${id}`);
|
||||
return true;
|
||||
}
|
||||
if (!this.groups.get(id).visible) {
|
||||
if (!this._groups.get(id).visible) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (group.policy === "AnyOff") {
|
||||
for (const id of group.ids) {
|
||||
if (!this.groups.has(id)) {
|
||||
if (!this._groups.has(id)) {
|
||||
warn(`Optional content group not found: ${id}`);
|
||||
return true;
|
||||
}
|
||||
if (!this.groups.get(id).visible) {
|
||||
if (!this._groups.get(id).visible) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (group.policy === "AllOff") {
|
||||
for (const id of group.ids) {
|
||||
if (!this.groups.has(id)) {
|
||||
if (!this._groups.has(id)) {
|
||||
warn(`Optional content group not found: ${id}`);
|
||||
return true;
|
||||
}
|
||||
if (this.groups.get(id).visible) {
|
||||
if (this._groups.get(id).visible) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -120,6 +122,35 @@ class OptionalContentConfig {
|
||||
warn(`Unknown group type ${group.type}.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
setVisibility(id, visible = true) {
|
||||
if (!this._groups.has(id)) {
|
||||
warn(`Optional content group not found: ${id}`);
|
||||
return;
|
||||
}
|
||||
this._groups.get(id).visible = !!visible;
|
||||
}
|
||||
|
||||
getOrder() {
|
||||
if (!this._groups.size) {
|
||||
return null;
|
||||
}
|
||||
if (this._order) {
|
||||
return this._order.slice();
|
||||
}
|
||||
return Array.from(this._groups.keys());
|
||||
}
|
||||
|
||||
getGroups() {
|
||||
if (!this._groups.size) {
|
||||
return null;
|
||||
}
|
||||
return Object.fromEntries(this._groups);
|
||||
}
|
||||
|
||||
getGroup(id) {
|
||||
return this._groups.get(id) || null;
|
||||
}
|
||||
}
|
||||
|
||||
export { OptionalContentConfig };
|
||||
|
@ -397,6 +397,8 @@ var Driver = (function DriverClosure() {
|
||||
loadingTask.promise.then(
|
||||
doc => {
|
||||
task.pdfDoc = doc;
|
||||
task.optionalContentConfigPromise = doc.getOptionalContentConfig();
|
||||
|
||||
this._nextPage(task, failure);
|
||||
},
|
||||
err => {
|
||||
@ -605,6 +607,7 @@ var Driver = (function DriverClosure() {
|
||||
canvasContext: ctx,
|
||||
viewport,
|
||||
renderInteractiveForms: renderForms,
|
||||
optionalContentConfigPromise: task.optionalContentConfigPromise,
|
||||
};
|
||||
if (renderPrint) {
|
||||
const annotationStorage = task.annotationStorage;
|
||||
|
33
web/app.js
33
web/app.js
@ -64,6 +64,7 @@ import { PDFDocumentProperties } from "./pdf_document_properties.js";
|
||||
import { PDFFindBar } from "./pdf_find_bar.js";
|
||||
import { PDFFindController } from "./pdf_find_controller.js";
|
||||
import { PDFHistory } from "./pdf_history.js";
|
||||
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";
|
||||
@ -209,6 +210,8 @@ const PDFViewerApplication = {
|
||||
pdfOutlineViewer: null,
|
||||
/** @type {PDFAttachmentViewer} */
|
||||
pdfAttachmentViewer: null,
|
||||
/** @type {PDFLayerViewer} */
|
||||
pdfLayerViewer: null,
|
||||
/** @type {PDFCursorTools} */
|
||||
pdfCursorTools: null,
|
||||
/** @type {ViewHistory} */
|
||||
@ -445,6 +448,7 @@ const PDFViewerApplication = {
|
||||
|
||||
this.pdfThumbnailViewer = new PDFThumbnailViewer({
|
||||
container: appConfig.sidebar.thumbnailView,
|
||||
eventBus,
|
||||
renderingQueue: pdfRenderingQueue,
|
||||
linkService: pdfLinkService,
|
||||
l10n: this.l10n,
|
||||
@ -509,6 +513,12 @@ const PDFViewerApplication = {
|
||||
downloadManager,
|
||||
});
|
||||
|
||||
this.pdfLayerViewer = new PDFLayerViewer({
|
||||
container: appConfig.sidebar.layersView,
|
||||
eventBus,
|
||||
l10n: this.l10n,
|
||||
});
|
||||
|
||||
this.pdfSidebar = new PDFSidebar({
|
||||
elements: appConfig.sidebar,
|
||||
pdfViewer: this.pdfViewer,
|
||||
@ -737,6 +747,7 @@ const PDFViewerApplication = {
|
||||
this.pdfSidebar.reset();
|
||||
this.pdfOutlineViewer.reset();
|
||||
this.pdfAttachmentViewer.reset();
|
||||
this.pdfLayerViewer.reset();
|
||||
|
||||
if (this.pdfHistory) {
|
||||
this.pdfHistory.reset();
|
||||
@ -1318,6 +1329,11 @@ const PDFViewerApplication = {
|
||||
pdfDocument.getAttachments().then(attachments => {
|
||||
this.pdfAttachmentViewer.render({ attachments });
|
||||
});
|
||||
// Ensure that the layers accurately reflects the current state in the
|
||||
// viewer itself, rather than the default state provided by the API.
|
||||
pdfViewer.optionalContentConfigPromise.then(optionalContentConfig => {
|
||||
this.pdfLayerViewer.render({ optionalContentConfig, pdfDocument });
|
||||
});
|
||||
});
|
||||
|
||||
this._initializePageLabels(pdfDocument);
|
||||
@ -1667,12 +1683,15 @@ const PDFViewerApplication = {
|
||||
const pagesOverview = this.pdfViewer.getPagesOverview();
|
||||
const printContainer = this.appConfig.printContainer;
|
||||
const printResolution = AppOptions.get("printResolution");
|
||||
const optionalContentConfigPromise = this.pdfViewer
|
||||
.optionalContentConfigPromise;
|
||||
|
||||
const printService = PDFPrintServiceFactory.instance.createPrintService(
|
||||
this.pdfDocument,
|
||||
pagesOverview,
|
||||
printContainer,
|
||||
printResolution,
|
||||
optionalContentConfigPromise,
|
||||
this.l10n
|
||||
);
|
||||
this.printService = printService;
|
||||
@ -1748,6 +1767,7 @@ const PDFViewerApplication = {
|
||||
eventBus._on("scalechanged", webViewerScaleChanged);
|
||||
eventBus._on("rotatecw", webViewerRotateCw);
|
||||
eventBus._on("rotateccw", webViewerRotateCcw);
|
||||
eventBus._on("optionalcontentconfig", webViewerOptionalContentConfig);
|
||||
eventBus._on("switchscrollmode", webViewerSwitchScrollMode);
|
||||
eventBus._on("scrollmodechanged", webViewerScrollModeChanged);
|
||||
eventBus._on("switchspreadmode", webViewerSwitchSpreadMode);
|
||||
@ -1827,6 +1847,7 @@ const PDFViewerApplication = {
|
||||
eventBus._off("scalechanged", webViewerScaleChanged);
|
||||
eventBus._off("rotatecw", webViewerRotateCw);
|
||||
eventBus._off("rotateccw", webViewerRotateCcw);
|
||||
eventBus._off("optionalcontentconfig", webViewerOptionalContentConfig);
|
||||
eventBus._off("switchscrollmode", webViewerSwitchScrollMode);
|
||||
eventBus._off("scrollmodechanged", webViewerScrollModeChanged);
|
||||
eventBus._off("switchspreadmode", webViewerSwitchSpreadMode);
|
||||
@ -2169,12 +2190,15 @@ function webViewerPageMode({ mode }) {
|
||||
view = SidebarView.THUMBS;
|
||||
break;
|
||||
case "bookmarks":
|
||||
case "outline":
|
||||
case "outline": // non-standard
|
||||
view = SidebarView.OUTLINE;
|
||||
break;
|
||||
case "attachments":
|
||||
case "attachments": // non-standard
|
||||
view = SidebarView.ATTACHMENTS;
|
||||
break;
|
||||
case "layers": // non-standard
|
||||
view = SidebarView.LAYERS;
|
||||
break;
|
||||
case "none":
|
||||
view = SidebarView.NONE;
|
||||
break;
|
||||
@ -2420,6 +2444,9 @@ function webViewerRotateCw() {
|
||||
function webViewerRotateCcw() {
|
||||
PDFViewerApplication.rotatePages(-90);
|
||||
}
|
||||
function webViewerOptionalContentConfig(evt) {
|
||||
PDFViewerApplication.pdfViewer.optionalContentConfigPromise = evt.promise;
|
||||
}
|
||||
function webViewerSwitchScrollMode(evt) {
|
||||
PDFViewerApplication.pdfViewer.scrollMode = evt.mode;
|
||||
}
|
||||
@ -3013,7 +3040,7 @@ function apiPageModeToSidebarView(mode) {
|
||||
case "UseAttachments":
|
||||
return SidebarView.ATTACHMENTS;
|
||||
case "UseOC":
|
||||
// Not implemented, since we don't support Optional Content Groups yet.
|
||||
return SidebarView.LAYERS;
|
||||
}
|
||||
return SidebarView.NONE; // Default value.
|
||||
}
|
||||
|
@ -439,6 +439,7 @@ class BaseViewer {
|
||||
const firstPagePromise = pdfDocument.getPage(1);
|
||||
|
||||
const annotationStorage = pdfDocument.annotationStorage;
|
||||
const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig();
|
||||
|
||||
this._pagesCapability.promise.then(() => {
|
||||
this.eventBus.dispatch("pagesloaded", {
|
||||
@ -474,6 +475,7 @@ class BaseViewer {
|
||||
firstPagePromise
|
||||
.then(firstPdfPage => {
|
||||
this._firstPageCapability.resolve(firstPdfPage);
|
||||
this._optionalContentConfigPromise = optionalContentConfigPromise;
|
||||
|
||||
const scale = this.currentScale;
|
||||
const viewport = firstPdfPage.getViewport({ scale: scale * CSS_UNITS });
|
||||
@ -486,8 +488,9 @@ class BaseViewer {
|
||||
eventBus: this.eventBus,
|
||||
id: pageNum,
|
||||
scale,
|
||||
annotationStorage,
|
||||
defaultViewport: viewport.clone(),
|
||||
annotationStorage,
|
||||
optionalContentConfigPromise,
|
||||
renderingQueue: this.renderingQueue,
|
||||
textLayerFactory,
|
||||
textLayerMode: this.textLayerMode,
|
||||
@ -605,6 +608,7 @@ class BaseViewer {
|
||||
this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
|
||||
this._location = null;
|
||||
this._pagesRotation = 0;
|
||||
this._optionalContentConfigPromise = null;
|
||||
this._pagesRequests = new WeakMap();
|
||||
this._firstPageCapability = createPromiseCapability();
|
||||
this._onePageRenderedCapability = createPromiseCapability();
|
||||
@ -1222,6 +1226,50 @@ class BaseViewer {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Promise<OptionalContentConfig | null>}
|
||||
*/
|
||||
get optionalContentConfigPromise() {
|
||||
if (!this.pdfDocument) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
if (!this._optionalContentConfigPromise) {
|
||||
// Prevent issues if the getter is accessed *before* the `onePageRendered`
|
||||
// promise has resolved; won't (normally) happen in the default viewer.
|
||||
return this.pdfDocument.getOptionalContentConfig();
|
||||
}
|
||||
return this._optionalContentConfigPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Promise<OptionalContentConfig>} promise - A promise that is
|
||||
* resolved with an {@link OptionalContentConfig} instance.
|
||||
*/
|
||||
set optionalContentConfigPromise(promise) {
|
||||
if (!(promise instanceof Promise)) {
|
||||
throw new Error(`Invalid optionalContentConfigPromise: ${promise}`);
|
||||
}
|
||||
if (!this.pdfDocument) {
|
||||
return;
|
||||
}
|
||||
if (!this._optionalContentConfigPromise) {
|
||||
// Ignore the setter *before* the `onePageRendered` promise has resolved,
|
||||
// since it'll be overwritten anyway; won't happen in the default viewer.
|
||||
return;
|
||||
}
|
||||
this._optionalContentConfigPromise = promise;
|
||||
|
||||
for (const pageView of this._pages) {
|
||||
pageView.update(pageView.scale, pageView.rotation, promise);
|
||||
}
|
||||
this.update();
|
||||
|
||||
this.eventBus.dispatch("optionalcontentconfigchanged", {
|
||||
source: this,
|
||||
promise,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number} One of the values in {ScrollMode}.
|
||||
*/
|
||||
|
@ -23,7 +23,8 @@ function composePage(
|
||||
pageNumber,
|
||||
size,
|
||||
printContainer,
|
||||
printResolution
|
||||
printResolution,
|
||||
optionalContentConfigPromise
|
||||
) {
|
||||
const canvas = document.createElement("canvas");
|
||||
|
||||
@ -58,6 +59,7 @@ function composePage(
|
||||
viewport: pdfPage.getViewport({ scale: 1, rotation: size.rotation }),
|
||||
intent: "print",
|
||||
annotationStorage: pdfDocument.annotationStorage,
|
||||
optionalContentConfigPromise,
|
||||
};
|
||||
return pdfPage.render(renderContext).promise;
|
||||
})
|
||||
@ -84,12 +86,15 @@ function FirefoxPrintService(
|
||||
pdfDocument,
|
||||
pagesOverview,
|
||||
printContainer,
|
||||
printResolution
|
||||
printResolution,
|
||||
optionalContentConfigPromise = null
|
||||
) {
|
||||
this.pdfDocument = pdfDocument;
|
||||
this.pagesOverview = pagesOverview;
|
||||
this.printContainer = printContainer;
|
||||
this._printResolution = printResolution || 150;
|
||||
this._optionalContentConfigPromise =
|
||||
optionalContentConfigPromise || pdfDocument.getOptionalContentConfig();
|
||||
}
|
||||
|
||||
FirefoxPrintService.prototype = {
|
||||
@ -99,6 +104,7 @@ FirefoxPrintService.prototype = {
|
||||
pagesOverview,
|
||||
printContainer,
|
||||
_printResolution,
|
||||
_optionalContentConfigPromise,
|
||||
} = this;
|
||||
|
||||
const body = document.querySelector("body");
|
||||
@ -110,7 +116,8 @@ FirefoxPrintService.prototype = {
|
||||
/* pageNumber = */ i + 1,
|
||||
pagesOverview[i],
|
||||
printContainer,
|
||||
_printResolution
|
||||
_printResolution,
|
||||
_optionalContentConfigPromise
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -135,13 +142,15 @@ PDFPrintServiceFactory.instance = {
|
||||
pdfDocument,
|
||||
pagesOverview,
|
||||
printContainer,
|
||||
printResolution
|
||||
printResolution,
|
||||
optionalContentConfigPromise
|
||||
) {
|
||||
return new FirefoxPrintService(
|
||||
pdfDocument,
|
||||
pagesOverview,
|
||||
printContainer,
|
||||
printResolution
|
||||
printResolution,
|
||||
optionalContentConfigPromise
|
||||
);
|
||||
},
|
||||
};
|
||||
|
1
web/images/toolbarButton-viewLayers-dark.svg
Normal file
1
web/images/toolbarButton-viewLayers-dark.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4.233 4.233" height="16" width="16" fill="rgba(255,255,255,1)"><path d="M.15 2.992c-.198.1-.2.266-.002.365l1.604.802a.93.93 0 00.729-.001l1.602-.801c.198-.1.197-.264 0-.364l-.695-.348c-1.306.595-2.542 0-2.542 0m-.264.53l.658-.329c.6.252 1.238.244 1.754 0l.659.329-1.536.768zM.15 1.935c-.198.1-.198.265 0 .364l1.604.802a.926.926 0 00.727 0l1.603-.802c.198-.099.198-.264 0-.363l-.694-.35c-1.14.56-2.546.001-2.546.001m-.264.53l.664-.332c.52.266 1.261.235 1.75.002l.659.33-1.537.768zM.15.877c-.198.099-.198.264 0 .363l1.604.802a.926.926 0 00.727 0l1.603-.802c.198-.099.198-.264 0-.363L2.481.075a.926.926 0 00-.727 0zm.43.182L2.117.29l1.538.769-1.538.768z"/></svg>
|
After Width: | Height: | Size: 712 B |
1
web/images/toolbarButton-viewLayers.svg
Normal file
1
web/images/toolbarButton-viewLayers.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4.233 4.233" height="16" width="16"><path d="M.15 2.992c-.198.1-.2.266-.002.365l1.604.802a.93.93 0 00.729-.001l1.602-.801c.198-.1.197-.264 0-.364l-.695-.348c-1.306.595-2.542 0-2.542 0m-.264.53l.658-.329c.6.252 1.238.244 1.754 0l.659.329-1.536.768zM.15 1.935c-.198.1-.198.265 0 .364l1.604.802a.926.926 0 00.727 0l1.603-.802c.198-.099.198-.264 0-.363l-.694-.35c-1.14.56-2.546.001-2.546.001m-.264.53l.664-.332c.52.266 1.261.235 1.75.002l.659.33-1.537.768zM.15.877c-.198.099-.198.264 0 .363l1.604.802a.926.926 0 00.727 0l1.603-.802c.198-.099.198-.264 0-.363L2.481.075a.926.926 0 00-.727 0zm.43.182L2.117.29l1.538.769-1.538.768z"/></svg>
|
After Width: | Height: | Size: 685 B |
212
web/pdf_layer_viewer.js
Normal file
212
web/pdf_layer_viewer.js
Normal file
@ -0,0 +1,212 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
import { BaseTreeViewer } from "./base_tree_viewer.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} PDFLayerViewerOptions
|
||||
* @property {HTMLDivElement} container - The viewer element.
|
||||
* @property {EventBus} eventBus - The application event bus.
|
||||
* @property {IL10n} l10n - Localization service.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} PDFLayerViewerRenderParameters
|
||||
* @property {OptionalContentConfig|null} optionalContentConfig - An
|
||||
* {OptionalContentConfig} instance.
|
||||
* @property {PDFDocument} pdfDocument - A {PDFDocument} instance.
|
||||
*/
|
||||
|
||||
class PDFLayerViewer extends BaseTreeViewer {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.l10n = options.l10n;
|
||||
|
||||
this.eventBus._on("resetlayers", this._resetLayers.bind(this));
|
||||
this.eventBus._on("togglelayerstree", this._toggleAllTreeItems.bind(this));
|
||||
}
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
this._optionalContentConfig = null;
|
||||
this._pdfDocument = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_dispatchEvent(layersCount) {
|
||||
this.eventBus.dispatch("layersloaded", {
|
||||
source: this,
|
||||
layersCount,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_bindLink(element, { groupId, input }) {
|
||||
const setVisibility = () => {
|
||||
this._optionalContentConfig.setVisibility(groupId, input.checked);
|
||||
|
||||
this.eventBus.dispatch("optionalcontentconfig", {
|
||||
source: this,
|
||||
promise: Promise.resolve(this._optionalContentConfig),
|
||||
});
|
||||
};
|
||||
|
||||
element.onclick = evt => {
|
||||
if (evt.target === input) {
|
||||
setVisibility();
|
||||
return true;
|
||||
} else if (evt.target !== element) {
|
||||
return true; // The target is the "label", which is handled above.
|
||||
}
|
||||
input.checked = !input.checked;
|
||||
setVisibility();
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _setNestedName(element, { name = null }) {
|
||||
if (typeof name === "string") {
|
||||
element.textContent = this._normalizeTextContent(name);
|
||||
return;
|
||||
}
|
||||
element.textContent = await this.l10n.get(
|
||||
"additional_layers",
|
||||
null,
|
||||
"Additional Layers"
|
||||
);
|
||||
element.style.fontStyle = "italic";
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_addToggleButton(div, { name = null }) {
|
||||
super._addToggleButton(div, /* hidden = */ name === null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_toggleAllTreeItems() {
|
||||
if (!this._optionalContentConfig) {
|
||||
return;
|
||||
}
|
||||
super._toggleAllTreeItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PDFLayerViewerRenderParameters} params
|
||||
*/
|
||||
render({ optionalContentConfig, pdfDocument }) {
|
||||
if (this._optionalContentConfig) {
|
||||
this.reset();
|
||||
}
|
||||
this._optionalContentConfig = optionalContentConfig || null;
|
||||
this._pdfDocument = pdfDocument || null;
|
||||
|
||||
const groups = optionalContentConfig && optionalContentConfig.getOrder();
|
||||
if (!groups) {
|
||||
this._dispatchEvent(/* layersCount = */ 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const fragment = document.createDocumentFragment(),
|
||||
queue = [{ parent: fragment, groups }];
|
||||
let layersCount = 0,
|
||||
hasAnyNesting = false;
|
||||
while (queue.length > 0) {
|
||||
const levelData = queue.shift();
|
||||
for (const groupId of levelData.groups) {
|
||||
const div = document.createElement("div");
|
||||
div.className = "treeItem";
|
||||
|
||||
const element = document.createElement("a");
|
||||
div.appendChild(element);
|
||||
|
||||
if (typeof groupId === "object") {
|
||||
hasAnyNesting = true;
|
||||
this._addToggleButton(div, groupId);
|
||||
this._setNestedName(element, groupId);
|
||||
|
||||
const itemsDiv = document.createElement("div");
|
||||
itemsDiv.className = "treeItems";
|
||||
div.appendChild(itemsDiv);
|
||||
|
||||
queue.push({ parent: itemsDiv, groups: groupId.order });
|
||||
} else {
|
||||
const group = optionalContentConfig.getGroup(groupId);
|
||||
|
||||
const input = document.createElement("input");
|
||||
this._bindLink(element, { groupId, input });
|
||||
input.type = "checkbox";
|
||||
input.id = groupId;
|
||||
input.checked = group.visible;
|
||||
|
||||
const label = document.createElement("label");
|
||||
label.setAttribute("for", groupId);
|
||||
label.textContent = this._normalizeTextContent(group.name);
|
||||
|
||||
element.appendChild(input);
|
||||
element.appendChild(label);
|
||||
|
||||
layersCount++;
|
||||
}
|
||||
|
||||
levelData.parent.appendChild(div);
|
||||
}
|
||||
}
|
||||
if (hasAnyNesting) {
|
||||
this.container.classList.add("treeWithDeepNesting");
|
||||
|
||||
this._lastToggleIsShow =
|
||||
fragment.querySelectorAll(".treeItemsHidden").length === 0;
|
||||
}
|
||||
|
||||
this.container.appendChild(fragment);
|
||||
|
||||
this._dispatchEvent(layersCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _resetLayers() {
|
||||
if (!this._optionalContentConfig) {
|
||||
return;
|
||||
}
|
||||
// Fetch the default optional content configuration...
|
||||
const optionalContentConfig = await this._pdfDocument.getOptionalContentConfig();
|
||||
|
||||
this.eventBus.dispatch("optionalcontentconfig", {
|
||||
source: this,
|
||||
promise: Promise.resolve(optionalContentConfig),
|
||||
});
|
||||
|
||||
// ... and reset the sidebarView to the default state.
|
||||
this.render({
|
||||
optionalContentConfig,
|
||||
pdfDocument: this._pdfDocument,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { PDFLayerViewer };
|
@ -40,6 +40,9 @@ import { viewerCompatibilityParams } from "./viewer_compatibility.js";
|
||||
* @property {PageViewport} defaultViewport - The page viewport.
|
||||
* @property {AnnotationStorage} [annotationStorage] - Storage for annotation
|
||||
* data in forms. The default value is `null`.
|
||||
* @property {Promise<OptionalContentConfig>} [optionalContentConfigPromise] -
|
||||
* A promise that is resolved with an {@link OptionalContentConfig} instance.
|
||||
* The default value is `null`.
|
||||
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
|
||||
* @property {IPDFTextLayerFactory} textLayerFactory
|
||||
* @property {number} [textLayerMode] - Controls if the text layer used for
|
||||
@ -83,8 +86,10 @@ class PDFPageView {
|
||||
this.rotation = 0;
|
||||
this.scale = options.scale || DEFAULT_SCALE;
|
||||
this.viewport = defaultViewport;
|
||||
this._annotationStorage = options.annotationStorage || null;
|
||||
this.pdfPageRotate = defaultViewport.rotation;
|
||||
this._annotationStorage = options.annotationStorage || null;
|
||||
this._optionalContentConfigPromise =
|
||||
options.optionalContentConfigPromise || null;
|
||||
this.hasRestrictedScaling = false;
|
||||
this.textLayerMode = Number.isInteger(options.textLayerMode)
|
||||
? options.textLayerMode
|
||||
@ -236,12 +241,15 @@ class PDFPageView {
|
||||
div.appendChild(this.loadingIconDiv);
|
||||
}
|
||||
|
||||
update(scale, rotation) {
|
||||
update(scale, rotation, optionalContentConfigPromise = null) {
|
||||
this.scale = scale || this.scale;
|
||||
// The rotation may be zero.
|
||||
if (typeof rotation !== "undefined") {
|
||||
this.rotation = rotation;
|
||||
}
|
||||
if (optionalContentConfigPromise instanceof Promise) {
|
||||
this._optionalContentConfigPromise = optionalContentConfigPromise;
|
||||
}
|
||||
|
||||
const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
|
||||
this.viewport = this.viewport.clone({
|
||||
@ -660,6 +668,7 @@ class PDFPageView {
|
||||
viewport: this.viewport,
|
||||
enableWebGL: this.enableWebGL,
|
||||
renderInteractiveForms: this.renderInteractiveForms,
|
||||
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
||||
};
|
||||
const renderTask = this.pdfPage.render(renderContext);
|
||||
renderTask.onContinue = function (cont) {
|
||||
|
@ -27,7 +27,8 @@ function renderPage(
|
||||
pdfDocument,
|
||||
pageNumber,
|
||||
size,
|
||||
printResolution
|
||||
printResolution,
|
||||
optionalContentConfigPromise
|
||||
) {
|
||||
const scratchCanvas = activeService.scratchCanvas;
|
||||
|
||||
@ -55,6 +56,7 @@ function renderPage(
|
||||
viewport: pdfPage.getViewport({ scale: 1, rotation: size.rotation }),
|
||||
intent: "print",
|
||||
annotationStorage: pdfDocument.annotationStorage,
|
||||
optionalContentConfigPromise,
|
||||
};
|
||||
return pdfPage.render(renderContext).promise;
|
||||
})
|
||||
@ -71,12 +73,15 @@ function PDFPrintService(
|
||||
pagesOverview,
|
||||
printContainer,
|
||||
printResolution,
|
||||
optionalContentConfigPromise = null,
|
||||
l10n
|
||||
) {
|
||||
this.pdfDocument = pdfDocument;
|
||||
this.pagesOverview = pagesOverview;
|
||||
this.printContainer = printContainer;
|
||||
this._printResolution = printResolution || 150;
|
||||
this._optionalContentConfigPromise =
|
||||
optionalContentConfigPromise || pdfDocument.getOptionalContentConfig();
|
||||
this.l10n = l10n || NullL10n;
|
||||
this.currentPage = -1;
|
||||
// The temporary canvas where renderPage paints one page at a time.
|
||||
@ -170,7 +175,8 @@ PDFPrintService.prototype = {
|
||||
this.pdfDocument,
|
||||
/* pageNumber = */ index + 1,
|
||||
this.pagesOverview[index],
|
||||
this._printResolution
|
||||
this._printResolution,
|
||||
this._optionalContentConfigPromise
|
||||
)
|
||||
.then(this.useRenderedPage.bind(this))
|
||||
.then(function () {
|
||||
@ -372,6 +378,7 @@ PDFPrintServiceFactory.instance = {
|
||||
pagesOverview,
|
||||
printContainer,
|
||||
printResolution,
|
||||
optionalContentConfigPromise,
|
||||
l10n
|
||||
) {
|
||||
if (activeService) {
|
||||
@ -382,6 +389,7 @@ PDFPrintServiceFactory.instance = {
|
||||
pagesOverview,
|
||||
printContainer,
|
||||
printResolution,
|
||||
optionalContentConfigPromise,
|
||||
l10n
|
||||
);
|
||||
return activeService;
|
||||
|
@ -52,12 +52,16 @@ const SidebarView = {
|
||||
* the outline view.
|
||||
* @property {HTMLButtonElement} attachmentsButton - The button used to show
|
||||
* the attachments view.
|
||||
* @property {HTMLButtonElement} layersButton - The button used to show
|
||||
* the layers view.
|
||||
* @property {HTMLDivElement} thumbnailView - The container in which
|
||||
* the thumbnails are placed.
|
||||
* @property {HTMLDivElement} outlineView - The container in which
|
||||
* the outline is placed.
|
||||
* @property {HTMLDivElement} attachmentsView - The container in which
|
||||
* the attachments are placed.
|
||||
* @property {HTMLDivElement} layersView - The container in which
|
||||
* the layers are placed.
|
||||
*/
|
||||
|
||||
class PDFSidebar {
|
||||
@ -92,10 +96,12 @@ class PDFSidebar {
|
||||
this.thumbnailButton = elements.thumbnailButton;
|
||||
this.outlineButton = elements.outlineButton;
|
||||
this.attachmentsButton = elements.attachmentsButton;
|
||||
this.layersButton = elements.layersButton;
|
||||
|
||||
this.thumbnailView = elements.thumbnailView;
|
||||
this.outlineView = elements.outlineView;
|
||||
this.attachmentsView = elements.attachmentsView;
|
||||
this.layersView = elements.layersView;
|
||||
|
||||
this.eventBus = eventBus;
|
||||
this.l10n = l10n;
|
||||
@ -112,6 +118,7 @@ class PDFSidebar {
|
||||
|
||||
this.outlineButton.disabled = false;
|
||||
this.attachmentsButton.disabled = false;
|
||||
this.layersButton.disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,6 +140,10 @@ class PDFSidebar {
|
||||
return this.isOpen && this.active === SidebarView.ATTACHMENTS;
|
||||
}
|
||||
|
||||
get isLayersViewVisible() {
|
||||
return this.isOpen && this.active === SidebarView.LAYERS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} view - The sidebar view that should become visible,
|
||||
* must be one of the values in {SidebarView}.
|
||||
@ -196,6 +207,11 @@ class PDFSidebar {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case SidebarView.LAYERS:
|
||||
if (this.layersButton.disabled) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error(`PDFSidebar._switchView: "${view}" is not a valid view.`);
|
||||
return false;
|
||||
@ -217,6 +233,7 @@ class PDFSidebar {
|
||||
"toggled",
|
||||
view === SidebarView.ATTACHMENTS
|
||||
);
|
||||
this.layersButton.classList.toggle("toggled", view === SidebarView.LAYERS);
|
||||
// ... and for all views.
|
||||
this.thumbnailView.classList.toggle("hidden", view !== SidebarView.THUMBS);
|
||||
this.outlineView.classList.toggle("hidden", view !== SidebarView.OUTLINE);
|
||||
@ -224,6 +241,7 @@ class PDFSidebar {
|
||||
"hidden",
|
||||
view !== SidebarView.ATTACHMENTS
|
||||
);
|
||||
this.layersView.classList.toggle("hidden", view !== SidebarView.LAYERS);
|
||||
|
||||
if (forceOpen && !this.isOpen) {
|
||||
this.open();
|
||||
@ -331,9 +349,9 @@ class PDFSidebar {
|
||||
|
||||
this.l10n
|
||||
.get(
|
||||
"toggle_sidebar_notification.title",
|
||||
"toggle_sidebar_notification2.title",
|
||||
null,
|
||||
"Toggle Sidebar (document contains outline/attachments)"
|
||||
"Toggle Sidebar (document contains outline/attachments/layers)"
|
||||
)
|
||||
.then(msg => {
|
||||
this.toggleButton.title = msg;
|
||||
@ -356,6 +374,9 @@ class PDFSidebar {
|
||||
case SidebarView.ATTACHMENTS:
|
||||
this.attachmentsButton.classList.add(UI_NOTIFICATION_CLASS);
|
||||
break;
|
||||
case SidebarView.LAYERS:
|
||||
this.layersButton.classList.add(UI_NOTIFICATION_CLASS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,6 +396,9 @@ class PDFSidebar {
|
||||
case SidebarView.ATTACHMENTS:
|
||||
this.attachmentsButton.classList.remove(UI_NOTIFICATION_CLASS);
|
||||
break;
|
||||
case SidebarView.LAYERS:
|
||||
this.layersButton.classList.remove(UI_NOTIFICATION_CLASS);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
@ -429,6 +453,13 @@ class PDFSidebar {
|
||||
this.switchView(SidebarView.ATTACHMENTS);
|
||||
});
|
||||
|
||||
this.layersButton.addEventListener("click", () => {
|
||||
this.switchView(SidebarView.LAYERS);
|
||||
});
|
||||
this.layersButton.addEventListener("dblclick", () => {
|
||||
this.eventBus.dispatch("resetlayers", { source: this });
|
||||
});
|
||||
|
||||
// Disable/enable views.
|
||||
const onTreeLoaded = (count, button, view) => {
|
||||
button.disabled = !count;
|
||||
@ -454,6 +485,10 @@ class PDFSidebar {
|
||||
);
|
||||
});
|
||||
|
||||
this.eventBus._on("layersloaded", evt => {
|
||||
onTreeLoaded(evt.layersCount, this.layersButton, SidebarView.LAYERS);
|
||||
});
|
||||
|
||||
// Update the thumbnailViewer, if visible, when exiting presentation mode.
|
||||
this.eventBus._on("presentationmodechanged", evt => {
|
||||
if (!evt.active && !evt.switchInProgress && this.isThumbnailViewVisible) {
|
||||
|
@ -29,8 +29,12 @@ const THUMBNAIL_WIDTH = 98; // px
|
||||
* @property {HTMLDivElement} container - The viewer element.
|
||||
* @property {number} id - The thumbnail's unique ID (normally its number).
|
||||
* @property {PageViewport} defaultViewport - The page viewport.
|
||||
* @property {Promise<OptionalContentConfig>} [optionalContentConfigPromise] -
|
||||
* A promise that is resolved with an {@link OptionalContentConfig} instance.
|
||||
* The default value is `null`.
|
||||
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
||||
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
|
||||
* @property {function} checkSetImageDisabled
|
||||
* @property {boolean} [disableCanvasToImageConversion] - Don't convert the
|
||||
* canvas thumbnails to images. This prevents `toDataURL` calls, but
|
||||
* increases the overall memory usage. The default value is `false`.
|
||||
@ -91,8 +95,10 @@ class PDFThumbnailView {
|
||||
container,
|
||||
id,
|
||||
defaultViewport,
|
||||
optionalContentConfigPromise,
|
||||
linkService,
|
||||
renderingQueue,
|
||||
checkSetImageDisabled,
|
||||
disableCanvasToImageConversion = false,
|
||||
l10n = NullL10n,
|
||||
}) {
|
||||
@ -104,6 +110,7 @@ class PDFThumbnailView {
|
||||
this.rotation = 0;
|
||||
this.viewport = defaultViewport;
|
||||
this.pdfPageRotate = defaultViewport.rotation;
|
||||
this._optionalContentConfigPromise = optionalContentConfigPromise || null;
|
||||
|
||||
this.linkService = linkService;
|
||||
this.renderingQueue = renderingQueue;
|
||||
@ -111,6 +118,11 @@ class PDFThumbnailView {
|
||||
this.renderTask = null;
|
||||
this.renderingState = RenderingStates.INITIAL;
|
||||
this.resume = null;
|
||||
this._checkSetImageDisabled =
|
||||
checkSetImageDisabled ||
|
||||
function () {
|
||||
return false;
|
||||
};
|
||||
this.disableCanvasToImageConversion = disableCanvasToImageConversion;
|
||||
|
||||
this.pageWidth = this.viewport.width;
|
||||
@ -345,6 +357,7 @@ class PDFThumbnailView {
|
||||
const renderContext = {
|
||||
canvasContext: ctx,
|
||||
viewport: drawViewport,
|
||||
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
||||
};
|
||||
const renderTask = (this.renderTask = pdfPage.render(renderContext));
|
||||
renderTask.onContinue = renderContinueCallback;
|
||||
@ -361,6 +374,9 @@ class PDFThumbnailView {
|
||||
}
|
||||
|
||||
setImage(pageView) {
|
||||
if (this._checkSetImageDisabled()) {
|
||||
return;
|
||||
}
|
||||
if (this.renderingState !== RenderingStates.INITIAL) {
|
||||
return;
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ const THUMBNAIL_SELECTED_CLASS = "selected";
|
||||
* @typedef {Object} PDFThumbnailViewerOptions
|
||||
* @property {HTMLDivElement} container - The container for the thumbnail
|
||||
* elements.
|
||||
* @property {EventBus} eventBus - The application event bus.
|
||||
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
||||
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
|
||||
* @property {IL10n} l10n - Localization service.
|
||||
@ -43,7 +44,13 @@ class PDFThumbnailViewer {
|
||||
/**
|
||||
* @param {PDFThumbnailViewerOptions} options
|
||||
*/
|
||||
constructor({ container, linkService, renderingQueue, l10n = NullL10n }) {
|
||||
constructor({
|
||||
container,
|
||||
eventBus,
|
||||
linkService,
|
||||
renderingQueue,
|
||||
l10n = NullL10n,
|
||||
}) {
|
||||
this.container = container;
|
||||
this.linkService = linkService;
|
||||
this.renderingQueue = renderingQueue;
|
||||
@ -51,6 +58,12 @@ class PDFThumbnailViewer {
|
||||
|
||||
this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this));
|
||||
this._resetView();
|
||||
|
||||
eventBus._on("optionalcontentconfigchanged", () => {
|
||||
// Ensure that the thumbnails always render with the *default* optional
|
||||
// content configuration.
|
||||
this._setImageDisabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -151,7 +164,9 @@ class PDFThumbnailViewer {
|
||||
this._currentPageNumber = 1;
|
||||
this._pageLabels = null;
|
||||
this._pagesRotation = 0;
|
||||
this._optionalContentConfigPromise = null;
|
||||
this._pagesRequests = new WeakMap();
|
||||
this._setImageDisabled = false;
|
||||
|
||||
// Remove the thumbnails from the DOM.
|
||||
this.container.textContent = "";
|
||||
@ -167,19 +182,28 @@ class PDFThumbnailViewer {
|
||||
if (!pdfDocument) {
|
||||
return;
|
||||
}
|
||||
const firstPagePromise = pdfDocument.getPage(1);
|
||||
const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig();
|
||||
|
||||
pdfDocument
|
||||
.getPage(1)
|
||||
firstPagePromise
|
||||
.then(firstPdfPage => {
|
||||
this._optionalContentConfigPromise = optionalContentConfigPromise;
|
||||
|
||||
const pagesCount = pdfDocument.numPages;
|
||||
const viewport = firstPdfPage.getViewport({ scale: 1 });
|
||||
const checkSetImageDisabled = () => {
|
||||
return this._setImageDisabled;
|
||||
};
|
||||
|
||||
for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
|
||||
const thumbnail = new PDFThumbnailView({
|
||||
container: this.container,
|
||||
id: pageNum,
|
||||
defaultViewport: viewport.clone(),
|
||||
optionalContentConfigPromise,
|
||||
linkService: this.linkService,
|
||||
renderingQueue: this.renderingQueue,
|
||||
checkSetImageDisabled,
|
||||
disableCanvasToImageConversion: false,
|
||||
l10n: this.l10n,
|
||||
});
|
||||
|
@ -73,6 +73,7 @@
|
||||
--toolbarButton-viewThumbnail-icon: url(images/toolbarButton-viewThumbnail.svg);
|
||||
--toolbarButton-viewOutline-icon: url(images/toolbarButton-viewOutline.svg);
|
||||
--toolbarButton-viewAttachments-icon: url(images/toolbarButton-viewAttachments.svg);
|
||||
--toolbarButton-viewLayers-icon: url(images/toolbarButton-viewLayers.svg);
|
||||
--toolbarButton-search-icon: url(images/toolbarButton-search.svg);
|
||||
--findbarButton-previous-icon: url(images/findbarButton-previous.svg);
|
||||
--findbarButton-next-icon: url(images/findbarButton-next.svg);
|
||||
@ -143,6 +144,7 @@
|
||||
--toolbarButton-viewThumbnail-icon: url(images/toolbarButton-viewThumbnail-dark.svg);
|
||||
--toolbarButton-viewOutline-icon: url(images/toolbarButton-viewOutline-dark.svg);
|
||||
--toolbarButton-viewAttachments-icon: url(images/toolbarButton-viewAttachments-dark.svg);
|
||||
--toolbarButton-viewLayers-icon: url(images/toolbarButton-viewLayers-dark.svg);
|
||||
--toolbarButton-search-icon: url(images/toolbarButton-search-dark.svg);
|
||||
--findbarButton-previous-icon: url(images/findbarButton-previous-dark.svg);
|
||||
--findbarButton-next-icon: url(images/findbarButton-next-dark.svg);
|
||||
@ -1066,6 +1068,10 @@ html[dir="rtl"] #viewOutline.toolbarButton::before {
|
||||
content: var(--toolbarButton-viewAttachments-icon);
|
||||
}
|
||||
|
||||
#viewLayers.toolbarButton::before {
|
||||
content: var(--toolbarButton-viewLayers-icon);
|
||||
}
|
||||
|
||||
#viewFind.toolbarButton::before {
|
||||
content: var(--toolbarButton-search-icon);
|
||||
}
|
||||
@ -1339,7 +1345,8 @@ a:focus > .thumbnail > .thumbnailSelectionRing,
|
||||
}
|
||||
|
||||
#outlineView,
|
||||
#attachmentsView {
|
||||
#attachmentsView,
|
||||
#layersView {
|
||||
position: absolute;
|
||||
width: calc(100% - 8px);
|
||||
top: 0;
|
||||
@ -1383,6 +1390,16 @@ html[dir='rtl'] .treeItem > a {
|
||||
padding: 2px 4px 5px 0;
|
||||
}
|
||||
|
||||
#layersView .treeItem > a > * {
|
||||
cursor: pointer;
|
||||
}
|
||||
html[dir='ltr'] #layersView .treeItem > a > label {
|
||||
padding-left: 4px;
|
||||
}
|
||||
html[dir='rtl'] #layersView .treesItem > a > label {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.treeItemToggler {
|
||||
position: relative;
|
||||
height: 0;
|
||||
|
@ -86,6 +86,9 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
<button id="viewAttachments" class="toolbarButton" title="Show Attachments" tabindex="4" data-l10n-id="attachments">
|
||||
<span data-l10n-id="attachments_label">Attachments</span>
|
||||
</button>
|
||||
<button id="viewLayers" class="toolbarButton" title="Show Layers (double-click to reset all layers to the default state)" tabindex="5" data-l10n-id="layers">
|
||||
<span data-l10n-id="layers_label">Layers</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sidebarContent">
|
||||
@ -95,6 +98,8 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
</div>
|
||||
<div id="attachmentsView" class="hidden">
|
||||
</div>
|
||||
<div id="layersView" class="hidden">
|
||||
</div>
|
||||
</div>
|
||||
<div id="sidebarResizer" class="hidden"></div>
|
||||
</div> <!-- sidebarContainer -->
|
||||
|
@ -121,10 +121,12 @@ function getViewerConfiguration() {
|
||||
thumbnailButton: document.getElementById("viewThumbnail"),
|
||||
outlineButton: document.getElementById("viewOutline"),
|
||||
attachmentsButton: document.getElementById("viewAttachments"),
|
||||
layersButton: document.getElementById("viewLayers"),
|
||||
// Views
|
||||
thumbnailView: document.getElementById("thumbnailView"),
|
||||
outlineView: document.getElementById("outlineView"),
|
||||
attachmentsView: document.getElementById("attachmentsView"),
|
||||
layersView: document.getElementById("layersView"),
|
||||
},
|
||||
sidebarResizer: {
|
||||
outerContainer: document.getElementById("outerContainer"),
|
||||
|
Loading…
Reference in New Issue
Block a user