mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-16 22:50:13 +01:00
This commit is contained in:
parent
ae8ce006ba
commit
da71f8c4aa
44 changed files with 4505 additions and 3740 deletions
|
|
@ -28,7 +28,7 @@ const rectAnno = (config: any, pdf: any, element: HTMLElement) => {
|
|||
// 右键
|
||||
return;
|
||||
}
|
||||
const canvasRect = pdf.pdfViewer._getCurrentVisiblePage().first.view.canvas.getBoundingClientRect();
|
||||
const canvasRect = pdf.pdfViewer._getVisiblePages().first.view.canvas.getBoundingClientRect();
|
||||
const containerRet = config.mainContainer.getBoundingClientRect();
|
||||
const mostLeft = canvasRect.left;
|
||||
const mostRight = canvasRect.right;
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ export class Asset extends Model {
|
|||
</div>
|
||||
</div>
|
||||
<div id="overlayContainer" class="fn__hidden">
|
||||
<div id="passwordOverlay" class="container fn__hidden">
|
||||
<div id="passwordDialog" class="container fn__hidden">
|
||||
<div class="dialog">
|
||||
<div class="row">
|
||||
<p id="passwordText" data-l10n-id="password_label">Enter the password to open this PDF file:</p>
|
||||
|
|
@ -336,7 +336,7 @@ export class Asset extends Model {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="documentPropertiesOverlay" class="container fn__hidden">
|
||||
<div id="documentPropertiesDialog" class="container fn__hidden">
|
||||
<div class="dialog b3-menu">
|
||||
<div class="row">
|
||||
<span>${window.siyuan.languages.fileName}</span> <p id="fileNameField">-</p>
|
||||
|
|
@ -427,6 +427,13 @@ export class Asset extends Model {
|
|||
<span class="b3-menu__label">${window.siyuan.languages.remove}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="fn__none">
|
||||
<input id="editorFreeTextFontSize">
|
||||
<input id="editorFreeTextColor">
|
||||
<input id="editorInkColor">
|
||||
<input id="editorInkThickness">
|
||||
<input id="editorInkOpacity">
|
||||
</div>
|
||||
</div> <!-- outerContainer -->
|
||||
<div id="printContainer"></div>`;
|
||||
// 初始化完成后需等待页签是否显示设置完成,才可以判断 pdf 是否能进行渲染
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
||||
/** @typedef {import("./interfaces").IDownloadManager} IDownloadManager */
|
||||
/** @typedef {import("./interfaces").IL10n} IL10n */
|
||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./textaccessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
||||
|
||||
import { AnnotationLayer } from "./pdfjs";
|
||||
import { NullL10n } from "./l10n_utils.js";
|
||||
|
||||
|
|
@ -33,6 +42,7 @@ import { NullL10n } from "./l10n_utils.js";
|
|||
* [fieldObjectsPromise]
|
||||
* @property {Object} [mouseState]
|
||||
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap]
|
||||
* @property {TextAccessibilityManager} accessibilityManager
|
||||
*/
|
||||
|
||||
class AnnotationLayerBuilder {
|
||||
|
|
@ -53,6 +63,7 @@ class AnnotationLayerBuilder {
|
|||
fieldObjectsPromise = null,
|
||||
mouseState = null,
|
||||
annotationCanvasMap = null,
|
||||
accessibilityManager = null,
|
||||
}) {
|
||||
this.pageDiv = pageDiv;
|
||||
this.pdfPage = pdfPage;
|
||||
|
|
@ -67,6 +78,7 @@ class AnnotationLayerBuilder {
|
|||
this._fieldObjectsPromise = fieldObjectsPromise;
|
||||
this._mouseState = mouseState;
|
||||
this._annotationCanvasMap = annotationCanvasMap;
|
||||
this._accessibilityManager = accessibilityManager;
|
||||
|
||||
this.div = null;
|
||||
this._cancelled = false;
|
||||
|
|
@ -105,6 +117,7 @@ class AnnotationLayerBuilder {
|
|||
fieldObjects,
|
||||
mouseState: this._mouseState,
|
||||
annotationCanvasMap: this._annotationCanvasMap,
|
||||
accessibilityManager: this._accessibilityManager,
|
||||
};
|
||||
|
||||
if (this.div) {
|
||||
|
|
@ -116,7 +129,7 @@ class AnnotationLayerBuilder {
|
|||
// if there is at least one annotation.
|
||||
this.div = document.createElement("div");
|
||||
this.div.className = "annotationLayer";
|
||||
this.pageDiv.appendChild(this.div);
|
||||
this.pageDiv.append(this.div);
|
||||
parameters.div = this.div;
|
||||
|
||||
AnnotationLayer.render(parameters);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -17,28 +17,21 @@ import { Constants } from '../../constants'
|
|||
|
||||
const compatibilityParams = Object.create(null)
|
||||
if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
|
||||
const userAgent =
|
||||
(typeof navigator !== 'undefined' && navigator.userAgent) || ''
|
||||
const platform =
|
||||
(typeof navigator !== 'undefined' && navigator.platform) || ''
|
||||
const maxTouchPoints =
|
||||
(typeof navigator !== 'undefined' && navigator.maxTouchPoints) || 1
|
||||
if (
|
||||
typeof PDFJSDev !== 'undefined' &&
|
||||
PDFJSDev.test('LIB') &&
|
||||
typeof navigator === 'undefined'
|
||||
) {
|
||||
globalThis.navigator = Object.create(null)
|
||||
}
|
||||
const userAgent = navigator.userAgent || ''
|
||||
const platform = navigator.platform || ''
|
||||
const maxTouchPoints = navigator.maxTouchPoints || 1
|
||||
|
||||
const isAndroid = /Android/.test(userAgent)
|
||||
const isIOS =
|
||||
/\b(iPad|iPhone|iPod)(?=;)/.test(userAgent) ||
|
||||
(platform === 'MacIntel' && maxTouchPoints > 1)
|
||||
const isIOSChrome = /CriOS/.test(userAgent);
|
||||
|
||||
// Disables URL.createObjectURL() usage in some environments.
|
||||
// Support: Chrome on iOS
|
||||
(function checkOnBlobSupport () {
|
||||
// Sometimes Chrome on iOS loses data created with createObjectURL(),
|
||||
// see issue 8081.
|
||||
if (isIOSChrome) {
|
||||
compatibilityParams.disableCreateObjectURL = true
|
||||
}
|
||||
})();
|
||||
(platform === 'MacIntel' && maxTouchPoints > 1);
|
||||
|
||||
// Limit canvas size to 5 mega-pixels on mobile.
|
||||
// Support: Android, iOS
|
||||
|
|
@ -62,9 +55,14 @@ const OptionKind = {
|
|||
* primitive types and cannot rely on any imported types.
|
||||
*/
|
||||
const defaultOptions = {
|
||||
annotationEditorMode: {
|
||||
/** @type {number} */
|
||||
value: 0,
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
annotationMode: {
|
||||
/** @type {number} */
|
||||
value: 2, // https://github.com/siyuan-note/siyuan/issues/2975 DISABLE: 0, ENABLE: 1, ENABLE_FORMS: 2 (default), ENABLE_STORAGE: 3
|
||||
value: 2,
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
cursorToolOnLoad: {
|
||||
|
|
@ -72,11 +70,6 @@ const defaultOptions = {
|
|||
value: 0,
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
defaultUrl: {
|
||||
/** @type {string} */
|
||||
value: 'compressed.tracemonkey-pldi-09.pdf',
|
||||
kind: OptionKind.VIEWER,
|
||||
},
|
||||
defaultZoomValue: {
|
||||
/** @type {string} */
|
||||
value: '',
|
||||
|
|
@ -135,9 +128,23 @@ const defaultOptions = {
|
|||
maxCanvasPixels: {
|
||||
/** @type {number} */
|
||||
value: 16777216,
|
||||
compatibility: compatibilityParams.maxCanvasPixels,
|
||||
kind: OptionKind.VIEWER,
|
||||
},
|
||||
forcePageColors: {
|
||||
/** @type {boolean} */
|
||||
value: false,
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
pageColorsBackground: {
|
||||
/** @type {string} */
|
||||
value: 'Canvas',
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
pageColorsForeground: {
|
||||
/** @type {string} */
|
||||
value: 'CanvasText',
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
pdfBugEnabled: {
|
||||
/** @type {boolean} */
|
||||
value: typeof PDFJSDev === 'undefined' || !PDFJSDev.test('PRODUCTION'),
|
||||
|
|
@ -148,11 +155,6 @@ const defaultOptions = {
|
|||
value: 150,
|
||||
kind: OptionKind.VIEWER,
|
||||
},
|
||||
renderer: {
|
||||
/** @type {string} */
|
||||
value: 'canvas',
|
||||
kind: OptionKind.VIEWER,
|
||||
},
|
||||
sidebarViewOnLoad: {
|
||||
/** @type {number} */
|
||||
value: -1,
|
||||
|
|
@ -196,7 +198,10 @@ const defaultOptions = {
|
|||
},
|
||||
cMapUrl: {
|
||||
/** @type {string} */
|
||||
value: 'cmaps/',
|
||||
value:
|
||||
typeof PDFJSDev === 'undefined' || !PDFJSDev.test('PRODUCTION')
|
||||
? '../external/bcmaps/'
|
||||
: 'cmaps/', // NOTE
|
||||
kind: OptionKind.API,
|
||||
},
|
||||
disableAutoFetch: {
|
||||
|
|
@ -270,7 +275,8 @@ const defaultOptions = {
|
|||
},
|
||||
workerSrc: {
|
||||
/** @type {string} */
|
||||
value: `${Constants.PROTYLE_CDN}/js/pdf/pdf.worker.js?v=2.14.102`,
|
||||
// NOTE
|
||||
value: `${Constants.PROTYLE_CDN}/js/pdf/pdf.worker.js?v=3.0.150`,
|
||||
kind: OptionKind.WORKER,
|
||||
},
|
||||
}
|
||||
|
|
@ -278,6 +284,11 @@ if (
|
|||
typeof PDFJSDev === 'undefined' ||
|
||||
PDFJSDev.test('!PRODUCTION || GENERIC')
|
||||
) {
|
||||
defaultOptions.defaultUrl = {
|
||||
/** @type {string} */
|
||||
value: 'compressed.tracemonkey-pldi-09.pdf',
|
||||
kind: OptionKind.VIEWER,
|
||||
}
|
||||
defaultOptions.disablePreferences = {
|
||||
/** @type {boolean} */
|
||||
value: typeof PDFJSDev !== 'undefined' && PDFJSDev.test('TESTING'),
|
||||
|
|
@ -285,9 +296,14 @@ if (
|
|||
}
|
||||
defaultOptions.locale = {
|
||||
/** @type {string} */
|
||||
value: typeof navigator !== 'undefined' ? navigator.language : 'en-US',
|
||||
value: navigator.language || 'en-US',
|
||||
kind: OptionKind.VIEWER,
|
||||
}
|
||||
defaultOptions.renderer = {
|
||||
/** @type {string} */
|
||||
value: 'canvas',
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
}
|
||||
defaultOptions.sandboxBundleSrc = {
|
||||
/** @type {string} */
|
||||
value:
|
||||
|
|
@ -296,9 +312,12 @@ if (
|
|||
: '../build/pdf.sandbox.js',
|
||||
kind: OptionKind.VIEWER,
|
||||
}
|
||||
|
||||
defaultOptions.renderer.kind += OptionKind.PREFERENCE
|
||||
} else if (PDFJSDev.test('CHROME')) {
|
||||
defaultOptions.defaultUrl = {
|
||||
/** @type {string} */
|
||||
value: '',
|
||||
kind: OptionKind.VIEWER,
|
||||
}
|
||||
defaultOptions.disableTelemetry = {
|
||||
/** @type {boolean} */
|
||||
value: false,
|
||||
|
|
@ -325,7 +344,7 @@ class AppOptions {
|
|||
}
|
||||
const defaultOption = defaultOptions[name]
|
||||
if (defaultOption !== undefined) {
|
||||
return defaultOption.compatibility ?? defaultOption.value
|
||||
return compatibilityParams[name] ?? defaultOption.value
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
|
@ -357,7 +376,7 @@ class AppOptions {
|
|||
options[name] =
|
||||
userOption !== undefined
|
||||
? userOption
|
||||
: defaultOption.compatibility ?? defaultOption.value
|
||||
: compatibilityParams[name] ?? defaultOption.value
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { removeNullCharacters } from "./ui_utils";
|
||||
import { removeNullCharacters } from "./ui_utils.js";
|
||||
|
||||
const TREEITEM_OFFSET_TOP = -100; // px
|
||||
const TREEITEM_SELECTED_CLASS = "selected";
|
||||
|
|
@ -74,6 +74,7 @@ class BaseTreeViewer {
|
|||
*/
|
||||
_addToggleButton(div, hidden = false) {
|
||||
const toggler = document.createElement("div");
|
||||
// NOTE
|
||||
toggler.innerHTML = `<svg><use xlink:href="#iconDown"></use></svg>`
|
||||
toggler.className = "treeItemToggler";
|
||||
if (hidden) {
|
||||
|
|
@ -88,7 +89,7 @@ class BaseTreeViewer {
|
|||
this._toggleTreeItem(div, shouldShowAll);
|
||||
}
|
||||
};
|
||||
div.insertBefore(toggler, div.firstChild);
|
||||
div.prepend(toggler);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -123,7 +124,7 @@ class BaseTreeViewer {
|
|||
|
||||
this._lastToggleIsShow = !fragment.querySelector(".treeItemsHidden");
|
||||
}
|
||||
this.container.appendChild(fragment);
|
||||
this.container.append(fragment);
|
||||
|
||||
this._dispatchEvent(count);
|
||||
}
|
||||
|
|
@ -168,7 +169,7 @@ class BaseTreeViewer {
|
|||
|
||||
this.container.scrollTo(
|
||||
treeItem.offsetLeft,
|
||||
treeItem.offsetTop + TREEITEM_OFFSET_TOP + treeItem.offsetParent.offsetTop
|
||||
treeItem.offsetTop + TREEITEM_OFFSET_TOP
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -13,6 +13,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @typedef {import("./interfaces").IDownloadManager} IDownloadManager */
|
||||
|
||||
import { createValidAbsoluteUrl, isPdfFile } from "./pdfjs";
|
||||
|
||||
if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("CHROME || GENERIC")) {
|
||||
|
|
@ -36,7 +38,7 @@ function download(blobUrl, filename) {
|
|||
}
|
||||
// <a> must be in the document for recent Firefox versions,
|
||||
// otherwise .click() is ignored.
|
||||
(document.body || document.documentElement).appendChild(a);
|
||||
(document.body || document.documentElement).append(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
}
|
||||
|
|
@ -107,13 +109,7 @@ class DownloadManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sourceEventType {string} Used to signal what triggered the download.
|
||||
* The version of PDF.js integrated with Firefox uses this to to determine
|
||||
* which dialog to show. "save" triggers "save as" and "download" triggers
|
||||
* the "open with" dialog.
|
||||
*/
|
||||
download(blob, url, filename, sourceEventType = "download") {
|
||||
download(blob, url, filename) {
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
download(blobUrl, filename);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,29 +13,30 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BasePreferences } from './preferences.js'
|
||||
import { DownloadManager } from './download_manager.js'
|
||||
import { GenericScripting } from './generic_scripting.js'
|
||||
import { BasePreferences } from "./preferences.js";
|
||||
import { DownloadManager } from "./download_manager.js";
|
||||
import { GenericScripting } from "./generic_scripting.js";
|
||||
import { shadow } from './pdfjs'
|
||||
|
||||
if (typeof PDFJSDev !== 'undefined' && !PDFJSDev.test('GENERIC')) {
|
||||
if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("GENERIC")) {
|
||||
throw new Error(
|
||||
'Module "pdfjs-web/genericcom" shall not be used outside GENERIC build.',
|
||||
)
|
||||
'Module "pdfjs-web/genericcom" shall not be used outside GENERIC build.'
|
||||
);
|
||||
}
|
||||
|
||||
const GenericCom = {}
|
||||
const GenericCom = {};
|
||||
|
||||
class GenericPreferences extends BasePreferences {
|
||||
async _writeToStorage (prefObj) {
|
||||
localStorage.setItem('pdfjs.preferences', JSON.stringify(prefObj))
|
||||
async _writeToStorage(prefObj) {
|
||||
localStorage.setItem("pdfjs.preferences", JSON.stringify(prefObj));
|
||||
}
|
||||
|
||||
async _readFromStorage (prefObj) {
|
||||
return JSON.parse(localStorage.getItem('pdfjs.preferences'))
|
||||
async _readFromStorage(prefObj) {
|
||||
return JSON.parse(localStorage.getItem("pdfjs.preferences"));
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE
|
||||
class GenericExternalServices {
|
||||
constructor () {
|
||||
throw new Error('Cannot initialize DefaultExternalServices.')
|
||||
|
|
@ -70,17 +71,22 @@ class GenericExternalServices {
|
|||
return shadow(this, 'isInAutomation', false)
|
||||
}
|
||||
|
||||
static createDownloadManager (options) {
|
||||
return new DownloadManager()
|
||||
static createDownloadManager(options) {
|
||||
return new DownloadManager();
|
||||
}
|
||||
|
||||
static createPreferences () {
|
||||
return new GenericPreferences()
|
||||
static createPreferences() {
|
||||
return new GenericPreferences();
|
||||
}
|
||||
|
||||
static createScripting ({sandboxBundleSrc}) {
|
||||
return new GenericScripting(sandboxBundleSrc)
|
||||
static createL10n({ locale = "en-US" }) {
|
||||
// NOTE
|
||||
// return new GenericL10n(locale);
|
||||
}
|
||||
|
||||
static createScripting({ sandboxBundleSrc }) {
|
||||
return new GenericScripting(sandboxBundleSrc);
|
||||
}
|
||||
}
|
||||
|
||||
export { GenericCom, GenericExternalServices }
|
||||
// NOTE
|
||||
export { GenericCom, GenericExternalServices };
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ class GrabToPan {
|
|||
this.element.scrollLeft = scrollLeft;
|
||||
}
|
||||
if (!this.overlay.parentNode) {
|
||||
document.body.appendChild(this.overlay);
|
||||
document.body.append(this.overlay);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,11 @@ const DEFAULT_L10N_STRINGS = {
|
|||
printing_not_ready: "Warning: The PDF is not fully loaded for printing.",
|
||||
web_fonts_disabled:
|
||||
"Web fonts are disabled: unable to use embedded PDF fonts.",
|
||||
|
||||
free_text2_default_content: "Start typing…",
|
||||
editor_free_text2_aria_label: "Text Editor",
|
||||
editor_ink2_aria_label: "Draw Editor",
|
||||
editor_ink_canvas_aria_label: "User-created image",
|
||||
};
|
||||
|
||||
function getL10nFallback(key, args) {
|
||||
|
|
|
|||
|
|
@ -14,123 +14,103 @@
|
|||
*/
|
||||
|
||||
class OverlayManager {
|
||||
#overlays = Object.create(null);
|
||||
#overlays = new WeakMap();
|
||||
|
||||
#active = null;
|
||||
|
||||
#keyDownBound = null;
|
||||
|
||||
get active() {
|
||||
return this.#active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - The name of the overlay that is registered.
|
||||
* @param {HTMLDivElement} element - The overlay's DOM element.
|
||||
* @param {function} [callerCloseMethod] - The method that, if present, calls
|
||||
* `OverlayManager.close` from the object registering the
|
||||
* overlay. Access to this method is necessary in order to
|
||||
* run cleanup code when e.g. the overlay is force closed.
|
||||
* The default is `null`.
|
||||
* @param {HTMLDialogElement} dialog - The overlay's DOM element.
|
||||
* @param {boolean} [canForceClose] - Indicates if opening the overlay closes
|
||||
* an active overlay. The default is `false`.
|
||||
* @returns {Promise} A promise that is resolved when the overlay has been
|
||||
* registered.
|
||||
*/
|
||||
async register(
|
||||
name,
|
||||
element,
|
||||
callerCloseMethod = null,
|
||||
canForceClose = false
|
||||
) {
|
||||
let container;
|
||||
if (!name || !element || !(container = element.parentNode)) {
|
||||
async register(dialog, canForceClose = false) {
|
||||
if (typeof dialog !== "object") {
|
||||
throw new Error("Not enough parameters.");
|
||||
} else if (this.#overlays[name]) {
|
||||
} else if (this.#overlays.has(dialog)) {
|
||||
throw new Error("The overlay is already registered.");
|
||||
}
|
||||
this.#overlays[name] = {
|
||||
element,
|
||||
container,
|
||||
callerCloseMethod,
|
||||
canForceClose,
|
||||
};
|
||||
this.#overlays.set(dialog, { canForceClose });
|
||||
|
||||
// NOTE
|
||||
// if (
|
||||
// typeof PDFJSDev !== "undefined" &&
|
||||
// PDFJSDev.test("GENERIC && !SKIP_BABEL") &&
|
||||
// !dialog.showModal
|
||||
// ) {
|
||||
// const dialogPolyfill = require("dialog-polyfill/dist/dialog-polyfill.js");
|
||||
// dialogPolyfill.registerDialog(dialog);
|
||||
//
|
||||
// if (!this._dialogPolyfillCSS) {
|
||||
// this._dialogPolyfillCSS = true;
|
||||
//
|
||||
// const style = document.createElement("style");
|
||||
// style.textContent = PDFJSDev.eval("DIALOG_POLYFILL_CSS");
|
||||
//
|
||||
// document.head.prepend(style);
|
||||
// }
|
||||
// }
|
||||
|
||||
dialog.addEventListener("cancel", evt => {
|
||||
this.#active = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - The name of the overlay that is unregistered.
|
||||
* @param {HTMLDialogElement} dialog - The overlay's DOM element.
|
||||
* @returns {Promise} A promise that is resolved when the overlay has been
|
||||
* unregistered.
|
||||
*/
|
||||
async unregister(name) {
|
||||
if (!this.#overlays[name]) {
|
||||
async unregister(dialog) {
|
||||
if (!this.#overlays.has(dialog)) {
|
||||
throw new Error("The overlay does not exist.");
|
||||
} else if (this.#active === name) {
|
||||
} else if (this.#active === dialog) {
|
||||
throw new Error("The overlay cannot be removed while it is active.");
|
||||
}
|
||||
delete this.#overlays[name];
|
||||
this.#overlays.delete(dialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - The name of the overlay that should be opened.
|
||||
* @param {HTMLDialogElement} dialog - The overlay's DOM element.
|
||||
* @returns {Promise} A promise that is resolved when the overlay has been
|
||||
* opened.
|
||||
*/
|
||||
async open(name) {
|
||||
if (!this.#overlays[name]) {
|
||||
async open(dialog) {
|
||||
if (!this.#overlays.has(dialog)) {
|
||||
throw new Error("The overlay does not exist.");
|
||||
} else if (this.#active) {
|
||||
if (this.#active === name) {
|
||||
if (this.#active === dialog) {
|
||||
throw new Error("The overlay is already active.");
|
||||
} else if (this.#overlays[name].canForceClose) {
|
||||
this.#closeThroughCaller();
|
||||
} else if (this.#overlays.get(dialog).canForceClose) {
|
||||
await this.close();
|
||||
} else {
|
||||
throw new Error("Another overlay is currently active.");
|
||||
}
|
||||
}
|
||||
this.#active = name;
|
||||
this.#overlays[this.#active].element.classList.remove("fn__hidden");
|
||||
this.#overlays[this.#active].container.classList.remove("fn__hidden");
|
||||
|
||||
this.#keyDownBound = this.#keyDown.bind(this);
|
||||
window.addEventListener("keydown", this.#keyDownBound);
|
||||
this.#active = dialog;
|
||||
dialog.showModal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - The name of the overlay that should be closed.
|
||||
* @param {HTMLDialogElement} dialog - The overlay's DOM element.
|
||||
* @returns {Promise} A promise that is resolved when the overlay has been
|
||||
* closed.
|
||||
*/
|
||||
async close(name) {
|
||||
if (!this.#overlays[name]) {
|
||||
async close(dialog = this.#active) {
|
||||
if (!this.#overlays.has(dialog)) {
|
||||
throw new Error("The overlay does not exist.");
|
||||
} else if (!this.#active) {
|
||||
throw new Error("The overlay is currently not active.");
|
||||
} else if (this.#active !== name) {
|
||||
} else if (this.#active !== dialog) {
|
||||
throw new Error("Another overlay is currently active.");
|
||||
}
|
||||
this.#overlays[this.#active].container.classList.add("fn__hidden");
|
||||
this.#overlays[this.#active].element.classList.add("fn__hidden");
|
||||
dialog.close();
|
||||
this.#active = null;
|
||||
|
||||
window.removeEventListener("keydown", this.#keyDownBound);
|
||||
this.#keyDownBound = null;
|
||||
}
|
||||
|
||||
#keyDown(evt) {
|
||||
if (this.#active && evt.keyCode === /* Esc = */ 27) {
|
||||
this.#closeThroughCaller();
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
#closeThroughCaller() {
|
||||
if (this.#overlays[this.#active].callerCloseMethod) {
|
||||
this.#overlays[this.#active].callerCloseMethod();
|
||||
}
|
||||
if (this.#active) {
|
||||
this.close(this.#active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,12 +13,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PasswordResponses } from "./pdfjs";
|
||||
import { createPromiseCapability, PasswordResponses } from "./pdfjs";
|
||||
|
||||
/**
|
||||
* @typedef {Object} PasswordPromptOptions
|
||||
* @property {string} overlayName - Name of the overlay for the overlay manager.
|
||||
* @property {HTMLDivElement} container - Div container for the overlay.
|
||||
* @property {HTMLDialogElement} dialog - The overlay's DOM element.
|
||||
* @property {HTMLParagraphElement} label - Label containing instructions for
|
||||
* entering the password.
|
||||
* @property {HTMLInputElement} input - Input field for entering the password.
|
||||
|
|
@ -29,6 +28,12 @@ import { PasswordResponses } from "./pdfjs";
|
|||
*/
|
||||
|
||||
class PasswordPrompt {
|
||||
#activeCapability = null;
|
||||
|
||||
#updateCallback = null;
|
||||
|
||||
#reason = null;
|
||||
|
||||
/**
|
||||
* @param {PasswordPromptOptions} options
|
||||
* @param {OverlayManager} overlayManager - Manager for the viewer overlays.
|
||||
|
|
@ -37,8 +42,7 @@ class PasswordPrompt {
|
|||
* an <iframe> or an <object>. The default value is `false`.
|
||||
*/
|
||||
constructor(options, overlayManager, l10n, isViewerEmbedded = false) {
|
||||
this.overlayName = options.overlayName;
|
||||
this.container = options.container;
|
||||
this.dialog = options.dialog;
|
||||
this.label = options.label;
|
||||
this.input = options.input;
|
||||
this.submitButton = options.submitButton;
|
||||
|
|
@ -47,61 +51,80 @@ class PasswordPrompt {
|
|||
this.l10n = l10n;
|
||||
this._isViewerEmbedded = isViewerEmbedded;
|
||||
|
||||
this.updateCallback = null;
|
||||
this.reason = null;
|
||||
|
||||
// Attach the event listeners.
|
||||
this.submitButton.addEventListener("click", this.#verify.bind(this));
|
||||
this.cancelButton.addEventListener("click", this.#cancel.bind(this));
|
||||
this.cancelButton.addEventListener("click", this.close.bind(this));
|
||||
this.input.addEventListener("keydown", e => {
|
||||
if (e.keyCode === /* Enter = */ 13) {
|
||||
this.#verify();
|
||||
}
|
||||
});
|
||||
|
||||
this.overlayManager.register(
|
||||
this.overlayName,
|
||||
this.container,
|
||||
this.#cancel.bind(this),
|
||||
true
|
||||
);
|
||||
this.overlayManager.register(this.dialog, /* canForceClose = */ true);
|
||||
|
||||
this.dialog.addEventListener("close", this.#cancel.bind(this));
|
||||
}
|
||||
|
||||
async open() {
|
||||
await this.overlayManager.open(this.overlayName);
|
||||
if (this.#activeCapability) {
|
||||
await this.#activeCapability.promise;
|
||||
}
|
||||
this.#activeCapability = createPromiseCapability();
|
||||
|
||||
try {
|
||||
await this.overlayManager.open(this.dialog);
|
||||
} catch (ex) {
|
||||
this.#activeCapability = null;
|
||||
throw ex;
|
||||
}
|
||||
|
||||
const passwordIncorrect =
|
||||
this.reason === PasswordResponses.INCORRECT_PASSWORD;
|
||||
this.#reason === PasswordResponses.INCORRECT_PASSWORD;
|
||||
|
||||
if (!this._isViewerEmbedded || passwordIncorrect) {
|
||||
this.input.focus();
|
||||
}
|
||||
// NOTE
|
||||
this.label.textContent = window.siyuan.languages[`password_${passwordIncorrect
|
||||
? 'invalid'
|
||||
: 'label'}`]
|
||||
}
|
||||
|
||||
async close() {
|
||||
await this.overlayManager.close(this.overlayName);
|
||||
this.input.value = "";
|
||||
if (this.overlayManager.active === this.dialog) {
|
||||
this.overlayManager.close(this.dialog);
|
||||
}
|
||||
}
|
||||
|
||||
#verify() {
|
||||
const password = this.input.value;
|
||||
if (password?.length > 0) {
|
||||
this.close();
|
||||
this.updateCallback(password);
|
||||
this.#invokeCallback(password);
|
||||
}
|
||||
}
|
||||
|
||||
#cancel() {
|
||||
this.close();
|
||||
this.updateCallback(new Error("PasswordPrompt cancelled."));
|
||||
this.#invokeCallback(new Error("PasswordPrompt cancelled."));
|
||||
this.#activeCapability.resolve();
|
||||
}
|
||||
|
||||
setUpdateCallback(updateCallback, reason) {
|
||||
this.updateCallback = updateCallback;
|
||||
this.reason = reason;
|
||||
#invokeCallback(password) {
|
||||
if (!this.#updateCallback) {
|
||||
return; // Ensure that the callback is only invoked once.
|
||||
}
|
||||
this.close();
|
||||
this.input.value = "";
|
||||
|
||||
this.#updateCallback(password);
|
||||
this.#updateCallback = null;
|
||||
}
|
||||
|
||||
async setUpdateCallback(updateCallback, reason) {
|
||||
if (this.#activeCapability) {
|
||||
await this.#activeCapability.promise;
|
||||
}
|
||||
this.#updateCallback = updateCallback;
|
||||
this.#reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
import { createPromiseCapability, getFilenameFromUrl } from "./pdfjs";
|
||||
import { BaseTreeViewer } from "./base_tree_viewer.js";
|
||||
import { waitOnEventOrTimeout } from "./event_utils.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} PDFAttachmentViewerOptions
|
||||
|
|
@ -38,7 +39,7 @@ class PDFAttachmentViewer extends BaseTreeViewer {
|
|||
|
||||
this.eventBus._on(
|
||||
"fileattachmentannotation",
|
||||
this._appendAttachment.bind(this)
|
||||
this.#appendAttachment.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -51,36 +52,33 @@ class PDFAttachmentViewer extends BaseTreeViewer {
|
|||
// replaced is when appending FileAttachment annotations.
|
||||
this._renderedCapability = createPromiseCapability();
|
||||
}
|
||||
if (this._pendingDispatchEvent) {
|
||||
clearTimeout(this._pendingDispatchEvent);
|
||||
}
|
||||
this._pendingDispatchEvent = null;
|
||||
this._pendingDispatchEvent = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_dispatchEvent(attachmentsCount) {
|
||||
async _dispatchEvent(attachmentsCount) {
|
||||
this._renderedCapability.resolve();
|
||||
|
||||
if (this._pendingDispatchEvent) {
|
||||
clearTimeout(this._pendingDispatchEvent);
|
||||
this._pendingDispatchEvent = null;
|
||||
}
|
||||
if (attachmentsCount === 0) {
|
||||
if (attachmentsCount === 0 && !this._pendingDispatchEvent) {
|
||||
// Delay the event when no "regular" attachments exist, to allow time for
|
||||
// parsing of any FileAttachment annotations that may be present on the
|
||||
// *initially* rendered page; this reduces the likelihood of temporarily
|
||||
// disabling the attachmentsView when the `PDFSidebar` handles the event.
|
||||
this._pendingDispatchEvent = setTimeout(() => {
|
||||
this.eventBus.dispatch("attachmentsloaded", {
|
||||
source: this,
|
||||
attachmentsCount: 0,
|
||||
});
|
||||
this._pendingDispatchEvent = null;
|
||||
this._pendingDispatchEvent = true;
|
||||
|
||||
await waitOnEventOrTimeout({
|
||||
target: this.eventBus,
|
||||
name: "annotationlayerrendered",
|
||||
delay: 1000,
|
||||
});
|
||||
return;
|
||||
|
||||
if (!this._pendingDispatchEvent) {
|
||||
return; // There was already another `_dispatchEvent`-call`.
|
||||
}
|
||||
}
|
||||
this._pendingDispatchEvent = false;
|
||||
|
||||
this.eventBus.dispatch("attachmentsloaded", {
|
||||
source: this,
|
||||
|
|
@ -129,9 +127,9 @@ class PDFAttachmentViewer extends BaseTreeViewer {
|
|||
this._bindLink(element, { content, filename });
|
||||
element.textContent = this._normalizeTextContent(filename);
|
||||
|
||||
div.appendChild(element);
|
||||
div.append(element);
|
||||
|
||||
fragment.appendChild(div);
|
||||
fragment.append(div);
|
||||
attachmentsCount++;
|
||||
}
|
||||
|
||||
|
|
@ -140,27 +138,22 @@ class PDFAttachmentViewer extends BaseTreeViewer {
|
|||
|
||||
/**
|
||||
* Used to append FileAttachment annotations to the sidebar.
|
||||
* @private
|
||||
*/
|
||||
_appendAttachment({ id, filename, content }) {
|
||||
#appendAttachment({ filename, content }) {
|
||||
const renderedPromise = this._renderedCapability.promise;
|
||||
|
||||
renderedPromise.then(() => {
|
||||
if (renderedPromise !== this._renderedCapability.promise) {
|
||||
return; // The FileAttachment annotation belongs to a previous document.
|
||||
}
|
||||
let attachments = this._attachments;
|
||||
const attachments = this._attachments || Object.create(null);
|
||||
|
||||
if (!attachments) {
|
||||
attachments = Object.create(null);
|
||||
} else {
|
||||
for (const name in attachments) {
|
||||
if (id === name) {
|
||||
return; // Ignore the new attachment if it already exists.
|
||||
}
|
||||
for (const name in attachments) {
|
||||
if (filename === name) {
|
||||
return; // Ignore the new attachment if it already exists.
|
||||
}
|
||||
}
|
||||
attachments[id] = {
|
||||
attachments[filename] = {
|
||||
filename,
|
||||
content,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AnnotationEditorType } from "./pdfjs";
|
||||
import { GrabToPan } from "./grab_to_pan.js";
|
||||
import { PresentationModeState } from "./ui_utils.js";
|
||||
|
||||
|
|
@ -40,7 +41,7 @@ class PDFCursorTools {
|
|||
this.eventBus = eventBus;
|
||||
|
||||
this.active = CursorTool.SELECT;
|
||||
this.activeBeforePresentationMode = null;
|
||||
this.previouslyActive = null;
|
||||
|
||||
this.handTool = new GrabToPan({
|
||||
element: this.container,
|
||||
|
|
@ -63,13 +64,13 @@ class PDFCursorTools {
|
|||
}
|
||||
|
||||
/**
|
||||
* NOTE: This method is ignored while Presentation Mode is active.
|
||||
* @param {number} tool - The cursor mode that should be switched to,
|
||||
* must be one of the values in {CursorTool}.
|
||||
*/
|
||||
switchTool(tool) {
|
||||
if (this.activeBeforePresentationMode !== null) {
|
||||
return; // Cursor tools cannot be used in Presentation Mode.
|
||||
if (this.previouslyActive !== null) {
|
||||
// Cursor tools cannot be used in PresentationMode/AnnotationEditor.
|
||||
return;
|
||||
}
|
||||
if (tool === this.active) {
|
||||
return; // The requested tool is already active.
|
||||
|
|
@ -121,22 +122,54 @@ class PDFCursorTools {
|
|||
this.switchTool(evt.tool);
|
||||
});
|
||||
|
||||
this.eventBus._on("presentationmodechanged", evt => {
|
||||
switch (evt.state) {
|
||||
case PresentationModeState.FULLSCREEN: {
|
||||
const previouslyActive = this.active;
|
||||
let annotationEditorMode = AnnotationEditorType.NONE,
|
||||
presentationModeState = PresentationModeState.NORMAL;
|
||||
|
||||
this.switchTool(CursorTool.SELECT);
|
||||
this.activeBeforePresentationMode = previouslyActive;
|
||||
break;
|
||||
}
|
||||
case PresentationModeState.NORMAL: {
|
||||
const previouslyActive = this.activeBeforePresentationMode;
|
||||
const disableActive = () => {
|
||||
const previouslyActive = this.active;
|
||||
|
||||
this.activeBeforePresentationMode = null;
|
||||
this.switchTool(previouslyActive);
|
||||
break;
|
||||
}
|
||||
this.switchTool(CursorTool.SELECT);
|
||||
this.previouslyActive ??= previouslyActive; // Keep track of the first one.
|
||||
};
|
||||
const enableActive = () => {
|
||||
const previouslyActive = this.previouslyActive;
|
||||
|
||||
if (
|
||||
previouslyActive !== null &&
|
||||
annotationEditorMode === AnnotationEditorType.NONE &&
|
||||
presentationModeState === PresentationModeState.NORMAL
|
||||
) {
|
||||
this.previouslyActive = null;
|
||||
this.switchTool(previouslyActive);
|
||||
}
|
||||
};
|
||||
|
||||
this.eventBus._on("secondarytoolbarreset", evt => {
|
||||
if (this.previouslyActive !== null) {
|
||||
annotationEditorMode = AnnotationEditorType.NONE;
|
||||
presentationModeState = PresentationModeState.NORMAL;
|
||||
|
||||
enableActive();
|
||||
}
|
||||
});
|
||||
|
||||
this.eventBus._on("annotationeditormodechanged", ({ mode }) => {
|
||||
annotationEditorMode = mode;
|
||||
|
||||
if (mode === AnnotationEditorType.NONE) {
|
||||
enableActive();
|
||||
} else {
|
||||
disableActive();
|
||||
}
|
||||
});
|
||||
|
||||
this.eventBus._on("presentationmodechanged", ({ state }) => {
|
||||
presentationModeState = state;
|
||||
|
||||
if (state === PresentationModeState.NORMAL) {
|
||||
enableActive();
|
||||
} else if (state === PresentationModeState.FULLSCREEN) {
|
||||
disableActive();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
createPromiseCapability,
|
||||
getPdfFilenameFromUrl,
|
||||
PDFDateString,
|
||||
} from "./pdfjs";
|
||||
import { createPromiseCapability, PDFDateString } from "./pdfjs";
|
||||
import { getPageSizeInches, isPortraitOrientation } from "./ui_utils.js";
|
||||
|
||||
const DEFAULT_FIELD_CONTENT = "-";
|
||||
|
|
@ -45,9 +41,8 @@ function getPageName(size, isPortrait, pageNames) {
|
|||
|
||||
/**
|
||||
* @typedef {Object} PDFDocumentPropertiesOptions
|
||||
* @property {string} overlayName - Name/identifier for the overlay.
|
||||
* @property {HTMLDialogElement} dialog - The overlay's DOM element.
|
||||
* @property {Object} fields - Names and elements of the overlay's fields.
|
||||
* @property {HTMLDivElement} container - Div container for the overlay.
|
||||
* @property {HTMLButtonElement} closeButton - Button for closing the overlay.
|
||||
*/
|
||||
|
||||
|
|
@ -59,28 +54,27 @@ class PDFDocumentProperties {
|
|||
* @param {OverlayManager} overlayManager - Manager for the viewer overlays.
|
||||
* @param {EventBus} eventBus - The application event bus.
|
||||
* @param {IL10n} l10n - Localization service.
|
||||
* @param {function} fileNameLookup - The function that is used to lookup
|
||||
* the document fileName.
|
||||
*/
|
||||
constructor(
|
||||
{ overlayName, fields, container, closeButton },
|
||||
{ dialog, fields, closeButton },
|
||||
overlayManager,
|
||||
eventBus,
|
||||
l10n
|
||||
l10n,
|
||||
fileNameLookup
|
||||
) {
|
||||
this.overlayName = overlayName;
|
||||
this.dialog = dialog;
|
||||
this.fields = fields;
|
||||
this.container = container;
|
||||
this.overlayManager = overlayManager;
|
||||
this.l10n = l10n;
|
||||
this._fileNameLookup = fileNameLookup;
|
||||
|
||||
this.#reset();
|
||||
// Bind the event listener for the Close button.
|
||||
closeButton.addEventListener("click", this.close.bind(this));
|
||||
|
||||
this.overlayManager.register(
|
||||
this.overlayName,
|
||||
this.container,
|
||||
this.close.bind(this)
|
||||
);
|
||||
this.overlayManager.register(this.dialog);
|
||||
|
||||
eventBus._on("pagechanging", evt => {
|
||||
this._currentPageNumber = evt.pageNumber;
|
||||
|
|
@ -90,6 +84,10 @@ class PDFDocumentProperties {
|
|||
});
|
||||
|
||||
this._isNonMetricLocale = true; // The default viewer locale is 'en-us'.
|
||||
// NOTE
|
||||
// l10n.getLanguage().then(locale => {
|
||||
// this._isNonMetricLocale = NON_METRIC_LOCALES.includes(locale);
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -97,7 +95,7 @@ class PDFDocumentProperties {
|
|||
*/
|
||||
async open() {
|
||||
await Promise.all([
|
||||
this.overlayManager.open(this.overlayName),
|
||||
this.overlayManager.open(this.dialog),
|
||||
this._dataAvailableCapability.promise,
|
||||
]);
|
||||
const currentPageNumber = this._currentPageNumber;
|
||||
|
|
@ -118,7 +116,7 @@ class PDFDocumentProperties {
|
|||
const {
|
||||
info,
|
||||
/* metadata, */
|
||||
contentDispositionFilename,
|
||||
/* contentDispositionFilename, */
|
||||
contentLength,
|
||||
} = await this.pdfDocument.getMetadata();
|
||||
|
||||
|
|
@ -130,7 +128,7 @@ class PDFDocumentProperties {
|
|||
pageSize,
|
||||
isLinearized,
|
||||
] = await Promise.all([
|
||||
contentDispositionFilename || getPdfFilenameFromUrl(this.url),
|
||||
this._fileNameLookup(),
|
||||
this.#parseFileSize(contentLength),
|
||||
this.#parseDate(info.CreationDate),
|
||||
this.#parseDate(info.ModDate),
|
||||
|
|
@ -176,20 +174,18 @@ class PDFDocumentProperties {
|
|||
/**
|
||||
* Close the document properties overlay.
|
||||
*/
|
||||
close() {
|
||||
this.overlayManager.close(this.overlayName);
|
||||
async close() {
|
||||
this.overlayManager.close(this.dialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a reference to the PDF document and the URL in order
|
||||
* to populate the overlay fields with the document properties.
|
||||
* Note that the overlay will contain no information if this method
|
||||
* is not called.
|
||||
* Set a reference to the PDF document in order to populate the dialog fields
|
||||
* with the document properties. Note that the dialog will contain no
|
||||
* information if this method is not called.
|
||||
*
|
||||
* @param {PDFDocumentProxy} pdfDocument - A reference to the PDF document.
|
||||
* @param {string} url - The URL of the document.
|
||||
*/
|
||||
setDocument(pdfDocument, url = null) {
|
||||
setDocument(pdfDocument) {
|
||||
if (this.pdfDocument) {
|
||||
this.#reset();
|
||||
this.#updateUI(true);
|
||||
|
|
@ -198,14 +194,12 @@ class PDFDocumentProperties {
|
|||
return;
|
||||
}
|
||||
this.pdfDocument = pdfDocument;
|
||||
this.url = url;
|
||||
|
||||
this._dataAvailableCapability.resolve();
|
||||
}
|
||||
|
||||
#reset() {
|
||||
this.pdfDocument = null;
|
||||
this.url = null;
|
||||
|
||||
this.#fieldData = null;
|
||||
this._dataAvailableCapability = createPromiseCapability();
|
||||
|
|
@ -225,7 +219,7 @@ class PDFDocumentProperties {
|
|||
}
|
||||
return;
|
||||
}
|
||||
if (this.overlayManager.active !== this.overlayName) {
|
||||
if (this.overlayManager.active !== this.dialog) {
|
||||
// Don't bother updating the dialog if has already been closed,
|
||||
// since it will be updated the next time `this.open` is called.
|
||||
return;
|
||||
|
|
@ -243,6 +237,7 @@ class PDFDocumentProperties {
|
|||
if (!kb) {
|
||||
return undefined;
|
||||
}
|
||||
// NOTE
|
||||
if (mb >= 1) {
|
||||
return `${mb >= 1 && (+mb.toPrecision(
|
||||
3)).toLocaleString()} MB ${fileSize.toLocaleString()} bytes)`
|
||||
|
|
@ -315,6 +310,7 @@ class PDFDocumentProperties {
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE
|
||||
const [{ width, height }, unit, name, orientation] = await Promise.all([
|
||||
this._isNonMetricLocale ? sizeInches : sizeMillimeters,
|
||||
this._isNonMetricLocale
|
||||
|
|
@ -337,10 +333,12 @@ class PDFDocumentProperties {
|
|||
if (!dateObject) {
|
||||
return undefined;
|
||||
}
|
||||
// NOTE
|
||||
return `${dateObject.toLocaleDateString()}, ${dateObject.toLocaleTimeString()}`
|
||||
}
|
||||
|
||||
#parseLinearization(isLinearized) {
|
||||
// NOTE
|
||||
return isLinearized ? 'Yes' : 'No'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FindState } from './pdf_find_controller.js'
|
||||
import { FindState } from "./pdf_find_controller.js";
|
||||
|
||||
const MATCHES_COUNT_LIMIT = 1000
|
||||
const MATCHES_COUNT_LIMIT = 1000;
|
||||
|
||||
/**
|
||||
* Creates a "search bar" given a set of DOM elements that act as controls
|
||||
|
|
@ -24,99 +24,102 @@ const MATCHES_COUNT_LIMIT = 1000
|
|||
* is done by PDFFindController.
|
||||
*/
|
||||
class PDFFindBar {
|
||||
constructor (options, eventBus, l10n) {
|
||||
this.opened = false
|
||||
constructor(options, eventBus, l10n) {
|
||||
this.opened = false;
|
||||
|
||||
this.bar = options.bar
|
||||
this.toggleButton = options.toggleButton
|
||||
this.findField = options.findField
|
||||
this.highlightAll = options.highlightAllCheckbox
|
||||
this.caseSensitive = options.caseSensitiveCheckbox
|
||||
this.matchDiacritics = options.matchDiacriticsCheckbox
|
||||
this.entireWord = options.entireWordCheckbox
|
||||
this.findMsg = options.findMsg
|
||||
this.findResultsCount = options.findResultsCount
|
||||
this.findPreviousButton = options.findPreviousButton
|
||||
this.findNextButton = options.findNextButton
|
||||
this.eventBus = eventBus
|
||||
this.l10n = l10n
|
||||
this.bar = options.bar;
|
||||
this.toggleButton = options.toggleButton;
|
||||
this.findField = options.findField;
|
||||
this.highlightAll = options.highlightAllCheckbox;
|
||||
this.caseSensitive = options.caseSensitiveCheckbox;
|
||||
this.matchDiacritics = options.matchDiacriticsCheckbox;
|
||||
this.entireWord = options.entireWordCheckbox;
|
||||
this.findMsg = options.findMsg;
|
||||
this.findResultsCount = options.findResultsCount;
|
||||
this.findPreviousButton = options.findPreviousButton;
|
||||
this.findNextButton = options.findNextButton;
|
||||
this.eventBus = eventBus;
|
||||
this.l10n = l10n;
|
||||
|
||||
// Add event listeners to the DOM elements.
|
||||
this.toggleButton.addEventListener('click', () => {
|
||||
this.toggle()
|
||||
})
|
||||
this.toggleButton.addEventListener("click", () => {
|
||||
this.toggle();
|
||||
});
|
||||
|
||||
this.findField.addEventListener('input', () => {
|
||||
this.dispatchEvent('')
|
||||
})
|
||||
this.findField.addEventListener("input", () => {
|
||||
this.dispatchEvent("");
|
||||
});
|
||||
|
||||
this.bar.addEventListener('keydown', e => {
|
||||
this.bar.addEventListener("keydown", e => {
|
||||
switch (e.keyCode) {
|
||||
case 13: // Enter
|
||||
if (e.target === this.findField) {
|
||||
this.dispatchEvent('again', e.shiftKey)
|
||||
this.dispatchEvent("again", e.shiftKey);
|
||||
}
|
||||
break
|
||||
break;
|
||||
case 27: // Escape
|
||||
this.close()
|
||||
break
|
||||
this.close();
|
||||
break;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.findPreviousButton.addEventListener('click', () => {
|
||||
this.dispatchEvent('again', true)
|
||||
})
|
||||
this.findPreviousButton.addEventListener("click", () => {
|
||||
this.dispatchEvent("again", true);
|
||||
});
|
||||
|
||||
this.findNextButton.addEventListener('click', () => {
|
||||
this.dispatchEvent('again', false)
|
||||
})
|
||||
this.findNextButton.addEventListener("click", () => {
|
||||
this.dispatchEvent("again", false);
|
||||
});
|
||||
|
||||
this.highlightAll.addEventListener('click', () => {
|
||||
this.dispatchEvent('highlightallchange')
|
||||
// NOTE: 以下三个相同 https://github.com/siyuan-note/siyuan/issues/5338
|
||||
this.highlightAll.addEventListener("click", () => {
|
||||
this.dispatchEvent("highlightallchange");
|
||||
// NOTE
|
||||
if (this.highlightAll.checked) {
|
||||
this.highlightAll.parentElement.classList.remove("b3-button--outline")
|
||||
} else {
|
||||
this.highlightAll.parentElement.classList.add("b3-button--outline")
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.caseSensitive.addEventListener('click', () => {
|
||||
this.dispatchEvent('casesensitivitychange')
|
||||
this.caseSensitive.addEventListener("click", () => {
|
||||
this.dispatchEvent("casesensitivitychange");
|
||||
// NOTE
|
||||
if (this.caseSensitive.checked) {
|
||||
this.caseSensitive.parentElement.classList.remove("b3-button--outline")
|
||||
} else {
|
||||
this.caseSensitive.parentElement.classList.add("b3-button--outline")
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.entireWord.addEventListener('click', () => {
|
||||
this.dispatchEvent('entirewordchange')
|
||||
this.entireWord.addEventListener("click", () => {
|
||||
this.dispatchEvent("entirewordchange");
|
||||
// NOTE
|
||||
if (this.entireWord.checked) {
|
||||
this.entireWord.parentElement.classList.remove("b3-button--outline")
|
||||
} else {
|
||||
this.entireWord.parentElement.classList.add("b3-button--outline")
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.matchDiacritics.addEventListener('click', () => {
|
||||
this.dispatchEvent('diacriticmatchingchange')
|
||||
this.matchDiacritics.addEventListener("click", () => {
|
||||
this.dispatchEvent("diacriticmatchingchange");
|
||||
// NOTE
|
||||
if (this.matchDiacritics.checked) {
|
||||
this.matchDiacritics.parentElement.classList.remove("b3-button--outline")
|
||||
} else {
|
||||
this.matchDiacritics.parentElement.classList.add("b3-button--outline")
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.eventBus._on('resize', this.#adjustWidth.bind(this))
|
||||
this.eventBus._on("resize", this.#adjustWidth.bind(this));
|
||||
}
|
||||
|
||||
reset () {
|
||||
this.updateUIState()
|
||||
reset() {
|
||||
this.updateUIState();
|
||||
}
|
||||
|
||||
dispatchEvent (type, findPrev = false) {
|
||||
this.eventBus.dispatch('find', {
|
||||
dispatchEvent(type, findPrev = false) {
|
||||
this.eventBus.dispatch("find", {
|
||||
source: this,
|
||||
type,
|
||||
query: this.findField.value,
|
||||
|
|
@ -126,108 +129,115 @@ class PDFFindBar {
|
|||
highlightAll: this.highlightAll.checked,
|
||||
findPrevious: findPrev,
|
||||
matchDiacritics: this.matchDiacritics.checked,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
updateUIState (state, previous, matchesCount) {
|
||||
let findMsg = ''
|
||||
let status = ''
|
||||
updateUIState(state, previous, matchesCount) {
|
||||
// NOTE
|
||||
let findMsg = "";
|
||||
let status = "";
|
||||
|
||||
switch (state) {
|
||||
case FindState.FOUND:
|
||||
break
|
||||
break;
|
||||
case FindState.PENDING:
|
||||
status = 'pending'
|
||||
break
|
||||
status = "pending";
|
||||
break;
|
||||
case FindState.NOT_FOUND:
|
||||
// NOTE
|
||||
findMsg = window.siyuan.languages.find_not_found
|
||||
status = 'notFound'
|
||||
break
|
||||
status = "notFound";
|
||||
break;
|
||||
case FindState.WRAPPED:
|
||||
findMsg = window.siyuan.languages.find_not_found[`find_reached_${previous
|
||||
? 'top'
|
||||
: 'bottom'}`]
|
||||
break
|
||||
break;
|
||||
}
|
||||
this.findField.setAttribute('data-status', status)
|
||||
this.findField.setAttribute("data-status", status);
|
||||
this.findField.setAttribute("aria-invalid", state === FindState.NOT_FOUND);
|
||||
|
||||
// NOTE
|
||||
this.findMsg.textContent = findMsg
|
||||
this.#adjustWidth()
|
||||
this.updateResultsCount(matchesCount)
|
||||
this.updateResultsCount(matchesCount);
|
||||
}
|
||||
|
||||
updateResultsCount ({current = 0, total = 0} = {}) {
|
||||
const limit = MATCHES_COUNT_LIMIT
|
||||
let msg = ''
|
||||
updateResultsCount({ current = 0, total = 0 } = {}) {
|
||||
const limit = MATCHES_COUNT_LIMIT;
|
||||
// // NOTE
|
||||
let matchCountMsg = "";
|
||||
|
||||
if (total > 0) {
|
||||
if (total > limit) {
|
||||
msg = window.siyuan.languages.find_match_count_limit.replace(
|
||||
matchCountMsg = window.siyuan.languages.find_match_count_limit.replace(
|
||||
'{{limit}}', limit)
|
||||
} else {
|
||||
msg = window.siyuan.languages.find_match_count.replace('{{current}}',
|
||||
matchCountMsg = window.siyuan.languages.find_match_count.replace('{{current}}',
|
||||
current).replace('{{total}}', total)
|
||||
}
|
||||
}
|
||||
this.findResultsCount.textContent = msg
|
||||
this.findResultsCount.classList.toggle('fn__hidden', !total)
|
||||
|
||||
this.findResultsCount.textContent = matchCountMsg
|
||||
this.#adjustWidth()
|
||||
}
|
||||
|
||||
open () {
|
||||
open() {
|
||||
if (!this.opened) {
|
||||
this.opened = true
|
||||
this.toggleButton.classList.add('toggled')
|
||||
this.toggleButton.setAttribute('aria-expanded', 'true')
|
||||
this.bar.classList.remove('fn__hidden')
|
||||
this.opened = true;
|
||||
this.toggleButton.classList.add("toggled");
|
||||
this.toggleButton.setAttribute("aria-expanded", "true");
|
||||
// NOTE
|
||||
this.bar.classList.remove("fn__hidden");
|
||||
}
|
||||
this.findField.select()
|
||||
this.findField.focus()
|
||||
this.findField.select();
|
||||
this.findField.focus();
|
||||
|
||||
this.#adjustWidth()
|
||||
this.#adjustWidth();
|
||||
}
|
||||
|
||||
close () {
|
||||
close() {
|
||||
if (!this.opened) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.opened = false
|
||||
this.toggleButton.classList.remove('toggled')
|
||||
this.toggleButton.setAttribute('aria-expanded', 'false')
|
||||
this.bar.classList.add('fn__hidden')
|
||||
this.opened = false;
|
||||
this.toggleButton.classList.remove("toggled");
|
||||
this.toggleButton.setAttribute("aria-expanded", "false");
|
||||
// NOTE
|
||||
this.bar.classList.add("fn__hidden");
|
||||
|
||||
this.eventBus.dispatch('findbarclose', {source: this})
|
||||
this.eventBus.dispatch("findbarclose", { source: this });
|
||||
}
|
||||
|
||||
toggle () {
|
||||
toggle() {
|
||||
if (this.opened) {
|
||||
this.close()
|
||||
this.close();
|
||||
} else {
|
||||
this.open()
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
|
||||
#adjustWidth () {
|
||||
#adjustWidth() {
|
||||
if (!this.opened) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// The find bar has an absolute position and thus the browser extends
|
||||
// its width to the maximum possible width once the find bar does not fit
|
||||
// entirely within the window anymore (and its elements are automatically
|
||||
// wrapped). Here we detect and fix that.
|
||||
this.bar.classList.remove('wrapContainers')
|
||||
this.bar.classList.remove("wrapContainers");
|
||||
|
||||
const findbarHeight = this.bar.clientHeight
|
||||
const inputContainerHeight = this.bar.firstElementChild.clientHeight
|
||||
const findbarHeight = this.bar.clientHeight;
|
||||
const inputContainerHeight = this.bar.firstElementChild.clientHeight;
|
||||
|
||||
if (findbarHeight > inputContainerHeight) {
|
||||
// The findbar is taller than the input container, which means that
|
||||
// the browser wrapped some of the elements. For a consistent look,
|
||||
// wrap all of them to adjust the width of the find bar.
|
||||
this.bar.classList.add('wrapContainers')
|
||||
this.bar.classList.add("wrapContainers");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { PDFFindBar }
|
||||
export { PDFFindBar };
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @typedef {import("../src/display/api").PDFDocumentProxy} PDFDocumentProxy */
|
||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
||||
|
||||
import { binarySearchFirstItem, scrollIntoView } from "./ui_utils.js";
|
||||
import { createPromiseCapability } from "./pdfjs";
|
||||
import { getCharacterType } from "./pdf_find_utils.js";
|
||||
|
|
@ -82,19 +86,62 @@ const SPECIAL_CHARS_REG_EXP =
|
|||
const NOT_DIACRITIC_FROM_END_REG_EXP = /([^\p{M}])\p{M}*$/u;
|
||||
const NOT_DIACRITIC_FROM_START_REG_EXP = /^\p{M}*([^\p{M}])/u;
|
||||
|
||||
let normalizationRegex = null;
|
||||
// The range [AC00-D7AF] corresponds to the Hangul syllables.
|
||||
// The few other chars are some CJK Compatibility Ideographs.
|
||||
const SYLLABLES_REG_EXP = /[\uAC00-\uD7AF\uFA6C\uFACF-\uFAD1\uFAD5-\uFAD7]+/g;
|
||||
const SYLLABLES_LENGTHS = new Map();
|
||||
// When decomposed (in using NFD) the above syllables will start
|
||||
// with one of the chars in this regexp.
|
||||
const FIRST_CHAR_SYLLABLES_REG_EXP =
|
||||
"[\\u1100-\\u1112\\ud7a4-\\ud7af\\ud84a\\ud84c\\ud850\\ud854\\ud857\\ud85f]";
|
||||
|
||||
let noSyllablesRegExp = null;
|
||||
let withSyllablesRegExp = null;
|
||||
|
||||
function normalize(text) {
|
||||
// The diacritics in the text or in the query can be composed or not.
|
||||
// So we use a decomposed text using NFD (and the same for the query)
|
||||
// in order to be sure that diacritics are in the same order.
|
||||
|
||||
if (!normalizationRegex) {
|
||||
// Collect syllables length and positions.
|
||||
const syllablePositions = [];
|
||||
let m;
|
||||
while ((m = SYLLABLES_REG_EXP.exec(text)) !== null) {
|
||||
let { index } = m;
|
||||
for (const char of m[0]) {
|
||||
let len = SYLLABLES_LENGTHS.get(char);
|
||||
if (!len) {
|
||||
len = char.normalize("NFD").length;
|
||||
SYLLABLES_LENGTHS.set(char, len);
|
||||
}
|
||||
syllablePositions.push([len, index++]);
|
||||
}
|
||||
}
|
||||
|
||||
let normalizationRegex;
|
||||
if (syllablePositions.length === 0 && noSyllablesRegExp) {
|
||||
normalizationRegex = noSyllablesRegExp;
|
||||
} else if (syllablePositions.length > 0 && withSyllablesRegExp) {
|
||||
normalizationRegex = withSyllablesRegExp;
|
||||
} else {
|
||||
// Compile the regular expression for text normalization once.
|
||||
const replace = Object.keys(CHARACTERS_TO_NORMALIZE).join("");
|
||||
normalizationRegex = new RegExp(
|
||||
`([${replace}])|(\\p{M}+(?:-\\n)?)|(\\S-\\n)|(\\n)`,
|
||||
"gum"
|
||||
);
|
||||
const regexp = `([${replace}])|(\\p{M}+(?:-\\n)?)|(\\S-\\n)|(\\p{Ideographic}\\n)|(\\n)`;
|
||||
|
||||
if (syllablePositions.length === 0) {
|
||||
// Most of the syllables belong to Hangul so there are no need
|
||||
// to search for them in a non-Hangul document.
|
||||
// We use the \0 in order to have the same number of groups.
|
||||
normalizationRegex = noSyllablesRegExp = new RegExp(
|
||||
regexp + "|(\\u0000)",
|
||||
"gum"
|
||||
);
|
||||
} else {
|
||||
normalizationRegex = withSyllablesRegExp = new RegExp(
|
||||
regexp + `|(${FIRST_CHAR_SYLLABLES_REG_EXP})`,
|
||||
"gum"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The goal of this function is to normalize the string and
|
||||
|
|
@ -126,14 +173,14 @@ function normalize(text) {
|
|||
|
||||
// Collect diacritics length and positions.
|
||||
const rawDiacriticsPositions = [];
|
||||
let m;
|
||||
while ((m = DIACRITICS_REG_EXP.exec(text)) !== null) {
|
||||
rawDiacriticsPositions.push([m[0].length, m.index]);
|
||||
}
|
||||
|
||||
let normalized = text.normalize("NFD");
|
||||
const positions = [[0, 0]];
|
||||
let k = 0;
|
||||
let rawDiacriticsIndex = 0;
|
||||
let syllableIndex = 0;
|
||||
let shift = 0;
|
||||
let shiftOrigin = 0;
|
||||
let eol = 0;
|
||||
|
|
@ -141,7 +188,7 @@ function normalize(text) {
|
|||
|
||||
normalized = normalized.replace(
|
||||
normalizationRegex,
|
||||
(match, p1, p2, p3, p4, i) => {
|
||||
(match, p1, p2, p3, p4, p5, p6, i) => {
|
||||
i -= shiftOrigin;
|
||||
if (p1) {
|
||||
// Maybe fractions or quotations mark...
|
||||
|
|
@ -161,12 +208,12 @@ function normalize(text) {
|
|||
// Diacritics.
|
||||
hasDiacritics = true;
|
||||
let jj = len;
|
||||
if (i + eol === rawDiacriticsPositions[k]?.[1]) {
|
||||
jj -= rawDiacriticsPositions[k][0];
|
||||
++k;
|
||||
if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) {
|
||||
jj -= rawDiacriticsPositions[rawDiacriticsIndex][0];
|
||||
++rawDiacriticsIndex;
|
||||
}
|
||||
|
||||
for (let j = 1; j < jj + 1; j++) {
|
||||
for (let j = 1; j <= jj; j++) {
|
||||
// i is the position of the first diacritic
|
||||
// so (i - 1) is the position for the letter before.
|
||||
positions.push([i - 1 - shift + j, shift - j]);
|
||||
|
|
@ -200,14 +247,38 @@ function normalize(text) {
|
|||
return p3.charAt(0);
|
||||
}
|
||||
|
||||
// p4
|
||||
// eol is replaced by space: "foo\nbar" is likely equivalent to
|
||||
// "foo bar".
|
||||
positions.push([i - shift + 1, shift - 1]);
|
||||
shift -= 1;
|
||||
shiftOrigin += 1;
|
||||
eol += 1;
|
||||
return " ";
|
||||
if (p4) {
|
||||
// An ideographic at the end of a line doesn't imply adding an extra
|
||||
// white space.
|
||||
positions.push([i - shift + 1, shift]);
|
||||
shiftOrigin += 1;
|
||||
eol += 1;
|
||||
return p4.charAt(0);
|
||||
}
|
||||
|
||||
if (p5) {
|
||||
// eol is replaced by space: "foo\nbar" is likely equivalent to
|
||||
// "foo bar".
|
||||
positions.push([i - shift + 1, shift - 1]);
|
||||
shift -= 1;
|
||||
shiftOrigin += 1;
|
||||
eol += 1;
|
||||
return " ";
|
||||
}
|
||||
|
||||
// p6
|
||||
if (i + eol === syllablePositions[syllableIndex]?.[1]) {
|
||||
// A syllable (1 char) is replaced with several chars (n) so
|
||||
// newCharsLen = n - 1.
|
||||
const newCharLen = syllablePositions[syllableIndex][0] - 1;
|
||||
++syllableIndex;
|
||||
for (let j = 1; j <= newCharLen; j++) {
|
||||
positions.push([i - (shift - j), shift - j]);
|
||||
}
|
||||
shift -= newCharLen;
|
||||
shiftOrigin += newCharLen;
|
||||
}
|
||||
return p6;
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
isValidRotation,
|
||||
parseQueryString,
|
||||
PresentationModeState,
|
||||
} from "./ui_utils.js";
|
||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
||||
|
||||
import { isValidRotation, parseQueryString } from "./ui_utils.js";
|
||||
import { waitOnEventOrTimeout } from "./event_utils.js";
|
||||
|
||||
// Heuristic value used when force-resetting `this._blockHashChange`.
|
||||
|
|
@ -66,13 +65,8 @@ class PDFHistory {
|
|||
this.reset();
|
||||
|
||||
this._boundEvents = null;
|
||||
this._isViewerInPresentationMode = false;
|
||||
// Ensure that we don't miss either a 'presentationmodechanged' or a
|
||||
// 'pagesinit' event, by registering the listeners immediately.
|
||||
this.eventBus._on("presentationmodechanged", evt => {
|
||||
this._isViewerInPresentationMode =
|
||||
evt.state !== PresentationModeState.NORMAL;
|
||||
});
|
||||
// Ensure that we don't miss a "pagesinit" event,
|
||||
// by registering the listener immediately.
|
||||
this.eventBus._on("pagesinit", () => {
|
||||
this._isPagesLoaded = false;
|
||||
|
||||
|
|
@ -196,13 +190,13 @@ class PDFHistory {
|
|||
if (namedDest && typeof namedDest !== "string") {
|
||||
console.error(
|
||||
"PDFHistory.push: " +
|
||||
`"${namedDest}" is not a valid namedDest parameter.`
|
||||
`"${namedDest}" is not a valid namedDest parameter.`
|
||||
);
|
||||
return;
|
||||
} else if (!Array.isArray(explicitDest)) {
|
||||
console.error(
|
||||
"PDFHistory.push: " +
|
||||
`"${explicitDest}" is not a valid explicitDest parameter.`
|
||||
`"${explicitDest}" is not a valid explicitDest parameter.`
|
||||
);
|
||||
return;
|
||||
} else if (!this._isValidPage(pageNumber)) {
|
||||
|
|
@ -211,7 +205,7 @@ class PDFHistory {
|
|||
if (pageNumber !== null || this._destination) {
|
||||
console.error(
|
||||
"PDFHistory.push: " +
|
||||
`"${pageNumber}" is not a valid pageNumber parameter.`
|
||||
`"${pageNumber}" is not a valid pageNumber parameter.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -563,9 +557,7 @@ class PDFHistory {
|
|||
}
|
||||
|
||||
this._position = {
|
||||
hash: this._isViewerInPresentationMode
|
||||
? `page=${location.pageNumber}`
|
||||
: location.pdfOpenParams.substring(1),
|
||||
hash: location.pdfOpenParams.substring(1),
|
||||
page: this.linkService.page,
|
||||
first: location.pageNumber,
|
||||
rotation: location.rotation,
|
||||
|
|
|
|||
|
|
@ -34,13 +34,19 @@ class PDFLayerViewer extends BaseTreeViewer {
|
|||
super(options);
|
||||
this.l10n = options.l10n;
|
||||
|
||||
this.eventBus._on("resetlayers", this._resetLayers.bind(this));
|
||||
this.eventBus._on("optionalcontentconfigchanged", evt => {
|
||||
this.#updateLayers(evt.promise);
|
||||
});
|
||||
this.eventBus._on("resetlayers", () => {
|
||||
this.#updateLayers();
|
||||
});
|
||||
this.eventBus._on("togglelayerstree", this._toggleAllTreeItems.bind(this));
|
||||
}
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
this._optionalContentConfig = null;
|
||||
this._optionalContentHash = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -59,6 +65,7 @@ class PDFLayerViewer extends BaseTreeViewer {
|
|||
_bindLink(element, { groupId, input }) {
|
||||
const setVisibility = () => {
|
||||
this._optionalContentConfig.setVisibility(groupId, input.checked);
|
||||
this._optionalContentHash = this._optionalContentConfig.getHash();
|
||||
|
||||
this.eventBus.dispatch("optionalcontentconfig", {
|
||||
source: this,
|
||||
|
|
@ -87,8 +94,9 @@ class PDFLayerViewer extends BaseTreeViewer {
|
|||
element.textContent = this._normalizeTextContent(name);
|
||||
return;
|
||||
}
|
||||
// NOTE
|
||||
element.textContent = window.siyuan.languages.additionalLayers
|
||||
element.style.fontStyle = 'italic'
|
||||
element.style.fontStyle = "italic";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -123,6 +131,7 @@ class PDFLayerViewer extends BaseTreeViewer {
|
|||
this._dispatchEvent(/* layersCount = */ 0);
|
||||
return;
|
||||
}
|
||||
this._optionalContentHash = optionalContentConfig.getHash();
|
||||
|
||||
const fragment = document.createDocumentFragment(),
|
||||
queue = [{ parent: fragment, groups }];
|
||||
|
|
@ -135,7 +144,7 @@ class PDFLayerViewer extends BaseTreeViewer {
|
|||
div.className = "treeItem";
|
||||
|
||||
const element = document.createElement("a");
|
||||
div.appendChild(element);
|
||||
div.append(element);
|
||||
|
||||
if (typeof groupId === "object") {
|
||||
hasAnyNesting = true;
|
||||
|
|
@ -144,7 +153,7 @@ class PDFLayerViewer extends BaseTreeViewer {
|
|||
|
||||
const itemsDiv = document.createElement("div");
|
||||
itemsDiv.className = "treeItems";
|
||||
div.appendChild(itemsDiv);
|
||||
div.append(itemsDiv);
|
||||
|
||||
queue.push({ parent: itemsDiv, groups: groupId.order });
|
||||
} else {
|
||||
|
|
@ -153,43 +162,46 @@ class PDFLayerViewer extends BaseTreeViewer {
|
|||
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);
|
||||
|
||||
label.append(input);
|
||||
element.append(label);
|
||||
layersCount++;
|
||||
}
|
||||
|
||||
levelData.parent.appendChild(div);
|
||||
levelData.parent.append(div);
|
||||
}
|
||||
}
|
||||
|
||||
this._finishRendering(fragment, layersCount, hasAnyNesting);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _resetLayers() {
|
||||
async #updateLayers(promise = null) {
|
||||
if (!this._optionalContentConfig) {
|
||||
return;
|
||||
}
|
||||
// Fetch the default optional content configuration...
|
||||
const optionalContentConfig =
|
||||
await this._pdfDocument.getOptionalContentConfig();
|
||||
const pdfDocument = this._pdfDocument;
|
||||
const optionalContentConfig = await (promise ||
|
||||
pdfDocument.getOptionalContentConfig());
|
||||
|
||||
this.eventBus.dispatch("optionalcontentconfig", {
|
||||
source: this,
|
||||
promise: Promise.resolve(optionalContentConfig),
|
||||
});
|
||||
if (pdfDocument !== this._pdfDocument) {
|
||||
return; // The document was closed while the optional content resolved.
|
||||
}
|
||||
if (promise) {
|
||||
if (optionalContentConfig.getHash() === this._optionalContentHash) {
|
||||
return; // The optional content didn't change, hence no need to reset the UI.
|
||||
}
|
||||
} else {
|
||||
this.eventBus.dispatch("optionalcontentconfig", {
|
||||
source: this,
|
||||
promise: Promise.resolve(optionalContentConfig),
|
||||
});
|
||||
}
|
||||
|
||||
// ... and reset the sidebarView to the default state.
|
||||
// Reset the sidebarView to the new state.
|
||||
this.render({
|
||||
optionalContentConfig,
|
||||
pdfDocument: this._pdfDocument,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
||||
|
||||
import { parseQueryString, removeNullCharacters } from "./ui_utils.js";
|
||||
|
||||
const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
|
||||
|
|
@ -104,11 +107,11 @@ class PDFLinkService {
|
|||
* @param {PDFLinkServiceOptions} options
|
||||
*/
|
||||
constructor({
|
||||
eventBus,
|
||||
externalLinkTarget = null,
|
||||
externalLinkRel = null,
|
||||
ignoreDestinationZoom = false,
|
||||
} = {}) {
|
||||
eventBus,
|
||||
externalLinkTarget = null,
|
||||
externalLinkRel = null,
|
||||
ignoreDestinationZoom = false,
|
||||
} = {}) {
|
||||
this.eventBus = eventBus;
|
||||
this.externalLinkTarget = externalLinkTarget;
|
||||
this.externalLinkRel = externalLinkRel;
|
||||
|
|
@ -182,17 +185,17 @@ class PDFLinkService {
|
|||
// Fetch the page reference if it's not yet available. This could
|
||||
// only occur during loading, before all pages have been resolved.
|
||||
this.pdfDocument
|
||||
.getPageIndex(destRef)
|
||||
.then(pageIndex => {
|
||||
this.cachePageRef(pageIndex + 1, destRef);
|
||||
this.#goToDestinationHelper(rawDest, namedDest, explicitDest);
|
||||
})
|
||||
.catch(() => {
|
||||
console.error(
|
||||
`PDFLinkService.#goToDestinationHelper: "${destRef}" is not ` +
|
||||
`a valid page reference, for dest="${rawDest}".`
|
||||
);
|
||||
});
|
||||
.getPageIndex(destRef)
|
||||
.then(pageIndex => {
|
||||
this.cachePageRef(pageIndex + 1, destRef);
|
||||
this.#goToDestinationHelper(rawDest, namedDest, explicitDest);
|
||||
})
|
||||
.catch(() => {
|
||||
console.error(
|
||||
`PDFLinkService.#goToDestinationHelper: "${destRef}" is not ` +
|
||||
`a valid page reference, for dest="${rawDest}".`
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (Number.isInteger(destRef)) {
|
||||
|
|
@ -200,14 +203,14 @@ class PDFLinkService {
|
|||
} else {
|
||||
console.error(
|
||||
`PDFLinkService.#goToDestinationHelper: "${destRef}" is not ` +
|
||||
`a valid destination reference, for dest="${rawDest}".`
|
||||
`a valid destination reference, for dest="${rawDest}".`
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
|
||||
console.error(
|
||||
`PDFLinkService.#goToDestinationHelper: "${pageNumber}" is not ` +
|
||||
`a valid page number, for dest="${rawDest}".`
|
||||
`a valid page number, for dest="${rawDest}".`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -246,7 +249,7 @@ class PDFLinkService {
|
|||
if (!Array.isArray(explicitDest)) {
|
||||
console.error(
|
||||
`PDFLinkService.goToDestination: "${explicitDest}" is not ` +
|
||||
`a valid destination array, for dest="${dest}".`
|
||||
`a valid destination array, for dest="${dest}".`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -490,6 +493,48 @@ class PDFLinkService {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} action
|
||||
*/
|
||||
async executeSetOCGState(action) {
|
||||
const pdfDocument = this.pdfDocument;
|
||||
const optionalContentConfig = await this.pdfViewer
|
||||
.optionalContentConfigPromise;
|
||||
|
||||
if (pdfDocument !== this.pdfDocument) {
|
||||
return; // The document was closed while the optional content resolved.
|
||||
}
|
||||
let operator;
|
||||
|
||||
for (const elem of action.state) {
|
||||
switch (elem) {
|
||||
case "ON":
|
||||
case "OFF":
|
||||
case "Toggle":
|
||||
operator = elem;
|
||||
continue;
|
||||
}
|
||||
switch (operator) {
|
||||
case "ON":
|
||||
optionalContentConfig.setVisibility(elem, true);
|
||||
break;
|
||||
case "OFF":
|
||||
optionalContentConfig.setVisibility(elem, false);
|
||||
break;
|
||||
case "Toggle":
|
||||
const group = optionalContentConfig.getGroup(elem);
|
||||
if (group) {
|
||||
optionalContentConfig.setVisibility(elem, !group.visible);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.pdfViewer.optionalContentConfigPromise = Promise.resolve(
|
||||
optionalContentConfig
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pageNum - page number.
|
||||
* @param {Object} pageRef - reference to the page.
|
||||
|
|
@ -673,6 +718,11 @@ class SimpleLinkService {
|
|||
*/
|
||||
executeNamedAction(action) {}
|
||||
|
||||
/**
|
||||
* @param {Object} action
|
||||
*/
|
||||
executeSetOCGState(action) {}
|
||||
|
||||
/**
|
||||
* @param {number} pageNum - page number.
|
||||
* @param {Object} pageRef - reference to the page.
|
||||
|
|
|
|||
|
|
@ -109,13 +109,29 @@ class PDFOutlineViewer extends BaseTreeViewer {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
_bindLink(element, { url, newWindow, dest }) {
|
||||
_bindLink(element, { url, newWindow, action, dest, setOCGState }) {
|
||||
const { linkService } = this;
|
||||
|
||||
if (url) {
|
||||
linkService.addLinkAttributes(element, url, newWindow);
|
||||
return;
|
||||
}
|
||||
if (action) {
|
||||
element.href = linkService.getAnchorUrl("");
|
||||
element.onclick = () => {
|
||||
linkService.executeNamedAction(action);
|
||||
return false;
|
||||
};
|
||||
return;
|
||||
}
|
||||
if (setOCGState) {
|
||||
element.href = linkService.getAnchorUrl("");
|
||||
element.onclick = () => {
|
||||
linkService.executeSetOCGState(setOCGState);
|
||||
return false;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
element.href = linkService.getDestinationHash(dest);
|
||||
element.onclick = evt => {
|
||||
|
|
@ -204,7 +220,7 @@ class PDFOutlineViewer extends BaseTreeViewer {
|
|||
this._setStyles(element, item);
|
||||
element.textContent = this._normalizeTextContent(item.title);
|
||||
|
||||
div.appendChild(element);
|
||||
div.append(element);
|
||||
|
||||
if (item.items.length > 0) {
|
||||
hasAnyNesting = true;
|
||||
|
|
@ -212,12 +228,12 @@ class PDFOutlineViewer extends BaseTreeViewer {
|
|||
|
||||
const itemsDiv = document.createElement("div");
|
||||
itemsDiv.className = "treeItems";
|
||||
div.appendChild(itemsDiv);
|
||||
div.append(itemsDiv);
|
||||
|
||||
queue.push({ parent: itemsDiv, items: item.items });
|
||||
}
|
||||
|
||||
levelData.parent.appendChild(div);
|
||||
levelData.parent.append(div);
|
||||
outlineCount++;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,25 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/optional_content_config").OptionalContentConfig} OptionalContentConfig */
|
||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||
/** @typedef {import("./interfaces").IL10n} IL10n */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./interfaces").IPDFAnnotationLayerFactory} IPDFAnnotationLayerFactory */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./interfaces").IPDFAnnotationEditorLayerFactory} IPDFAnnotationEditorLayerFactory */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./interfaces").IPDFStructTreeLayerFactory} IPDFStructTreeLayerFactory */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./interfaces").IPDFTextLayerFactory} IPDFTextLayerFactory */
|
||||
/** @typedef {import("./interfaces").IPDFXfaLayerFactory} IPDFXfaLayerFactory */
|
||||
/** @typedef {import("./interfaces").IRenderableView} IRenderableView */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
||||
|
||||
import {
|
||||
AnnotationMode,
|
||||
createPromiseCapability,
|
||||
|
|
@ -23,6 +42,7 @@ import {
|
|||
import {
|
||||
approximateFraction,
|
||||
DEFAULT_SCALE,
|
||||
docStyle,
|
||||
OutputScale,
|
||||
RendererType,
|
||||
RenderingStates,
|
||||
|
|
@ -31,41 +51,44 @@ import {
|
|||
} from "./ui_utils.js";
|
||||
import { compatibilityParams } from "./app_options.js";
|
||||
import { NullL10n } from "./l10n_utils.js";
|
||||
import { TextAccessibilityManager } from "./text_accessibility.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} PDFPageViewOptions
|
||||
* @property {HTMLDivElement} [container] - The viewer element.
|
||||
* @property {EventBus} eventBus - The application event bus.
|
||||
* @property {number} id - The page unique ID (normally its number).
|
||||
* @property {number} scale - The page scale display.
|
||||
* @property {number} [scale] - The page scale display.
|
||||
* @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 {PDFRenderingQueue} renderingQueue - The rendering queue object.
|
||||
* @property {IPDFTextLayerFactory} textLayerFactory
|
||||
* @property {PDFRenderingQueue} [renderingQueue] - The rendering queue object.
|
||||
* @property {IPDFTextLayerFactory} [textLayerFactory]
|
||||
* @property {number} [textLayerMode] - Controls if the text layer used for
|
||||
* selection and searching is created, and if the improved text selection
|
||||
* behaviour is enabled. The constants from {TextLayerMode} should be used.
|
||||
* The default value is `TextLayerMode.ENABLE`.
|
||||
* selection and searching is created. The constants from {TextLayerMode}
|
||||
* should be used. The default value is `TextLayerMode.ENABLE`.
|
||||
* @property {number} [annotationMode] - Controls if the annotation layer is
|
||||
* created, and if interactive form elements or `AnnotationStorage`-data are
|
||||
* being rendered. The constants from {@link AnnotationMode} should be used;
|
||||
* see also {@link RenderParameters} and {@link GetOperatorListParameters}.
|
||||
* The default value is `AnnotationMode.ENABLE_FORMS`.
|
||||
* @property {IPDFAnnotationLayerFactory} annotationLayerFactory
|
||||
* @property {IPDFXfaLayerFactory} xfaLayerFactory
|
||||
* @property {IPDFStructTreeLayerFactory} structTreeLayerFactory
|
||||
* @property {IPDFAnnotationLayerFactory} [annotationLayerFactory]
|
||||
* @property {IPDFAnnotationEditorLayerFactory} [annotationEditorLayerFactory]
|
||||
* @property {IPDFXfaLayerFactory} [xfaLayerFactory]
|
||||
* @property {IPDFStructTreeLayerFactory} [structTreeLayerFactory]
|
||||
* @property {Object} [textHighlighterFactory]
|
||||
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
||||
* for annotation icons. Include trailing slash.
|
||||
* @property {string} renderer - 'canvas' or 'svg'. The default is 'canvas'.
|
||||
* @property {boolean} [useOnlyCssZoom] - Enables CSS only zooming. The default
|
||||
* value is `false`.
|
||||
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
||||
* total pixels, i.e. width * height. Use -1 for no limit. The default value
|
||||
* is 4096 * 4096 (16 mega-pixels).
|
||||
* @property {IL10n} l10n - Localization service.
|
||||
* @property {Object} [pageColors] - Overwrites background and foreground colors
|
||||
* with user defined ones in order to improve readability in high contrast
|
||||
* mode.
|
||||
* @property {IL10n} [l10n] - Localization service.
|
||||
*/
|
||||
|
||||
const MAX_CANVAS_PIXELS = compatibilityParams.maxCanvasPixels || 16777216;
|
||||
|
|
@ -76,6 +99,11 @@ const MAX_CANVAS_PIXELS = compatibilityParams.maxCanvasPixels || 16777216;
|
|||
class PDFPageView {
|
||||
#annotationMode = AnnotationMode.ENABLE_FORMS;
|
||||
|
||||
#useThumbnailCanvas = {
|
||||
initialOptionalContent: true,
|
||||
regularAnnotations: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {PDFPageViewOptions} options
|
||||
*/
|
||||
|
|
@ -101,19 +129,26 @@ class PDFPageView {
|
|||
this.imageResourcesPath = options.imageResourcesPath || "";
|
||||
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
|
||||
this.maxCanvasPixels = options.maxCanvasPixels || MAX_CANVAS_PIXELS;
|
||||
this.pageColors = options.pageColors || null;
|
||||
|
||||
this.eventBus = options.eventBus;
|
||||
this.renderingQueue = options.renderingQueue;
|
||||
this.textLayerFactory = options.textLayerFactory;
|
||||
this.annotationLayerFactory = options.annotationLayerFactory;
|
||||
this.annotationEditorLayerFactory = options.annotationEditorLayerFactory;
|
||||
this.xfaLayerFactory = options.xfaLayerFactory;
|
||||
this.textHighlighter =
|
||||
options.textHighlighterFactory?.createTextHighlighter(
|
||||
this.id - 1,
|
||||
this.eventBus
|
||||
);
|
||||
options.textHighlighterFactory?.createTextHighlighter({
|
||||
pageIndex: this.id - 1,
|
||||
eventBus: this.eventBus,
|
||||
});
|
||||
this.structTreeLayerFactory = options.structTreeLayerFactory;
|
||||
this.renderer = options.renderer || RendererType.CANVAS;
|
||||
if (
|
||||
typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("!PRODUCTION || GENERIC")
|
||||
) {
|
||||
this.renderer = options.renderer || RendererType.CANVAS;
|
||||
}
|
||||
this.l10n = options.l10n || NullL10n;
|
||||
|
||||
this.paintTask = null;
|
||||
|
|
@ -121,11 +156,17 @@ class PDFPageView {
|
|||
this.renderingState = RenderingStates.INITIAL;
|
||||
this.resume = null;
|
||||
this._renderError = null;
|
||||
this._isStandalone = !this.renderingQueue?.hasViewer();
|
||||
if (
|
||||
typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("!PRODUCTION || GENERIC")
|
||||
) {
|
||||
this._isStandalone = !this.renderingQueue?.hasViewer();
|
||||
}
|
||||
|
||||
this._annotationCanvasMap = null;
|
||||
|
||||
this.annotationLayer = null;
|
||||
this.annotationEditorLayer = null;
|
||||
this.textLayer = null;
|
||||
this.zoomLayer = null;
|
||||
this.xfaLayer = null;
|
||||
|
|
@ -142,7 +183,28 @@ class PDFPageView {
|
|||
window.siyuan.languages.thumbPageTitle.replace('{{page}}', this.id))
|
||||
this.div = div;
|
||||
|
||||
container?.appendChild(div);
|
||||
container?.append(div);
|
||||
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("!PRODUCTION || GENERIC")) &&
|
||||
this._isStandalone
|
||||
) {
|
||||
const { optionalContentConfigPromise } = options;
|
||||
if (optionalContentConfigPromise) {
|
||||
// Ensure that the thumbnails always display the *initial* document
|
||||
// state, for documents with optional content.
|
||||
optionalContentConfigPromise.then(optionalContentConfig => {
|
||||
if (
|
||||
optionalContentConfigPromise !== this._optionalContentConfigPromise
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.#useThumbnailCanvas.initialOptionalContent =
|
||||
optionalContentConfig.hasInitialVisibility;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setPdfPage(pdfPage) {
|
||||
|
|
@ -159,9 +221,7 @@ class PDFPageView {
|
|||
|
||||
destroy() {
|
||||
this.reset();
|
||||
if (this.pdfPage) {
|
||||
this.pdfPage.cleanup();
|
||||
}
|
||||
this.pdfPage?.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -172,6 +232,7 @@ class PDFPageView {
|
|||
try {
|
||||
await this.annotationLayer.render(this.viewport, "display");
|
||||
} catch (ex) {
|
||||
console.error(`_renderAnnotationLayer: "${ex}".`);
|
||||
error = ex;
|
||||
} finally {
|
||||
this.eventBus.dispatch("annotationlayerrendered", {
|
||||
|
|
@ -182,6 +243,25 @@ class PDFPageView {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _renderAnnotationEditorLayer() {
|
||||
let error = null;
|
||||
try {
|
||||
await this.annotationEditorLayer.render(this.viewport, "display");
|
||||
} catch (ex) {
|
||||
console.error(`_renderAnnotationEditorLayer: "${ex}".`);
|
||||
error = ex;
|
||||
} finally {
|
||||
this.eventBus.dispatch("annotationeditorlayerrendered", {
|
||||
source: this,
|
||||
pageNumber: this.id,
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
|
@ -189,10 +269,11 @@ class PDFPageView {
|
|||
let error = null;
|
||||
try {
|
||||
const result = await this.xfaLayer.render(this.viewport, "display");
|
||||
if (this.textHighlighter) {
|
||||
if (result?.textDivs && this.textHighlighter) {
|
||||
this._buildXfaTextContentItems(result.textDivs);
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error(`_renderXfaLayer: "${ex}".`);
|
||||
error = ex;
|
||||
} finally {
|
||||
this.eventBus.dispatch("xfalayerrendered", {
|
||||
|
|
@ -235,11 +316,16 @@ class PDFPageView {
|
|||
}
|
||||
|
||||
reset({
|
||||
keepZoomLayer = false,
|
||||
keepAnnotationLayer = false,
|
||||
keepXfaLayer = false,
|
||||
} = {}) {
|
||||
this.cancelRendering({ keepAnnotationLayer, keepXfaLayer });
|
||||
keepZoomLayer = false,
|
||||
keepAnnotationLayer = false,
|
||||
keepAnnotationEditorLayer = false,
|
||||
keepXfaLayer = false,
|
||||
} = {}) {
|
||||
this.cancelRendering({
|
||||
keepAnnotationLayer,
|
||||
keepAnnotationEditorLayer,
|
||||
keepXfaLayer,
|
||||
});
|
||||
this.renderingState = RenderingStates.INITIAL;
|
||||
|
||||
const div = this.div;
|
||||
|
|
@ -250,12 +336,15 @@ class PDFPageView {
|
|||
zoomLayerNode = (keepZoomLayer && this.zoomLayer) || null,
|
||||
annotationLayerNode =
|
||||
(keepAnnotationLayer && this.annotationLayer?.div) || null,
|
||||
annotationEditorLayerNode =
|
||||
(keepAnnotationEditorLayer && this.annotationEditorLayer?.div) || null,
|
||||
xfaLayerNode = (keepXfaLayer && this.xfaLayer?.div) || null;
|
||||
for (let i = childNodes.length - 1; i >= 0; i--) {
|
||||
const node = childNodes[i];
|
||||
switch (node) {
|
||||
case zoomLayerNode:
|
||||
case annotationLayerNode:
|
||||
case annotationEditorLayerNode:
|
||||
case xfaLayerNode:
|
||||
continue;
|
||||
}
|
||||
|
|
@ -268,6 +357,12 @@ class PDFPageView {
|
|||
// so they are not displayed on the already resized page.
|
||||
this.annotationLayer.hide();
|
||||
}
|
||||
|
||||
if (annotationEditorLayerNode) {
|
||||
this.annotationEditorLayer.hide();
|
||||
} else {
|
||||
this.annotationEditorLayer?.destroy();
|
||||
}
|
||||
if (xfaLayerNode) {
|
||||
// Hide the XFA layer until all elements are resized
|
||||
// so they are not displayed on the already resized page.
|
||||
|
|
@ -285,20 +380,29 @@ class PDFPageView {
|
|||
}
|
||||
this._resetZoomLayer();
|
||||
}
|
||||
if (this.svg) {
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("!PRODUCTION || GENERIC")) &&
|
||||
this.svg
|
||||
) {
|
||||
this.paintedViewportMap.delete(this.svg);
|
||||
delete this.svg;
|
||||
}
|
||||
|
||||
this.loadingIconDiv = document.createElement("div");
|
||||
this.loadingIconDiv.className = "loadingIcon notVisible";
|
||||
if (this._isStandalone) {
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("!PRODUCTION || GENERIC")) &&
|
||||
this._isStandalone
|
||||
) {
|
||||
this.toggleLoadingIconSpinner(/* viewVisible = */ true);
|
||||
}
|
||||
this.loadingIconDiv.setAttribute("role", "img");
|
||||
// NOTE
|
||||
this.loadingIconDiv?.setAttribute('aria-label',
|
||||
window.siyuan.languages.loading)
|
||||
div.appendChild(this.loadingIconDiv);
|
||||
div.append(this.loadingIconDiv);
|
||||
}
|
||||
|
||||
update({ scale = 0, rotation = null, optionalContentConfigPromise = null }) {
|
||||
|
|
@ -308,25 +412,43 @@ class PDFPageView {
|
|||
}
|
||||
if (optionalContentConfigPromise instanceof Promise) {
|
||||
this._optionalContentConfigPromise = optionalContentConfigPromise;
|
||||
|
||||
// Ensure that the thumbnails always display the *initial* document state,
|
||||
// for documents with optional content.
|
||||
optionalContentConfigPromise.then(optionalContentConfig => {
|
||||
if (
|
||||
optionalContentConfigPromise !== this._optionalContentConfigPromise
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.#useThumbnailCanvas.initialOptionalContent =
|
||||
optionalContentConfig.hasInitialVisibility;
|
||||
});
|
||||
}
|
||||
|
||||
const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
|
||||
const viewportScale = this.scale * PixelsPerInch.PDF_TO_CSS_UNITS;
|
||||
this.viewport = this.viewport.clone({
|
||||
scale: viewportScale,
|
||||
scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,
|
||||
rotation: totalRotation,
|
||||
});
|
||||
|
||||
if (this._isStandalone) {
|
||||
const { style } = document.documentElement;
|
||||
style.setProperty("--zoom-factor", this.scale);
|
||||
style.setProperty("--viewport-scale-factor", viewportScale);
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("!PRODUCTION || GENERIC")) &&
|
||||
this._isStandalone
|
||||
) {
|
||||
docStyle.setProperty("--scale-factor", this.viewport.scale);
|
||||
}
|
||||
|
||||
if (this.svg) {
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("!PRODUCTION || GENERIC")) &&
|
||||
this.svg
|
||||
) {
|
||||
this.cssTransform({
|
||||
target: this.svg,
|
||||
redrawAnnotationLayer: true,
|
||||
redrawAnnotationEditorLayer: true,
|
||||
redrawXfaLayer: true,
|
||||
});
|
||||
|
||||
|
|
@ -345,7 +467,7 @@ class PDFPageView {
|
|||
const outputScale = this.outputScale;
|
||||
if (
|
||||
((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
|
||||
((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
|
||||
((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
|
||||
this.maxCanvasPixels
|
||||
) {
|
||||
isScalingRestricted = true;
|
||||
|
|
@ -360,6 +482,7 @@ class PDFPageView {
|
|||
this.cssTransform({
|
||||
target: this.canvas,
|
||||
redrawAnnotationLayer: true,
|
||||
redrawAnnotationEditorLayer: true,
|
||||
redrawXfaLayer: true,
|
||||
});
|
||||
|
||||
|
|
@ -383,6 +506,7 @@ class PDFPageView {
|
|||
this.reset({
|
||||
keepZoomLayer: true,
|
||||
keepAnnotationLayer: true,
|
||||
keepAnnotationEditorLayer: true,
|
||||
keepXfaLayer: true,
|
||||
});
|
||||
}
|
||||
|
|
@ -391,7 +515,11 @@ class PDFPageView {
|
|||
* PLEASE NOTE: Most likely you want to use the `this.reset()` method,
|
||||
* rather than calling this one directly.
|
||||
*/
|
||||
cancelRendering({ keepAnnotationLayer = false, keepXfaLayer = false } = {}) {
|
||||
cancelRendering({
|
||||
keepAnnotationLayer = false,
|
||||
keepAnnotationEditorLayer = false,
|
||||
keepXfaLayer = false,
|
||||
} = {}) {
|
||||
if (this.paintTask) {
|
||||
this.paintTask.cancel();
|
||||
this.paintTask = null;
|
||||
|
|
@ -410,6 +538,13 @@ class PDFPageView {
|
|||
this.annotationLayer = null;
|
||||
this._annotationCanvasMap = null;
|
||||
}
|
||||
if (
|
||||
this.annotationEditorLayer &&
|
||||
(!keepAnnotationEditorLayer || !this.annotationEditorLayer.div)
|
||||
) {
|
||||
this.annotationEditorLayer.cancel();
|
||||
this.annotationEditorLayer = null;
|
||||
}
|
||||
if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) {
|
||||
this.xfaLayer.cancel();
|
||||
this.xfaLayer = null;
|
||||
|
|
@ -422,22 +557,23 @@ class PDFPageView {
|
|||
}
|
||||
|
||||
cssTransform({
|
||||
target,
|
||||
redrawAnnotationLayer = false,
|
||||
redrawXfaLayer = false,
|
||||
}) {
|
||||
target,
|
||||
redrawAnnotationLayer = false,
|
||||
redrawAnnotationEditorLayer = false,
|
||||
redrawXfaLayer = false,
|
||||
}) {
|
||||
// Scale target (canvas or svg), its wrapper and page container.
|
||||
const width = this.viewport.width;
|
||||
const height = this.viewport.height;
|
||||
const div = this.div;
|
||||
target.style.width =
|
||||
target.parentNode.style.width =
|
||||
div.style.width =
|
||||
Math.floor(width) + "px";
|
||||
div.style.width =
|
||||
Math.floor(width) + "px";
|
||||
target.style.height =
|
||||
target.parentNode.style.height =
|
||||
div.style.height =
|
||||
Math.floor(height) + "px";
|
||||
div.style.height =
|
||||
Math.floor(height) + "px";
|
||||
// The canvas may have been originally rotated; rotate relative to that.
|
||||
const relativeRotation =
|
||||
this.viewport.rotation - this.paintedViewportMap.get(target).rotation;
|
||||
|
|
@ -497,6 +633,9 @@ class PDFPageView {
|
|||
if (redrawAnnotationLayer && this.annotationLayer) {
|
||||
this._renderAnnotationLayer();
|
||||
}
|
||||
if (redrawAnnotationEditorLayer && this.annotationEditorLayer) {
|
||||
this._renderAnnotationEditorLayer();
|
||||
}
|
||||
if (redrawXfaLayer && this.xfaLayer) {
|
||||
this._renderXfaLayer();
|
||||
}
|
||||
|
|
@ -547,34 +686,38 @@ class PDFPageView {
|
|||
canvasWrapper.style.height = div.style.height;
|
||||
canvasWrapper.classList.add("canvasWrapper");
|
||||
|
||||
if (this.annotationLayer?.div) {
|
||||
const lastDivBeforeTextDiv =
|
||||
this.annotationLayer?.div || this.annotationEditorLayer?.div;
|
||||
|
||||
if (lastDivBeforeTextDiv) {
|
||||
// The annotation layer needs to stay on top.
|
||||
div.insertBefore(canvasWrapper, this.annotationLayer.div);
|
||||
lastDivBeforeTextDiv.before(canvasWrapper);
|
||||
} else {
|
||||
div.appendChild(canvasWrapper);
|
||||
div.append(canvasWrapper);
|
||||
}
|
||||
|
||||
let textLayer = null;
|
||||
if (this.textLayerMode !== TextLayerMode.DISABLE && this.textLayerFactory) {
|
||||
this._accessibilityManager ||= new TextAccessibilityManager();
|
||||
const textLayerDiv = document.createElement("div");
|
||||
textLayerDiv.className = "textLayer";
|
||||
textLayerDiv.style.width = canvasWrapper.style.width;
|
||||
textLayerDiv.style.height = canvasWrapper.style.height;
|
||||
if (this.annotationLayer?.div) {
|
||||
if (lastDivBeforeTextDiv) {
|
||||
// The annotation layer needs to stay on top.
|
||||
div.insertBefore(textLayerDiv, this.annotationLayer.div);
|
||||
lastDivBeforeTextDiv.before(textLayerDiv);
|
||||
} else {
|
||||
div.appendChild(textLayerDiv);
|
||||
div.append(textLayerDiv);
|
||||
}
|
||||
|
||||
textLayer = this.textLayerFactory.createTextLayerBuilder(
|
||||
textLayer = this.textLayerFactory.createTextLayerBuilder({
|
||||
textLayerDiv,
|
||||
this.id - 1,
|
||||
this.viewport,
|
||||
this.textLayerMode === TextLayerMode.ENABLE_ENHANCE,
|
||||
this.eventBus,
|
||||
this.textHighlighter
|
||||
);
|
||||
pageIndex: this.id - 1,
|
||||
viewport: this.viewport,
|
||||
eventBus: this.eventBus,
|
||||
highlighter: this.textHighlighter,
|
||||
accessibilityManager: this._accessibilityManager,
|
||||
});
|
||||
}
|
||||
this.textLayer = textLayer;
|
||||
|
||||
|
|
@ -584,24 +727,20 @@ class PDFPageView {
|
|||
) {
|
||||
this._annotationCanvasMap ||= new Map();
|
||||
this.annotationLayer ||=
|
||||
this.annotationLayerFactory.createAnnotationLayerBuilder(
|
||||
div,
|
||||
this.annotationLayerFactory.createAnnotationLayerBuilder({
|
||||
pageDiv: div,
|
||||
pdfPage,
|
||||
/* annotationStorage = */ null,
|
||||
this.imageResourcesPath,
|
||||
this.#annotationMode === AnnotationMode.ENABLE_FORMS,
|
||||
this.l10n,
|
||||
/* enableScripting = */ null,
|
||||
/* hasJSActionsPromise = */ null,
|
||||
/* mouseState = */ null,
|
||||
/* fieldObjectsPromise = */ null,
|
||||
/* annotationCanvasMap */ this._annotationCanvasMap
|
||||
);
|
||||
imageResourcesPath: this.imageResourcesPath,
|
||||
renderForms: this.#annotationMode === AnnotationMode.ENABLE_FORMS,
|
||||
l10n: this.l10n,
|
||||
annotationCanvasMap: this._annotationCanvasMap,
|
||||
accessibilityManager: this._accessibilityManager,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.xfaLayer?.div) {
|
||||
// The xfa layer needs to stay on top.
|
||||
div.appendChild(this.xfaLayer.div);
|
||||
div.append(this.xfaLayer.div);
|
||||
}
|
||||
|
||||
let renderContinueCallback = null;
|
||||
|
|
@ -641,6 +780,10 @@ class PDFPageView {
|
|||
}
|
||||
this._resetZoomLayer(/* removeFromDOM = */ true);
|
||||
|
||||
// Ensure that the thumbnails won't become partially (or fully) blank,
|
||||
// for documents that contain interactive form elements.
|
||||
this.#useThumbnailCanvas.regularAnnotations = !paintTask.separateAnnots;
|
||||
|
||||
this.eventBus.dispatch("pagerendered", {
|
||||
source: this,
|
||||
pageNumber: this.id,
|
||||
|
|
@ -655,6 +798,8 @@ class PDFPageView {
|
|||
};
|
||||
|
||||
const paintTask =
|
||||
(typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("!PRODUCTION || GENERIC")) &&
|
||||
this.renderer === RendererType.SVG
|
||||
? this.paintOnSvg(canvasWrapper)
|
||||
: this.paintOnCanvas(canvasWrapper);
|
||||
|
|
@ -673,7 +818,20 @@ class PDFPageView {
|
|||
}
|
||||
|
||||
if (this.annotationLayer) {
|
||||
this._renderAnnotationLayer();
|
||||
this._renderAnnotationLayer().then(() => {
|
||||
if (this.annotationEditorLayerFactory) {
|
||||
this.annotationEditorLayer ||=
|
||||
this.annotationEditorLayerFactory.createAnnotationEditorLayerBuilder(
|
||||
{
|
||||
pageDiv: div,
|
||||
pdfPage,
|
||||
l10n: this.l10n,
|
||||
accessibilityManager: this._accessibilityManager,
|
||||
}
|
||||
);
|
||||
this._renderAnnotationEditorLayer();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
@ -683,13 +841,10 @@ class PDFPageView {
|
|||
);
|
||||
|
||||
if (this.xfaLayerFactory) {
|
||||
if (!this.xfaLayer) {
|
||||
this.xfaLayer = this.xfaLayerFactory.createXfaLayerBuilder(
|
||||
div,
|
||||
pdfPage,
|
||||
/* annotationStorage = */ null
|
||||
);
|
||||
}
|
||||
this.xfaLayer ||= this.xfaLayerFactory.createXfaLayerBuilder({
|
||||
pageDiv: div,
|
||||
pdfPage,
|
||||
});
|
||||
this._renderXfaLayer();
|
||||
}
|
||||
|
||||
|
|
@ -717,12 +872,12 @@ class PDFPageView {
|
|||
}
|
||||
const treeDom = this.structTreeLayer.render(tree);
|
||||
treeDom.classList.add("structTree");
|
||||
this.canvas.appendChild(treeDom);
|
||||
this.canvas.append(treeDom);
|
||||
});
|
||||
};
|
||||
this.eventBus._on("textlayerrendered", this._onTextLayerRendered);
|
||||
this.structTreeLayer =
|
||||
this.structTreeLayerFactory.createStructTreeLayerBuilder(pdfPage);
|
||||
this.structTreeLayerFactory.createStructTreeLayerBuilder({ pdfPage });
|
||||
}
|
||||
|
||||
div.setAttribute("data-loaded", true);
|
||||
|
|
@ -744,10 +899,14 @@ class PDFPageView {
|
|||
cancel() {
|
||||
renderTask.cancel();
|
||||
},
|
||||
get separateAnnots() {
|
||||
return renderTask.separateAnnots;
|
||||
},
|
||||
};
|
||||
|
||||
const viewport = this.viewport;
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.setAttribute("role", "presentation");
|
||||
|
||||
// Keep the canvas hidden until the first draw callback, or until drawing
|
||||
// is complete when `!this.renderingQueue`, to prevent black flickering.
|
||||
|
|
@ -760,16 +919,9 @@ class PDFPageView {
|
|||
}
|
||||
};
|
||||
|
||||
canvasWrapper.appendChild(canvas);
|
||||
canvasWrapper.append(canvas);
|
||||
this.canvas = canvas;
|
||||
|
||||
if (
|
||||
typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("MOZCENTRAL || GENERIC")
|
||||
) {
|
||||
canvas.mozOpaque = true;
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext("2d", { alpha: false });
|
||||
const outputScale = (this.outputScale = new OutputScale());
|
||||
|
||||
|
|
@ -816,6 +968,7 @@ class PDFPageView {
|
|||
annotationMode: this.#annotationMode,
|
||||
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
||||
annotationCanvasMap: this._annotationCanvasMap,
|
||||
pageColors: this.pageColors,
|
||||
};
|
||||
const renderTask = this.pdfPage.render(renderContext);
|
||||
renderTask.onContinue = function (cont) {
|
||||
|
|
@ -842,18 +995,13 @@ class PDFPageView {
|
|||
|
||||
paintOnSvg(wrapper) {
|
||||
if (
|
||||
typeof PDFJSDev !== "undefined" &&
|
||||
PDFJSDev.test("MOZCENTRAL || CHROME")
|
||||
!(
|
||||
typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("!PRODUCTION || GENERIC")
|
||||
)
|
||||
) {
|
||||
// Return a mock object, to prevent errors such as e.g.
|
||||
// "TypeError: paintTask.promise is undefined".
|
||||
return {
|
||||
promise: Promise.reject(new Error("SVG rendering is not supported.")),
|
||||
onRenderContinue(cont) {},
|
||||
cancel() {},
|
||||
};
|
||||
throw new Error("Not implemented: paintOnSvg");
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
const ensureNotCancelled = () => {
|
||||
if (cancelled) {
|
||||
|
|
@ -869,23 +1017,23 @@ class PDFPageView {
|
|||
scale: PixelsPerInch.PDF_TO_CSS_UNITS,
|
||||
});
|
||||
const promise = pdfPage
|
||||
.getOperatorList({
|
||||
annotationMode: this.#annotationMode,
|
||||
})
|
||||
.then(opList => {
|
||||
ensureNotCancelled();
|
||||
const svgGfx = new SVGGraphics(pdfPage.commonObjs, pdfPage.objs);
|
||||
return svgGfx.getSVG(opList, actualSizeViewport).then(svg => {
|
||||
.getOperatorList({
|
||||
annotationMode: this.#annotationMode,
|
||||
})
|
||||
.then(opList => {
|
||||
ensureNotCancelled();
|
||||
this.svg = svg;
|
||||
this.paintedViewportMap.set(svg, actualSizeViewport);
|
||||
const svgGfx = new SVGGraphics(pdfPage.commonObjs, pdfPage.objs);
|
||||
return svgGfx.getSVG(opList, actualSizeViewport).then(svg => {
|
||||
ensureNotCancelled();
|
||||
this.svg = svg;
|
||||
this.paintedViewportMap.set(svg, actualSizeViewport);
|
||||
|
||||
svg.style.width = wrapper.style.width;
|
||||
svg.style.height = wrapper.style.height;
|
||||
this.renderingState = RenderingStates.FINISHED;
|
||||
wrapper.appendChild(svg);
|
||||
svg.style.width = wrapper.style.width;
|
||||
svg.style.height = wrapper.style.height;
|
||||
this.renderingState = RenderingStates.FINISHED;
|
||||
wrapper.append(svg);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
promise,
|
||||
|
|
@ -895,6 +1043,9 @@ class PDFPageView {
|
|||
cancel() {
|
||||
cancelled = true;
|
||||
},
|
||||
get separateAnnots() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -910,6 +1061,16 @@ class PDFPageView {
|
|||
this.div.removeAttribute("data-page-label");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For use by the `PDFThumbnailView.setImage`-method.
|
||||
* @ignore
|
||||
*/
|
||||
get thumbnailCanvas() {
|
||||
const { initialOptionalContent, regularAnnotations } =
|
||||
this.#useThumbnailCanvas;
|
||||
return initialOptionalContent && regularAnnotations ? this.canvas : null;
|
||||
}
|
||||
}
|
||||
|
||||
export { PDFPageView };
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import {
|
|||
ScrollMode,
|
||||
SpreadMode,
|
||||
} from "./ui_utils.js";
|
||||
import { AnnotationEditorType } from "./pdfjs";
|
||||
|
||||
const DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1500; // in ms
|
||||
const DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms
|
||||
const ACTIVE_SELECTOR = "pdfPresentationMode";
|
||||
const CONTROLS_SELECTOR = "pdfPresentationModeControls";
|
||||
|
|
@ -42,6 +42,10 @@ const SWIPE_ANGLE_THRESHOLD = Math.PI / 6;
|
|||
*/
|
||||
|
||||
class PDFPresentationMode {
|
||||
#state = PresentationModeState.UNKNOWN;
|
||||
|
||||
#args = null;
|
||||
|
||||
/**
|
||||
* @param {PDFPresentationModeOptions} options
|
||||
*/
|
||||
|
|
@ -50,8 +54,6 @@ class PDFPresentationMode {
|
|||
this.pdfViewer = pdfViewer;
|
||||
this.eventBus = eventBus;
|
||||
|
||||
this.active = false;
|
||||
this.args = null;
|
||||
this.contextMenuOpen = false;
|
||||
this.mouseScrollTimeStamp = 0;
|
||||
this.mouseScrollDelta = 0;
|
||||
|
|
@ -60,37 +62,63 @@ class PDFPresentationMode {
|
|||
|
||||
/**
|
||||
* Request the browser to enter fullscreen mode.
|
||||
* @returns {boolean} Indicating if the request was successful.
|
||||
* @returns {Promise<boolean>} Indicating if the request was successful.
|
||||
*/
|
||||
request() {
|
||||
if (
|
||||
this.switchInProgress ||
|
||||
this.active ||
|
||||
!this.pdfViewer.pagesCount ||
|
||||
!this.container.requestFullscreen
|
||||
) {
|
||||
async request() {
|
||||
const { container, pdfViewer } = this;
|
||||
|
||||
if (this.active || !pdfViewer.pagesCount || !container.requestFullscreen) {
|
||||
return false;
|
||||
}
|
||||
this.#addFullscreenChangeListeners();
|
||||
this.#setSwitchInProgress();
|
||||
this.#notifyStateChange();
|
||||
this.#notifyStateChange(PresentationModeState.CHANGING);
|
||||
|
||||
this.container.requestFullscreen();
|
||||
const promise = container.requestFullscreen();
|
||||
|
||||
this.args = {
|
||||
pageNumber: this.pdfViewer.currentPageNumber,
|
||||
scaleValue: this.pdfViewer.currentScaleValue,
|
||||
scrollMode: this.pdfViewer.scrollMode,
|
||||
spreadMode: this.pdfViewer.spreadMode,
|
||||
this.#args = {
|
||||
pageNumber: pdfViewer.currentPageNumber,
|
||||
scaleValue: pdfViewer.currentScaleValue,
|
||||
scrollMode: pdfViewer.scrollMode,
|
||||
spreadMode: null,
|
||||
annotationEditorMode: null,
|
||||
};
|
||||
return true;
|
||||
|
||||
if (
|
||||
pdfViewer.spreadMode !== SpreadMode.NONE &&
|
||||
!(pdfViewer.pageViewsReady && pdfViewer.hasEqualPageSizes)
|
||||
) {
|
||||
console.warn(
|
||||
"Ignoring Spread modes when entering PresentationMode, " +
|
||||
"since the document may contain varying page sizes."
|
||||
);
|
||||
this.#args.spreadMode = pdfViewer.spreadMode;
|
||||
}
|
||||
if (pdfViewer.annotationEditorMode !== AnnotationEditorType.DISABLE) {
|
||||
this.#args.annotationEditorMode = pdfViewer.annotationEditorMode;
|
||||
}
|
||||
|
||||
try {
|
||||
await promise;
|
||||
pdfViewer.focus(); // Fixes bug 1787456.
|
||||
return true;
|
||||
} catch (reason) {
|
||||
this.#removeFullscreenChangeListeners();
|
||||
this.#notifyStateChange(PresentationModeState.NORMAL);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get active() {
|
||||
return (
|
||||
this.#state === PresentationModeState.CHANGING ||
|
||||
this.#state === PresentationModeState.FULLSCREEN
|
||||
);
|
||||
}
|
||||
|
||||
#mouseWheel(evt) {
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
evt.preventDefault();
|
||||
|
||||
const delta = normalizeWheelEventDelta(evt);
|
||||
|
|
@ -126,57 +154,29 @@ class PDFPresentationMode {
|
|||
}
|
||||
}
|
||||
|
||||
#notifyStateChange() {
|
||||
let state = PresentationModeState.NORMAL;
|
||||
if (this.switchInProgress) {
|
||||
state = PresentationModeState.CHANGING;
|
||||
} else if (this.active) {
|
||||
state = PresentationModeState.FULLSCREEN;
|
||||
}
|
||||
this.eventBus.dispatch("presentationmodechanged", {
|
||||
source: this,
|
||||
state,
|
||||
});
|
||||
}
|
||||
#notifyStateChange(state) {
|
||||
this.#state = state;
|
||||
|
||||
/**
|
||||
* Used to initialize a timeout when requesting Presentation Mode,
|
||||
* i.e. when the browser is requested to enter fullscreen mode.
|
||||
* This timeout is used to prevent the current page from being scrolled
|
||||
* partially, or completely, out of view when entering Presentation Mode.
|
||||
* NOTE: This issue seems limited to certain zoom levels (e.g. page-width).
|
||||
*/
|
||||
#setSwitchInProgress() {
|
||||
if (this.switchInProgress) {
|
||||
clearTimeout(this.switchInProgress);
|
||||
}
|
||||
this.switchInProgress = setTimeout(() => {
|
||||
this.#removeFullscreenChangeListeners();
|
||||
delete this.switchInProgress;
|
||||
this.#notifyStateChange();
|
||||
}, DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS);
|
||||
}
|
||||
|
||||
#resetSwitchInProgress() {
|
||||
if (this.switchInProgress) {
|
||||
clearTimeout(this.switchInProgress);
|
||||
delete this.switchInProgress;
|
||||
}
|
||||
this.eventBus.dispatch("presentationmodechanged", { source: this, state });
|
||||
}
|
||||
|
||||
#enter() {
|
||||
this.active = true;
|
||||
this.#resetSwitchInProgress();
|
||||
this.#notifyStateChange();
|
||||
this.#notifyStateChange(PresentationModeState.FULLSCREEN);
|
||||
this.container.classList.add(ACTIVE_SELECTOR);
|
||||
|
||||
// Ensure that the correct page is scrolled into view when entering
|
||||
// Presentation Mode, by waiting until fullscreen mode in enabled.
|
||||
setTimeout(() => {
|
||||
this.pdfViewer.scrollMode = ScrollMode.PAGE;
|
||||
this.pdfViewer.spreadMode = SpreadMode.NONE;
|
||||
this.pdfViewer.currentPageNumber = this.args.pageNumber;
|
||||
if (this.#args.spreadMode !== null) {
|
||||
this.pdfViewer.spreadMode = SpreadMode.NONE;
|
||||
}
|
||||
this.pdfViewer.currentPageNumber = this.#args.pageNumber;
|
||||
this.pdfViewer.currentScaleValue = "page-fit";
|
||||
|
||||
if (this.#args.annotationEditorMode !== null) {
|
||||
this.pdfViewer.annotationEditorMode = AnnotationEditorType.NONE;
|
||||
}
|
||||
}, 0);
|
||||
|
||||
this.#addWindowListeners();
|
||||
|
|
@ -196,15 +196,20 @@ class PDFPresentationMode {
|
|||
// Ensure that the correct page is scrolled into view when exiting
|
||||
// Presentation Mode, by waiting until fullscreen mode is disabled.
|
||||
setTimeout(() => {
|
||||
this.active = false;
|
||||
this.#removeFullscreenChangeListeners();
|
||||
this.#notifyStateChange();
|
||||
this.#notifyStateChange(PresentationModeState.NORMAL);
|
||||
|
||||
this.pdfViewer.scrollMode = this.args.scrollMode;
|
||||
this.pdfViewer.spreadMode = this.args.spreadMode;
|
||||
this.pdfViewer.currentScaleValue = this.args.scaleValue;
|
||||
this.pdfViewer.scrollMode = this.#args.scrollMode;
|
||||
if (this.#args.spreadMode !== null) {
|
||||
this.pdfViewer.spreadMode = this.#args.spreadMode;
|
||||
}
|
||||
this.pdfViewer.currentScaleValue = this.#args.scaleValue;
|
||||
this.pdfViewer.currentPageNumber = pageNumber;
|
||||
this.args = null;
|
||||
|
||||
if (this.#args.annotationEditorMode !== null) {
|
||||
this.pdfViewer.annotationEditorMode = this.#args.annotationEditorMode;
|
||||
}
|
||||
this.#args = null;
|
||||
}, 0);
|
||||
|
||||
this.#removeWindowListeners();
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @typedef {import("./interfaces").IRenderableView} IRenderableView */
|
||||
/** @typedef {import("./pdf_viewer").PDFViewer} PDFViewer */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./pdf_thumbnail_viewer").PDFThumbnailViewer} PDFThumbnailViewer */
|
||||
|
||||
import { RenderingCancelledException } from "./pdfjs";
|
||||
import { RenderingStates } from "./ui_utils.js";
|
||||
|
||||
|
|
@ -189,16 +194,16 @@ class PDFRenderingQueue {
|
|||
case RenderingStates.INITIAL:
|
||||
this.highestPriorityPage = view.renderingId;
|
||||
view
|
||||
.draw()
|
||||
.finally(() => {
|
||||
this.renderHighestPriority();
|
||||
})
|
||||
.catch(reason => {
|
||||
if (reason instanceof RenderingCancelledException) {
|
||||
return;
|
||||
}
|
||||
console.error(`renderView: "${reason}"`);
|
||||
});
|
||||
.draw()
|
||||
.finally(() => {
|
||||
this.renderHighestPriority();
|
||||
})
|
||||
.catch(reason => {
|
||||
if (reason instanceof RenderingCancelledException) {
|
||||
return;
|
||||
}
|
||||
console.error(`renderView: "${reason}"`);
|
||||
});
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||
|
||||
import { apiPageLayoutToViewerModes, RenderingStates } from "./ui_utils.js";
|
||||
import { createPromiseCapability, shadow } from "./pdfjs";
|
||||
|
||||
|
|
@ -33,11 +35,11 @@ class PDFScriptingManager {
|
|||
* @param {PDFScriptingManagerOptions} options
|
||||
*/
|
||||
constructor({
|
||||
eventBus,
|
||||
sandboxBundleSrc = null,
|
||||
scriptingFactory = null,
|
||||
docPropertiesLookup = null,
|
||||
}) {
|
||||
eventBus,
|
||||
sandboxBundleSrc = null,
|
||||
scriptingFactory = null,
|
||||
docPropertiesLookup = null,
|
||||
}) {
|
||||
this._pdfDocument = null;
|
||||
this._pdfViewer = null;
|
||||
this._closeCapability = null;
|
||||
|
|
@ -152,7 +154,7 @@ class PDFScriptingManager {
|
|||
this._eventBus._on(name, listener);
|
||||
}
|
||||
for (const [name, listener] of this._domEvents) {
|
||||
window.addEventListener(name, listener);
|
||||
window.addEventListener(name, listener, true);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -309,7 +311,7 @@ class PDFScriptingManager {
|
|||
this._pdfViewer.currentScaleValue = value;
|
||||
break;
|
||||
case "SaveAs":
|
||||
this._eventBus.dispatch("save", { source: this });
|
||||
this._eventBus.dispatch("download", { source: this });
|
||||
break;
|
||||
case "FirstPage":
|
||||
this._pdfViewer.currentPageNumber = 1;
|
||||
|
|
@ -349,7 +351,9 @@ class PDFScriptingManager {
|
|||
|
||||
const ids = siblings ? [id, ...siblings] : [id];
|
||||
for (const elementId of ids) {
|
||||
const element = document.getElementById(elementId);
|
||||
const element = document.querySelector(
|
||||
`[data-element-id="${elementId}"]`
|
||||
);
|
||||
if (element) {
|
||||
element.dispatchEvent(new CustomEvent("updatefromsandbox", { detail }));
|
||||
} else {
|
||||
|
|
@ -505,7 +509,7 @@ class PDFScriptingManager {
|
|||
this._internalEvents.clear();
|
||||
|
||||
for (const [name, listener] of this._domEvents) {
|
||||
window.removeEventListener(name, listener);
|
||||
window.removeEventListener(name, listener, true);
|
||||
}
|
||||
this._domEvents.clear();
|
||||
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ const UI_NOTIFICATION_CLASS = "pdfSidebarNotification";
|
|||
* @typedef {Object} PDFSidebarElements
|
||||
* @property {HTMLDivElement} outerContainer - The outer container
|
||||
* (encasing both the viewer and sidebar elements).
|
||||
* @property {HTMLDivElement} viewerContainer - The viewer container
|
||||
* (in which the viewer element is placed).
|
||||
* @property {HTMLDivElement} sidebarContainer - The sidebar container
|
||||
* (in which the views are placed).
|
||||
* @property {HTMLButtonElement} toggleButton - The button used for
|
||||
* opening/closing the sidebar.
|
||||
* @property {HTMLButtonElement} thumbnailButton - The button used to show
|
||||
|
|
@ -68,6 +68,7 @@ class PDFSidebar {
|
|||
this.isOpen = false;
|
||||
this.active = SidebarView.THUMBS;
|
||||
this.isInitialViewSet = false;
|
||||
this.isInitialEventDispatched = false;
|
||||
|
||||
/**
|
||||
* Callback used when the sidebar has been opened/closed, to ensure that
|
||||
|
|
@ -79,7 +80,7 @@ class PDFSidebar {
|
|||
this.pdfThumbnailViewer = pdfThumbnailViewer;
|
||||
|
||||
this.outerContainer = elements.outerContainer;
|
||||
this.viewerContainer = elements.viewerContainer;
|
||||
this.sidebarContainer = elements.sidebarContainer;
|
||||
this.toggleButton = elements.toggleButton;
|
||||
|
||||
this.thumbnailButton = elements.thumbnailButton;
|
||||
|
|
@ -98,13 +99,14 @@ class PDFSidebar {
|
|||
this.eventBus = eventBus;
|
||||
this.l10n = l10n;
|
||||
|
||||
this._addEventListeners();
|
||||
this.#addEventListeners();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.isInitialViewSet = false;
|
||||
this.isInitialEventDispatched = false;
|
||||
|
||||
this._hideUINotification(/* reset = */ true);
|
||||
this.#hideUINotification(/* reset = */ true);
|
||||
this.switchView(SidebarView.THUMBS);
|
||||
|
||||
this.outlineButton.disabled = false;
|
||||
|
|
@ -120,22 +122,6 @@ class PDFSidebar {
|
|||
return this.isOpen ? this.active : SidebarView.NONE;
|
||||
}
|
||||
|
||||
get isThumbnailViewVisible() {
|
||||
return this.isOpen && this.active === SidebarView.THUMBS;
|
||||
}
|
||||
|
||||
get isOutlineViewVisible() {
|
||||
return this.isOpen && this.active === SidebarView.OUTLINE;
|
||||
}
|
||||
|
||||
get isAttachmentsViewVisible() {
|
||||
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}.
|
||||
|
|
@ -149,13 +135,15 @@ class PDFSidebar {
|
|||
// If the user has already manually opened the sidebar, immediately closing
|
||||
// it would be bad UX; also ignore the "unknown" sidebar view value.
|
||||
if (view === SidebarView.NONE || view === SidebarView.UNKNOWN) {
|
||||
this._dispatchEvent();
|
||||
this.#dispatchEvent();
|
||||
return;
|
||||
}
|
||||
// Prevent dispatching two back-to-back `sidebarviewchanged` events,
|
||||
// since `this._switchView` dispatched the event if the view changed.
|
||||
if (!this._switchView(view, /* forceOpen */ true)) {
|
||||
this._dispatchEvent();
|
||||
this.switchView(view, /* forceOpen = */ true);
|
||||
|
||||
// Prevent dispatching two back-to-back "sidebarviewchanged" events,
|
||||
// since `this.switchView` dispatched the event if the view changed.
|
||||
if (!this.isInitialEventDispatched) {
|
||||
this.#dispatchEvent();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -166,14 +154,6 @@ class PDFSidebar {
|
|||
* The default value is `false`.
|
||||
*/
|
||||
switchView(view, forceOpen = false) {
|
||||
this._switchView(view, forceOpen);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} Indicating if `this._dispatchEvent` was called.
|
||||
* @private
|
||||
*/
|
||||
_switchView(view, forceOpen = false) {
|
||||
const isViewChanged = view !== this.active;
|
||||
let shouldForceRendering = false;
|
||||
|
||||
|
|
@ -181,9 +161,8 @@ class PDFSidebar {
|
|||
case SidebarView.NONE:
|
||||
if (this.isOpen) {
|
||||
this.close();
|
||||
return true; // Closing will trigger rendering and dispatch the event.
|
||||
}
|
||||
return false;
|
||||
return; // Closing will trigger rendering and dispatch the event.
|
||||
case SidebarView.THUMBS:
|
||||
if (this.isOpen && isViewChanged) {
|
||||
shouldForceRendering = true;
|
||||
|
|
@ -191,22 +170,22 @@ class PDFSidebar {
|
|||
break;
|
||||
case SidebarView.OUTLINE:
|
||||
if (this.outlineButton.disabled) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case SidebarView.ATTACHMENTS:
|
||||
if (this.attachmentsButton.disabled) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case SidebarView.LAYERS:
|
||||
if (this.layersButton.disabled) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error(`PDFSidebar._switchView: "${view}" is not a valid view.`);
|
||||
return false;
|
||||
console.error(`PDFSidebar.switchView: "${view}" is not a valid view.`);
|
||||
return;
|
||||
}
|
||||
// Update the active view *after* it has been validated above,
|
||||
// in order to prevent setting it to an invalid state.
|
||||
|
|
@ -223,31 +202,32 @@ class PDFSidebar {
|
|||
this.attachmentsButton.classList.toggle("toggled", isAttachments);
|
||||
this.layersButton.classList.toggle("toggled", isLayers);
|
||||
|
||||
this.thumbnailButton.setAttribute("aria-checked", `${isThumbs}`);
|
||||
this.outlineButton.setAttribute("aria-checked", `${isOutline}`);
|
||||
this.attachmentsButton.setAttribute("aria-checked", `${isAttachments}`);
|
||||
this.layersButton.setAttribute("aria-checked", `${isLayers}`);
|
||||
this.thumbnailButton.setAttribute("aria-checked", isThumbs);
|
||||
this.outlineButton.setAttribute("aria-checked", isOutline);
|
||||
this.attachmentsButton.setAttribute("aria-checked", isAttachments);
|
||||
this.layersButton.setAttribute("aria-checked", isLayers);
|
||||
// ... and for all views.
|
||||
// NOTE
|
||||
this.thumbnailView.classList.toggle("fn__hidden", !isThumbs);
|
||||
this.outlineView.classList.toggle("fn__hidden", !isOutline);
|
||||
this.attachmentsView.classList.toggle("fn__hidden", !isAttachments);
|
||||
this.layersView.classList.toggle("fn__hidden", !isLayers);
|
||||
|
||||
// Finally, update view-specific CSS classes.
|
||||
// NOTE
|
||||
this._outlineOptionsContainer.classList.toggle("fn__hidden", !isOutline);
|
||||
|
||||
if (forceOpen && !this.isOpen) {
|
||||
this.open();
|
||||
return true; // Opening will trigger rendering and dispatch the event.
|
||||
return; // Opening will trigger rendering and dispatch the event.
|
||||
}
|
||||
if (shouldForceRendering) {
|
||||
this._updateThumbnailViewer();
|
||||
this._forceRendering();
|
||||
this.#updateThumbnailViewer();
|
||||
this.#forceRendering();
|
||||
}
|
||||
if (isViewChanged) {
|
||||
this._dispatchEvent();
|
||||
this.#dispatchEvent();
|
||||
}
|
||||
return isViewChanged;
|
||||
}
|
||||
|
||||
open() {
|
||||
|
|
@ -261,12 +241,12 @@ class PDFSidebar {
|
|||
this.outerContainer.classList.add("sidebarMoving", "sidebarOpen");
|
||||
|
||||
if (this.active === SidebarView.THUMBS) {
|
||||
this._updateThumbnailViewer();
|
||||
this.#updateThumbnailViewer();
|
||||
}
|
||||
this._forceRendering();
|
||||
this._dispatchEvent();
|
||||
this.#forceRendering();
|
||||
this.#dispatchEvent();
|
||||
|
||||
this._hideUINotification();
|
||||
this.#hideUINotification();
|
||||
}
|
||||
|
||||
close() {
|
||||
|
|
@ -280,8 +260,8 @@ class PDFSidebar {
|
|||
this.outerContainer.classList.add("sidebarMoving");
|
||||
this.outerContainer.classList.remove("sidebarOpen");
|
||||
|
||||
this._forceRendering();
|
||||
this._dispatchEvent();
|
||||
this.#forceRendering();
|
||||
this.#dispatchEvent();
|
||||
}
|
||||
|
||||
toggle() {
|
||||
|
|
@ -292,20 +272,18 @@ class PDFSidebar {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_dispatchEvent() {
|
||||
#dispatchEvent() {
|
||||
if (this.isInitialViewSet && !this.isInitialEventDispatched) {
|
||||
this.isInitialEventDispatched = true;
|
||||
}
|
||||
|
||||
this.eventBus.dispatch("sidebarviewchanged", {
|
||||
source: this,
|
||||
view: this.visibleView,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_forceRendering() {
|
||||
#forceRendering() {
|
||||
if (this.onToggled) {
|
||||
this.onToggled();
|
||||
} else {
|
||||
|
|
@ -315,10 +293,7 @@ class PDFSidebar {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_updateThumbnailViewer() {
|
||||
#updateThumbnailViewer() {
|
||||
const { pdfViewer, pdfThumbnailViewer } = this;
|
||||
|
||||
// Use the rendered pages to set the corresponding thumbnail images.
|
||||
|
|
@ -333,10 +308,8 @@ class PDFSidebar {
|
|||
pdfThumbnailViewer.scrollThumbnailIntoView(pdfViewer.currentPageNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_showUINotification() {
|
||||
#showUINotification() {
|
||||
// NOTE
|
||||
this.toggleButton.title = window.siyuan.languages.toggleSidebarNotification2Title
|
||||
|
||||
if (!this.isOpen) {
|
||||
|
|
@ -346,10 +319,7 @@ class PDFSidebar {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_hideUINotification(reset = false) {
|
||||
#hideUINotification(reset = false) {
|
||||
if (this.isOpen || reset) {
|
||||
// Only hide the notification on the `toggleButton` if the sidebar is
|
||||
// currently open, or when the current PDF document is being closed.
|
||||
|
|
@ -357,16 +327,14 @@ class PDFSidebar {
|
|||
}
|
||||
|
||||
if (reset) {
|
||||
// NOTE
|
||||
this.toggleButton.title = window.siyuan.languages.toggleSidebarTitle
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_addEventListeners() {
|
||||
this.viewerContainer.addEventListener("transitionend", evt => {
|
||||
if (evt.target === this.viewerContainer) {
|
||||
#addEventListeners() {
|
||||
this.sidebarContainer.addEventListener("transitionend", evt => {
|
||||
if (evt.target === this.sidebarContainer) {
|
||||
this.outerContainer.classList.remove("sidebarMoving");
|
||||
}
|
||||
});
|
||||
|
|
@ -408,7 +376,7 @@ class PDFSidebar {
|
|||
button.disabled = !count;
|
||||
|
||||
if (count) {
|
||||
this._showUINotification();
|
||||
this.#showUINotification();
|
||||
} else if (this.active === view) {
|
||||
// If the `view` was opened by the user during document load,
|
||||
// switch away from it if it turns out to be empty.
|
||||
|
|
@ -443,9 +411,9 @@ class PDFSidebar {
|
|||
this.eventBus._on("presentationmodechanged", evt => {
|
||||
if (
|
||||
evt.state === PresentationModeState.NORMAL &&
|
||||
this.isThumbnailViewVisible
|
||||
this.visibleView === SidebarView.THUMBS
|
||||
) {
|
||||
this._updateThumbnailViewer();
|
||||
this.#updateThumbnailViewer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { docStyle } from "./ui_utils.js";
|
||||
|
||||
// NOTE
|
||||
const SIDEBAR_WIDTH_VAR = "--b3-pdf-sidebar-width";
|
||||
const SIDEBAR_MIN_WIDTH = 200; // pixels
|
||||
const SIDEBAR_RESIZING_CLASS = "sidebarResizing";
|
||||
|
|
@ -34,7 +37,6 @@ class PDFSidebarResizer {
|
|||
constructor(options, eventBus, l10n) {
|
||||
this.isRTL = false;
|
||||
this.sidebarOpen = false;
|
||||
this.doc = document.documentElement;
|
||||
this._width = null;
|
||||
this._outerContainerWidth = null;
|
||||
this._boundEvents = Object.create(null);
|
||||
|
|
@ -42,6 +44,7 @@ class PDFSidebarResizer {
|
|||
this.outerContainer = options.outerContainer;
|
||||
this.resizer = options.resizer;
|
||||
this.eventBus = eventBus;
|
||||
// NOTE
|
||||
this.isRTL = false;
|
||||
this._addEventListeners();
|
||||
}
|
||||
|
|
@ -72,7 +75,8 @@ class PDFSidebarResizer {
|
|||
return false;
|
||||
}
|
||||
this._width = width;
|
||||
this.doc.style.setProperty(SIDEBAR_WIDTH_VAR, `${width}px`);
|
||||
|
||||
docStyle.setProperty(SIDEBAR_WIDTH_VAR, `${width}px`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,13 +13,19 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { OutputScale, RenderingStates } from './ui_utils.js'
|
||||
import { RenderingCancelledException } from './pdfjs'
|
||||
/** @typedef {import("./interfaces").IL10n} IL10n */
|
||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
||||
/** @typedef {import("./interfaces").IRenderableView} IRenderableView */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
||||
|
||||
const DRAW_UPSCALE_FACTOR = 2 // See comment in `PDFThumbnailView.draw` below.
|
||||
const MAX_NUM_SCALING_STEPS = 3
|
||||
const THUMBNAIL_CANVAS_BORDER_WIDTH = 1 // px
|
||||
const THUMBNAIL_WIDTH = 98 // px
|
||||
import { OutputScale, RenderingStates } from "./ui_utils.js";
|
||||
import { RenderingCancelledException } from "./pdfjs";
|
||||
|
||||
const DRAW_UPSCALE_FACTOR = 2; // See comment in `PDFThumbnailView.draw` below.
|
||||
const MAX_NUM_SCALING_STEPS = 3;
|
||||
const THUMBNAIL_CANVAS_BORDER_WIDTH = 1; // px
|
||||
const THUMBNAIL_WIDTH = 98; // px
|
||||
|
||||
/**
|
||||
* @typedef {Object} PDFThumbnailViewOptions
|
||||
|
|
@ -31,44 +37,39 @@ const THUMBNAIL_WIDTH = 98 // px
|
|||
* The default value is `null`.
|
||||
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
||||
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
|
||||
* @property {function} checkSetImageDisabled
|
||||
* @property {IL10n} l10n - Localization service.
|
||||
* @property {Object} [pageColors] - Overwrites background and foreground colors
|
||||
* with user defined ones in order to improve readability in high contrast
|
||||
* mode.
|
||||
*/
|
||||
|
||||
class TempImageFactory {
|
||||
static #tempCanvas = null
|
||||
static #tempCanvas = null;
|
||||
|
||||
static getCanvas (width, height) {
|
||||
const tempCanvas = (this.#tempCanvas ||= document.createElement('canvas'))
|
||||
tempCanvas.width = width
|
||||
tempCanvas.height = height
|
||||
static getCanvas(width, height) {
|
||||
const tempCanvas = (this.#tempCanvas ||= document.createElement("canvas"));
|
||||
tempCanvas.width = width;
|
||||
tempCanvas.height = height;
|
||||
|
||||
// Since this is a temporary canvas, we need to fill it with a white
|
||||
// background ourselves. `_getPageDrawContext` uses CSS rules for this.
|
||||
if (
|
||||
typeof PDFJSDev === 'undefined' ||
|
||||
PDFJSDev.test('MOZCENTRAL || GENERIC')
|
||||
) {
|
||||
tempCanvas.mozOpaque = true
|
||||
}
|
||||
|
||||
const ctx = tempCanvas.getContext('2d', {alpha: false})
|
||||
ctx.save()
|
||||
ctx.fillStyle = 'rgb(255, 255, 255)'
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
ctx.restore()
|
||||
return [tempCanvas, tempCanvas.getContext('2d')]
|
||||
const ctx = tempCanvas.getContext("2d", { alpha: false });
|
||||
ctx.save();
|
||||
ctx.fillStyle = "rgb(255, 255, 255)";
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
ctx.restore();
|
||||
return [tempCanvas, tempCanvas.getContext("2d")];
|
||||
}
|
||||
|
||||
static destroyCanvas () {
|
||||
const tempCanvas = this.#tempCanvas
|
||||
static destroyCanvas() {
|
||||
const tempCanvas = this.#tempCanvas;
|
||||
if (tempCanvas) {
|
||||
// Zeroing the width and height causes Firefox to release graphics
|
||||
// resources immediately, which can greatly reduce memory consumption.
|
||||
tempCanvas.width = 0
|
||||
tempCanvas.height = 0
|
||||
tempCanvas.width = 0;
|
||||
tempCanvas.height = 0;
|
||||
}
|
||||
this.#tempCanvas = null
|
||||
this.#tempCanvas = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,303 +80,297 @@ class PDFThumbnailView {
|
|||
/**
|
||||
* @param {PDFThumbnailViewOptions} options
|
||||
*/
|
||||
constructor ({
|
||||
container,
|
||||
id,
|
||||
defaultViewport,
|
||||
optionalContentConfigPromise,
|
||||
linkService,
|
||||
renderingQueue,
|
||||
checkSetImageDisabled,
|
||||
l10n,
|
||||
}) {
|
||||
this.id = id
|
||||
this.renderingId = 'thumbnail' + id
|
||||
this.pageLabel = null
|
||||
constructor({
|
||||
container,
|
||||
id,
|
||||
defaultViewport,
|
||||
optionalContentConfigPromise,
|
||||
linkService,
|
||||
renderingQueue,
|
||||
l10n,
|
||||
pageColors,
|
||||
}) {
|
||||
this.id = id;
|
||||
this.renderingId = "thumbnail" + id;
|
||||
this.pageLabel = null;
|
||||
|
||||
this.pdfPage = null
|
||||
this.rotation = 0
|
||||
this.viewport = defaultViewport
|
||||
this.pdfPageRotate = defaultViewport.rotation
|
||||
this._optionalContentConfigPromise = optionalContentConfigPromise || null
|
||||
this.pdfPage = null;
|
||||
this.rotation = 0;
|
||||
this.viewport = defaultViewport;
|
||||
this.pdfPageRotate = defaultViewport.rotation;
|
||||
this._optionalContentConfigPromise = optionalContentConfigPromise || null;
|
||||
this.pageColors = pageColors || null;
|
||||
|
||||
this.linkService = linkService
|
||||
this.renderingQueue = renderingQueue
|
||||
this.linkService = linkService;
|
||||
this.renderingQueue = renderingQueue;
|
||||
|
||||
this.renderTask = null
|
||||
this.renderingState = RenderingStates.INITIAL
|
||||
this.resume = null
|
||||
this._checkSetImageDisabled =
|
||||
checkSetImageDisabled ||
|
||||
function () {
|
||||
return false
|
||||
}
|
||||
this.renderTask = null;
|
||||
this.renderingState = RenderingStates.INITIAL;
|
||||
this.resume = null;
|
||||
|
||||
const pageWidth = this.viewport.width,
|
||||
pageHeight = this.viewport.height,
|
||||
pageRatio = pageWidth / pageHeight
|
||||
pageRatio = pageWidth / pageHeight;
|
||||
|
||||
this.canvasWidth = THUMBNAIL_WIDTH
|
||||
this.canvasHeight = (this.canvasWidth / pageRatio) | 0
|
||||
this.scale = this.canvasWidth / pageWidth
|
||||
this.canvasWidth = THUMBNAIL_WIDTH;
|
||||
this.canvasHeight = (this.canvasWidth / pageRatio) | 0;
|
||||
this.scale = this.canvasWidth / pageWidth;
|
||||
|
||||
this.l10n = l10n
|
||||
this.l10n = l10n;
|
||||
|
||||
const anchor = document.createElement('a')
|
||||
anchor.href = linkService.getAnchorUrl('#page=' + id)
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = linkService.getAnchorUrl("#page=" + id);
|
||||
// NOTE
|
||||
anchor.title = this._thumbPageTitle
|
||||
anchor.onclick = function () {
|
||||
linkService.goToPage(id)
|
||||
return false
|
||||
}
|
||||
this.anchor = anchor
|
||||
linkService.goToPage(id);
|
||||
return false;
|
||||
};
|
||||
this.anchor = anchor;
|
||||
|
||||
const div = document.createElement('div')
|
||||
div.className = 'thumbnail'
|
||||
div.setAttribute('data-page-number', this.id)
|
||||
this.div = div
|
||||
const div = document.createElement("div");
|
||||
div.className = "thumbnail";
|
||||
div.setAttribute("data-page-number", this.id);
|
||||
this.div = div;
|
||||
|
||||
const ring = document.createElement('div')
|
||||
ring.className = 'thumbnailSelectionRing'
|
||||
const borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH
|
||||
ring.style.width = this.canvasWidth + borderAdjustment + 'px'
|
||||
ring.style.height = this.canvasHeight + borderAdjustment + 'px'
|
||||
this.ring = ring
|
||||
const ring = document.createElement("div");
|
||||
ring.className = "thumbnailSelectionRing";
|
||||
const borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH;
|
||||
ring.style.width = this.canvasWidth + borderAdjustment + "px";
|
||||
ring.style.height = this.canvasHeight + borderAdjustment + "px";
|
||||
this.ring = ring;
|
||||
|
||||
div.appendChild(ring)
|
||||
anchor.appendChild(div)
|
||||
container.appendChild(anchor)
|
||||
div.append(ring);
|
||||
anchor.append(div);
|
||||
container.append(anchor);
|
||||
}
|
||||
|
||||
setPdfPage (pdfPage) {
|
||||
this.pdfPage = pdfPage
|
||||
this.pdfPageRotate = pdfPage.rotate
|
||||
const totalRotation = (this.rotation + this.pdfPageRotate) % 360
|
||||
this.viewport = pdfPage.getViewport({scale: 1, rotation: totalRotation})
|
||||
this.reset()
|
||||
setPdfPage(pdfPage) {
|
||||
this.pdfPage = pdfPage;
|
||||
this.pdfPageRotate = pdfPage.rotate;
|
||||
const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
|
||||
this.viewport = pdfPage.getViewport({ scale: 1, rotation: totalRotation });
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset () {
|
||||
this.cancelRendering()
|
||||
this.renderingState = RenderingStates.INITIAL
|
||||
reset() {
|
||||
this.cancelRendering();
|
||||
this.renderingState = RenderingStates.INITIAL;
|
||||
|
||||
const pageWidth = this.viewport.width,
|
||||
pageHeight = this.viewport.height,
|
||||
pageRatio = pageWidth / pageHeight
|
||||
pageRatio = pageWidth / pageHeight;
|
||||
|
||||
this.canvasHeight = (this.canvasWidth / pageRatio) | 0
|
||||
this.scale = this.canvasWidth / pageWidth
|
||||
this.canvasHeight = (this.canvasWidth / pageRatio) | 0;
|
||||
this.scale = this.canvasWidth / pageWidth;
|
||||
|
||||
this.div.removeAttribute('data-loaded')
|
||||
const ring = this.ring
|
||||
ring.textContent = '' // Remove the thumbnail from the DOM.
|
||||
const borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH
|
||||
ring.style.width = this.canvasWidth + borderAdjustment + 'px'
|
||||
ring.style.height = this.canvasHeight + borderAdjustment + 'px'
|
||||
this.div.removeAttribute("data-loaded");
|
||||
const ring = this.ring;
|
||||
ring.textContent = ""; // Remove the thumbnail from the DOM.
|
||||
const borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH;
|
||||
ring.style.width = this.canvasWidth + borderAdjustment + "px";
|
||||
ring.style.height = this.canvasHeight + borderAdjustment + "px";
|
||||
|
||||
if (this.canvas) {
|
||||
// Zeroing the width and height causes Firefox to release graphics
|
||||
// resources immediately, which can greatly reduce memory consumption.
|
||||
this.canvas.width = 0
|
||||
this.canvas.height = 0
|
||||
delete this.canvas
|
||||
this.canvas.width = 0;
|
||||
this.canvas.height = 0;
|
||||
delete this.canvas;
|
||||
}
|
||||
if (this.image) {
|
||||
this.image.removeAttribute('src')
|
||||
delete this.image
|
||||
this.image.removeAttribute("src");
|
||||
delete this.image;
|
||||
}
|
||||
}
|
||||
|
||||
update ({rotation = null}) {
|
||||
if (typeof rotation === 'number') {
|
||||
this.rotation = rotation // The rotation may be zero.
|
||||
update({ rotation = null }) {
|
||||
if (typeof rotation === "number") {
|
||||
this.rotation = rotation; // The rotation may be zero.
|
||||
}
|
||||
const totalRotation = (this.rotation + this.pdfPageRotate) % 360
|
||||
const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
|
||||
this.viewport = this.viewport.clone({
|
||||
scale: 1,
|
||||
rotation: totalRotation,
|
||||
})
|
||||
this.reset()
|
||||
});
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* PLEASE NOTE: Most likely you want to use the `this.reset()` method,
|
||||
* rather than calling this one directly.
|
||||
*/
|
||||
cancelRendering () {
|
||||
cancelRendering() {
|
||||
if (this.renderTask) {
|
||||
this.renderTask.cancel()
|
||||
this.renderTask = null
|
||||
this.renderTask.cancel();
|
||||
this.renderTask = null;
|
||||
}
|
||||
this.resume = null
|
||||
this.resume = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getPageDrawContext (upscaleFactor = 1) {
|
||||
_getPageDrawContext(upscaleFactor = 1) {
|
||||
// Keep the no-thumbnail outline visible, i.e. `data-loaded === false`,
|
||||
// until rendering/image conversion is complete, to avoid display issues.
|
||||
const canvas = document.createElement('canvas')
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d", { alpha: false });
|
||||
const outputScale = new OutputScale();
|
||||
|
||||
if (
|
||||
typeof PDFJSDev === 'undefined' ||
|
||||
PDFJSDev.test('MOZCENTRAL || GENERIC')
|
||||
) {
|
||||
canvas.mozOpaque = true
|
||||
}
|
||||
const ctx = canvas.getContext('2d', {alpha: false})
|
||||
const outputScale = new OutputScale()
|
||||
|
||||
canvas.width = (upscaleFactor * this.canvasWidth * outputScale.sx) | 0
|
||||
canvas.height = (upscaleFactor * this.canvasHeight * outputScale.sy) | 0
|
||||
canvas.width = (upscaleFactor * this.canvasWidth * outputScale.sx) | 0;
|
||||
canvas.height = (upscaleFactor * this.canvasHeight * outputScale.sy) | 0;
|
||||
|
||||
const transform = outputScale.scaled
|
||||
? [outputScale.sx, 0, 0, outputScale.sy, 0, 0]
|
||||
: null
|
||||
: null;
|
||||
|
||||
return {ctx, canvas, transform}
|
||||
return { ctx, canvas, transform };
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_convertCanvasToImage (canvas) {
|
||||
_convertCanvasToImage(canvas) {
|
||||
if (this.renderingState !== RenderingStates.FINISHED) {
|
||||
throw new Error('_convertCanvasToImage: Rendering has not finished.')
|
||||
throw new Error("_convertCanvasToImage: Rendering has not finished.");
|
||||
}
|
||||
const reducedCanvas = this._reduceImage(canvas)
|
||||
const reducedCanvas = this._reduceImage(canvas);
|
||||
|
||||
const image = document.createElement('img')
|
||||
image.className = 'thumbnailImage'
|
||||
image.setAttribute('aria-label', this._thumbPageCanvas)
|
||||
image.style.width = this.canvasWidth + 'px'
|
||||
image.style.height = this.canvasHeight + 'px'
|
||||
const image = document.createElement("img");
|
||||
image.className = "thumbnailImage";
|
||||
this._thumbPageCanvas.then(msg => {
|
||||
image.setAttribute("aria-label", msg);
|
||||
});
|
||||
image.style.width = this.canvasWidth + "px";
|
||||
image.style.height = this.canvasHeight + "px";
|
||||
|
||||
image.src = reducedCanvas.toDataURL()
|
||||
this.image = image
|
||||
image.src = reducedCanvas.toDataURL();
|
||||
this.image = image;
|
||||
|
||||
this.div.setAttribute('data-loaded', true)
|
||||
this.ring.appendChild(image)
|
||||
this.div.setAttribute("data-loaded", true);
|
||||
this.ring.append(image);
|
||||
|
||||
// Zeroing the width and height causes Firefox to release graphics
|
||||
// resources immediately, which can greatly reduce memory consumption.
|
||||
reducedCanvas.width = 0
|
||||
reducedCanvas.height = 0
|
||||
reducedCanvas.width = 0;
|
||||
reducedCanvas.height = 0;
|
||||
}
|
||||
|
||||
draw () {
|
||||
draw() {
|
||||
if (this.renderingState !== RenderingStates.INITIAL) {
|
||||
console.error('Must be in new state before drawing')
|
||||
return Promise.resolve()
|
||||
console.error("Must be in new state before drawing");
|
||||
return Promise.resolve();
|
||||
}
|
||||
const {pdfPage} = this
|
||||
const { pdfPage } = this;
|
||||
|
||||
if (!pdfPage) {
|
||||
this.renderingState = RenderingStates.FINISHED
|
||||
return Promise.reject(new Error('pdfPage is not loaded'))
|
||||
this.renderingState = RenderingStates.FINISHED;
|
||||
return Promise.reject(new Error("pdfPage is not loaded"));
|
||||
}
|
||||
|
||||
this.renderingState = RenderingStates.RUNNING
|
||||
this.renderingState = RenderingStates.RUNNING;
|
||||
|
||||
const finishRenderTask = async (error = null) => {
|
||||
// The renderTask may have been replaced by a new one, so only remove
|
||||
// the reference to the renderTask if it matches the one that is
|
||||
// triggering this callback.
|
||||
if (renderTask === this.renderTask) {
|
||||
this.renderTask = null
|
||||
this.renderTask = null;
|
||||
}
|
||||
|
||||
if (error instanceof RenderingCancelledException) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.renderingState = RenderingStates.FINISHED
|
||||
this._convertCanvasToImage(canvas)
|
||||
this.renderingState = RenderingStates.FINISHED;
|
||||
this._convertCanvasToImage(canvas);
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Render the thumbnail at a larger size and downsize the canvas (similar
|
||||
// to `setImage`), to improve consistency between thumbnails created by
|
||||
// the `draw` and `setImage` methods (fixes issue 8233).
|
||||
// NOTE: To primarily avoid increasing memory usage too much, but also to
|
||||
// reduce downsizing overhead, we purposely limit the up-scaling factor.
|
||||
const {ctx, canvas, transform} =
|
||||
this._getPageDrawContext(DRAW_UPSCALE_FACTOR)
|
||||
const { ctx, canvas, transform } =
|
||||
this._getPageDrawContext(DRAW_UPSCALE_FACTOR);
|
||||
const drawViewport = this.viewport.clone({
|
||||
scale: DRAW_UPSCALE_FACTOR * this.scale,
|
||||
})
|
||||
});
|
||||
const renderContinueCallback = cont => {
|
||||
if (!this.renderingQueue.isHighestPriority(this)) {
|
||||
this.renderingState = RenderingStates.PAUSED
|
||||
this.renderingState = RenderingStates.PAUSED;
|
||||
this.resume = () => {
|
||||
this.renderingState = RenderingStates.RUNNING
|
||||
cont()
|
||||
}
|
||||
return
|
||||
this.renderingState = RenderingStates.RUNNING;
|
||||
cont();
|
||||
};
|
||||
return;
|
||||
}
|
||||
cont()
|
||||
}
|
||||
cont();
|
||||
};
|
||||
|
||||
const renderContext = {
|
||||
canvasContext: ctx,
|
||||
transform,
|
||||
viewport: drawViewport,
|
||||
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
||||
}
|
||||
const renderTask = (this.renderTask = pdfPage.render(renderContext))
|
||||
renderTask.onContinue = renderContinueCallback
|
||||
pageColors: this.pageColors,
|
||||
};
|
||||
const renderTask = (this.renderTask = pdfPage.render(renderContext));
|
||||
renderTask.onContinue = renderContinueCallback;
|
||||
|
||||
const resultPromise = renderTask.promise.then(
|
||||
function () {
|
||||
return finishRenderTask(null)
|
||||
return finishRenderTask(null);
|
||||
},
|
||||
function (error) {
|
||||
return finishRenderTask(error)
|
||||
},
|
||||
)
|
||||
return finishRenderTask(error);
|
||||
}
|
||||
);
|
||||
resultPromise.finally(() => {
|
||||
// Zeroing the width and height causes Firefox to release graphics
|
||||
// resources immediately, which can greatly reduce memory consumption.
|
||||
canvas.width = 0
|
||||
canvas.height = 0
|
||||
canvas.width = 0;
|
||||
canvas.height = 0;
|
||||
|
||||
// Only trigger cleanup, once rendering has finished, when the current
|
||||
// pageView is *not* cached on the `BaseViewer`-instance.
|
||||
const pageCached = this.linkService.isPageCached(this.id)
|
||||
const pageCached = this.linkService.isPageCached(this.id);
|
||||
if (!pageCached) {
|
||||
this.pdfPage?.cleanup()
|
||||
this.pdfPage?.cleanup();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return resultPromise
|
||||
return resultPromise;
|
||||
}
|
||||
|
||||
setImage (pageView) {
|
||||
if (this._checkSetImageDisabled()) {
|
||||
return
|
||||
}
|
||||
setImage(pageView) {
|
||||
if (this.renderingState !== RenderingStates.INITIAL) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const {canvas, pdfPage} = pageView
|
||||
const { thumbnailCanvas: canvas, pdfPage, scale } = pageView;
|
||||
if (!canvas) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!this.pdfPage) {
|
||||
this.setPdfPage(pdfPage)
|
||||
this.setPdfPage(pdfPage);
|
||||
}
|
||||
this.renderingState = RenderingStates.FINISHED
|
||||
this._convertCanvasToImage(canvas)
|
||||
if (scale < this.scale) {
|
||||
// Avoid upscaling the image, since that makes the thumbnail look blurry.
|
||||
return;
|
||||
}
|
||||
this.renderingState = RenderingStates.FINISHED;
|
||||
this._convertCanvasToImage(canvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_reduceImage (img) {
|
||||
const {ctx, canvas} = this._getPageDrawContext()
|
||||
_reduceImage(img) {
|
||||
const { ctx, canvas } = this._getPageDrawContext();
|
||||
|
||||
if (img.width <= 2 * canvas.width) {
|
||||
ctx.drawImage(
|
||||
|
|
@ -387,21 +382,21 @@ class PDFThumbnailView {
|
|||
0,
|
||||
0,
|
||||
canvas.width,
|
||||
canvas.height,
|
||||
)
|
||||
return canvas
|
||||
canvas.height
|
||||
);
|
||||
return canvas;
|
||||
}
|
||||
// drawImage does an awful job of rescaling the image, doing it gradually.
|
||||
let reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS
|
||||
let reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS
|
||||
let reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS;
|
||||
let reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS;
|
||||
const [reducedImage, reducedImageCtx] = TempImageFactory.getCanvas(
|
||||
reducedWidth,
|
||||
reducedHeight,
|
||||
)
|
||||
reducedHeight
|
||||
);
|
||||
|
||||
while (reducedWidth > img.width || reducedHeight > img.height) {
|
||||
reducedWidth >>= 1
|
||||
reducedHeight >>= 1
|
||||
reducedWidth >>= 1;
|
||||
reducedHeight >>= 1;
|
||||
}
|
||||
reducedImageCtx.drawImage(
|
||||
img,
|
||||
|
|
@ -412,8 +407,8 @@ class PDFThumbnailView {
|
|||
0,
|
||||
0,
|
||||
reducedWidth,
|
||||
reducedHeight,
|
||||
)
|
||||
reducedHeight
|
||||
);
|
||||
while (reducedWidth > 2 * canvas.width) {
|
||||
reducedImageCtx.drawImage(
|
||||
reducedImage,
|
||||
|
|
@ -424,10 +419,10 @@ class PDFThumbnailView {
|
|||
0,
|
||||
0,
|
||||
reducedWidth >> 1,
|
||||
reducedHeight >> 1,
|
||||
)
|
||||
reducedWidth >>= 1
|
||||
reducedHeight >>= 1
|
||||
reducedHeight >> 1
|
||||
);
|
||||
reducedWidth >>= 1;
|
||||
reducedHeight >>= 1;
|
||||
}
|
||||
ctx.drawImage(
|
||||
reducedImage,
|
||||
|
|
@ -438,17 +433,19 @@ class PDFThumbnailView {
|
|||
0,
|
||||
0,
|
||||
canvas.width,
|
||||
canvas.height,
|
||||
)
|
||||
return canvas
|
||||
canvas.height
|
||||
);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
get _thumbPageTitle () {
|
||||
get _thumbPageTitle() {
|
||||
// NOTE
|
||||
return window.siyuan.languages.thumbPageTitle.replace('{{page}}',
|
||||
this.pageLabel ?? this.id)
|
||||
}
|
||||
|
||||
get _thumbPageCanvas () {
|
||||
get _thumbPageCanvas() {
|
||||
// NOTE
|
||||
return window.siyuan.languages.thumbPage.replace('{{page}}',
|
||||
this.pageLabel ?? this.id)
|
||||
}
|
||||
|
|
@ -456,17 +453,17 @@ class PDFThumbnailView {
|
|||
/**
|
||||
* @param {string|null} label
|
||||
*/
|
||||
setPageLabel (label) {
|
||||
this.pageLabel = typeof label === 'string' ? label : null
|
||||
setPageLabel(label) {
|
||||
this.pageLabel = typeof label === "string" ? label : null;
|
||||
|
||||
// NOTE
|
||||
this.anchor.title = this._thumbPageTitle
|
||||
|
||||
if (this.renderingState !== RenderingStates.FINISHED) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this.image?.setAttribute('aria-label', this._thumbPageCanvas)
|
||||
}
|
||||
}
|
||||
|
||||
export { PDFThumbnailView, TempImageFactory }
|
||||
export { PDFThumbnailView, TempImageFactory };
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @typedef {import("../src/display/api").PDFDocumentProxy} PDFDocumentProxy */
|
||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||
/** @typedef {import("./interfaces").IL10n} IL10n */
|
||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
||||
|
||||
import {
|
||||
getVisibleElements,
|
||||
isValidRotation,
|
||||
|
|
@ -33,6 +40,9 @@ const THUMBNAIL_SELECTED_CLASS = "selected";
|
|||
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
||||
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
|
||||
* @property {IL10n} l10n - Localization service.
|
||||
* @property {Object} [pageColors] - Overwrites background and foreground colors
|
||||
* with user defined ones in order to improve readability in high contrast
|
||||
* mode.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -42,20 +52,39 @@ class PDFThumbnailViewer {
|
|||
/**
|
||||
* @param {PDFThumbnailViewerOptions} options
|
||||
*/
|
||||
constructor({ container, eventBus, linkService, renderingQueue, l10n }) {
|
||||
constructor({
|
||||
container,
|
||||
eventBus,
|
||||
linkService,
|
||||
renderingQueue,
|
||||
l10n,
|
||||
pageColors,
|
||||
}) {
|
||||
this.container = container;
|
||||
this.linkService = linkService;
|
||||
this.renderingQueue = renderingQueue;
|
||||
this.l10n = l10n;
|
||||
this.pageColors = pageColors || null;
|
||||
|
||||
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
|
||||
if (
|
||||
this.pageColors &&
|
||||
!(
|
||||
CSS.supports("color", this.pageColors.background) &&
|
||||
CSS.supports("color", this.pageColors.foreground)
|
||||
)
|
||||
) {
|
||||
if (this.pageColors.background || this.pageColors.foreground) {
|
||||
console.warn(
|
||||
"PDFThumbnailViewer: Ignoring `pageColors`-option, since the browser doesn't support the values used."
|
||||
);
|
||||
}
|
||||
this.pageColors = null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -144,12 +173,9 @@ class PDFThumbnailViewer {
|
|||
}
|
||||
|
||||
cleanup() {
|
||||
for (let i = 0, ii = this._thumbnails.length; i < ii; i++) {
|
||||
if (
|
||||
this._thumbnails[i] &&
|
||||
this._thumbnails[i].renderingState !== RenderingStates.FINISHED
|
||||
) {
|
||||
this._thumbnails[i].reset();
|
||||
for (const thumbnail of this._thumbnails) {
|
||||
if (thumbnail.renderingState !== RenderingStates.FINISHED) {
|
||||
thumbnail.reset();
|
||||
}
|
||||
}
|
||||
TempImageFactory.destroyCanvas();
|
||||
|
|
@ -163,8 +189,6 @@ class PDFThumbnailViewer {
|
|||
this._currentPageNumber = 1;
|
||||
this._pageLabels = null;
|
||||
this._pagesRotation = 0;
|
||||
this._optionalContentConfigPromise = null;
|
||||
this._setImageDisabled = false;
|
||||
|
||||
// Remove the thumbnails from the DOM.
|
||||
this.container.textContent = "";
|
||||
|
|
@ -187,53 +211,43 @@ class PDFThumbnailViewer {
|
|||
const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig();
|
||||
|
||||
firstPagePromise
|
||||
.then(firstPdfPage => {
|
||||
this._optionalContentConfigPromise = optionalContentConfigPromise;
|
||||
.then(firstPdfPage => {
|
||||
const pagesCount = pdfDocument.numPages;
|
||||
const viewport = firstPdfPage.getViewport({ scale: 1 });
|
||||
|
||||
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,
|
||||
l10n: this.l10n,
|
||||
pageColors: this.pageColors,
|
||||
});
|
||||
this._thumbnails.push(thumbnail);
|
||||
}
|
||||
// Set the first `pdfPage` immediately, since it's already loaded,
|
||||
// rather than having to repeat the `PDFDocumentProxy.getPage` call in
|
||||
// the `this.#ensurePdfPageLoaded` method before rendering can start.
|
||||
this._thumbnails[0]?.setPdfPage(firstPdfPage);
|
||||
|
||||
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,
|
||||
l10n: this.l10n,
|
||||
});
|
||||
this._thumbnails.push(thumbnail);
|
||||
}
|
||||
// Set the first `pdfPage` immediately, since it's already loaded,
|
||||
// rather than having to repeat the `PDFDocumentProxy.getPage` call in
|
||||
// the `this.#ensurePdfPageLoaded` method before rendering can start.
|
||||
const firstThumbnailView = this._thumbnails[0];
|
||||
if (firstThumbnailView) {
|
||||
firstThumbnailView.setPdfPage(firstPdfPage);
|
||||
}
|
||||
|
||||
// Ensure that the current thumbnail is always highlighted on load.
|
||||
const thumbnailView = this._thumbnails[this._currentPageNumber - 1];
|
||||
thumbnailView.div.classList.add(THUMBNAIL_SELECTED_CLASS);
|
||||
})
|
||||
.catch(reason => {
|
||||
console.error("Unable to initialize thumbnail viewer", reason);
|
||||
});
|
||||
// Ensure that the current thumbnail is always highlighted on load.
|
||||
const thumbnailView = this._thumbnails[this._currentPageNumber - 1];
|
||||
thumbnailView.div.classList.add(THUMBNAIL_SELECTED_CLASS);
|
||||
})
|
||||
.catch(reason => {
|
||||
console.error("Unable to initialize thumbnail viewer", reason);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_cancelRendering() {
|
||||
for (let i = 0, ii = this._thumbnails.length; i < ii; i++) {
|
||||
if (this._thumbnails[i]) {
|
||||
this._thumbnails[i].cancelRendering();
|
||||
}
|
||||
for (const thumbnail of this._thumbnails) {
|
||||
thumbnail.cancelRendering();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,17 +24,23 @@ import {
|
|||
PDFLinkService,
|
||||
SimpleLinkService,
|
||||
} from "./pdf_link_service.js";
|
||||
import { parseQueryString, ProgressBar } from "./ui_utils.js";
|
||||
import { PDFSinglePageViewer, PDFViewer } from "./pdf_viewer.js";
|
||||
import {
|
||||
parseQueryString,
|
||||
ProgressBar,
|
||||
RenderingStates,
|
||||
ScrollMode,
|
||||
SpreadMode,
|
||||
} from "./ui_utils.js";
|
||||
import { AnnotationLayerBuilder } from "./annotation_layer_builder.js";
|
||||
import { DownloadManager } from "./download_manager.js";
|
||||
import { EventBus } from "./event_utils.js";
|
||||
import { GenericL10n } from "./genericl10n.js";
|
||||
import { NullL10n } from "./l10n_utils.js";
|
||||
import { PDFFindController } from "./pdf_find_controller.js";
|
||||
import { PDFHistory } from "./pdf_history.js";
|
||||
import { PDFPageView } from "./pdf_page_view.js";
|
||||
import { PDFScriptingManager } from "./pdf_scripting_manager.js";
|
||||
import { PDFSinglePageViewer } from "./pdf_single_page_viewer.js";
|
||||
import { PDFViewer } from "./pdf_viewer.js";
|
||||
import { StructTreeLayerBuilder } from "./struct_tree_layer_builder.js";
|
||||
import { TextLayerBuilder } from "./text_layer_builder.js";
|
||||
import { XfaLayerBuilder } from "./xfa_layer_builder.js";
|
||||
|
|
@ -52,7 +58,6 @@ export {
|
|||
DefaultXfaLayerFactory,
|
||||
DownloadManager,
|
||||
EventBus,
|
||||
GenericL10n,
|
||||
LinkTarget,
|
||||
NullL10n,
|
||||
parseQueryString,
|
||||
|
|
@ -64,7 +69,10 @@ export {
|
|||
PDFSinglePageViewer,
|
||||
PDFViewer,
|
||||
ProgressBar,
|
||||
RenderingStates,
|
||||
ScrollMode,
|
||||
SimpleLinkService,
|
||||
SpreadMode,
|
||||
StructTreeLayerBuilder,
|
||||
TextLayerBuilder,
|
||||
XfaLayerBuilder,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -18,5 +18,5 @@
|
|||
|
||||
const {addScriptSync} = require('../../protyle/util/addScript')
|
||||
const {Constants} = require('../../constants')
|
||||
addScriptSync(`${Constants.PROTYLE_CDN}/js/pdf/pdf.js?v=2.14.102`, 'pdfjsScript')
|
||||
addScriptSync(`${Constants.PROTYLE_CDN}/js/pdf/pdf.js?v=3.0.150`, 'pdfjsScript')
|
||||
module.exports = window["pdfjs-dist/build/pdf"];
|
||||
|
|
|
|||
|
|
@ -13,18 +13,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SCROLLBAR_PADDING, ScrollMode, SpreadMode } from "./ui_utils.js";
|
||||
import { ScrollMode, SpreadMode } from "./ui_utils.js";
|
||||
import { CursorTool } from "./pdf_cursor_tools.js";
|
||||
import { PagesCountLimit } from "./base_viewer.js";
|
||||
import { PagesCountLimit } from "./pdf_viewer.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} SecondaryToolbarOptions
|
||||
* @property {HTMLDivElement} toolbar - Container for the secondary toolbar.
|
||||
* @property {HTMLButtonElement} toggleButton - Button to toggle the visibility
|
||||
* of the secondary toolbar.
|
||||
* @property {HTMLDivElement} toolbarButtonContainer - Container where all the
|
||||
* toolbar buttons are placed. The maximum height of the toolbar is controlled
|
||||
* dynamically by adjusting the 'max-height' CSS property of this DOM element.
|
||||
* @property {HTMLButtonElement} presentationModeButton - Button for entering
|
||||
* presentation mode.
|
||||
* @property {HTMLButtonElement} openFileButton - Button to open a file.
|
||||
|
|
@ -52,24 +49,21 @@ import { PagesCountLimit } from "./base_viewer.js";
|
|||
class SecondaryToolbar {
|
||||
/**
|
||||
* @param {SecondaryToolbarOptions} options
|
||||
* @param {HTMLDivElement} mainContainer
|
||||
* @param {EventBus} eventBus
|
||||
*/
|
||||
constructor(options, mainContainer, eventBus) {
|
||||
constructor(options, eventBus, externalServices) {
|
||||
this.toolbar = options.toolbar;
|
||||
this.toggleButton = options.toggleButton;
|
||||
this.toolbarButtonContainer = options.toolbarButtonContainer;
|
||||
this.buttons = [
|
||||
{
|
||||
element: options.presentationModeButton,
|
||||
eventName: "presentationmode",
|
||||
close: true,
|
||||
},
|
||||
// NOTE
|
||||
// {
|
||||
// element: options.presentationModeButton,
|
||||
// eventName: "presentationmode",
|
||||
// close: true,
|
||||
// },
|
||||
// { element: options.openFileButton, eventName: "openfile", close: true },
|
||||
// { element: options.printButton, eventName: "print", close: true },
|
||||
// { element: options.downloadButton, eventName: "download", close: true },
|
||||
// { element: options.viewBookmarkButton, eventName: null, close: true },
|
||||
{ element: options.viewBookmarkButton, eventName: null, close: true },
|
||||
{ element: options.firstPageButton, eventName: "firstpage", close: true },
|
||||
{ element: options.lastPageButton, eventName: "lastpage", close: true },
|
||||
{
|
||||
|
|
@ -142,6 +136,14 @@ class SecondaryToolbar {
|
|||
close: true,
|
||||
},
|
||||
];
|
||||
// NOTE
|
||||
// if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||
// this.buttons.push({
|
||||
// element: options.openFileButton,
|
||||
// eventName: "openfile",
|
||||
// close: true,
|
||||
// });
|
||||
// }
|
||||
this.items = {
|
||||
firstPage: options.firstPageButton,
|
||||
lastPage: options.lastPageButton,
|
||||
|
|
@ -149,14 +151,9 @@ class SecondaryToolbar {
|
|||
pageRotateCcw: options.pageRotateCcwButton,
|
||||
};
|
||||
|
||||
this.mainContainer = mainContainer;
|
||||
this.eventBus = eventBus;
|
||||
|
||||
this.externalServices = externalServices;
|
||||
this.opened = false;
|
||||
this.containerHeight = null;
|
||||
this.previousContainerHeight = null;
|
||||
|
||||
this.reset();
|
||||
|
||||
// Bind the event listeners for click, cursor tool, and scroll/spread mode
|
||||
// actions.
|
||||
|
|
@ -165,8 +162,7 @@ class SecondaryToolbar {
|
|||
this.#bindScrollModeListener(options);
|
||||
this.#bindSpreadModeListener(options);
|
||||
|
||||
// Bind the event listener for adjusting the 'max-height' of the toolbar.
|
||||
this.eventBus._on("resize", this.#setMaxHeight.bind(this));
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -219,6 +215,10 @@ class SecondaryToolbar {
|
|||
if (close) {
|
||||
this.close();
|
||||
}
|
||||
this.externalServices.reportTelemetry({
|
||||
type: "buttons",
|
||||
data: { id: element.id },
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -231,8 +231,8 @@ class SecondaryToolbar {
|
|||
cursorSelectToolButton.classList.toggle("toggled", isSelect);
|
||||
cursorHandToolButton.classList.toggle("toggled", isHand);
|
||||
|
||||
cursorSelectToolButton.setAttribute("aria-checked", `${isSelect}`);
|
||||
cursorHandToolButton.setAttribute("aria-checked", `${isHand}`);
|
||||
cursorSelectToolButton.setAttribute("aria-checked", isSelect);
|
||||
cursorHandToolButton.setAttribute("aria-checked", isHand);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -256,10 +256,10 @@ class SecondaryToolbar {
|
|||
scrollHorizontalButton.classList.toggle("toggled", isHorizontal);
|
||||
scrollWrappedButton.classList.toggle("toggled", isWrapped);
|
||||
|
||||
scrollPageButton.setAttribute("aria-checked", `${isPage}`);
|
||||
scrollVerticalButton.setAttribute("aria-checked", `${isVertical}`);
|
||||
scrollHorizontalButton.setAttribute("aria-checked", `${isHorizontal}`);
|
||||
scrollWrappedButton.setAttribute("aria-checked", `${isWrapped}`);
|
||||
scrollPageButton.setAttribute("aria-checked", isPage);
|
||||
scrollVerticalButton.setAttribute("aria-checked", isVertical);
|
||||
scrollHorizontalButton.setAttribute("aria-checked", isHorizontal);
|
||||
scrollWrappedButton.setAttribute("aria-checked", isWrapped);
|
||||
|
||||
// Permanently *disable* the Scroll buttons when PAGE-scrolling is being
|
||||
// enforced for *very* long/large documents; please see the `BaseViewer`.
|
||||
|
|
@ -299,9 +299,9 @@ class SecondaryToolbar {
|
|||
spreadOddButton.classList.toggle("toggled", isOdd);
|
||||
spreadEvenButton.classList.toggle("toggled", isEven);
|
||||
|
||||
spreadNoneButton.setAttribute("aria-checked", `${isNone}`);
|
||||
spreadOddButton.setAttribute("aria-checked", `${isOdd}`);
|
||||
spreadEvenButton.setAttribute("aria-checked", `${isEven}`);
|
||||
spreadNoneButton.setAttribute("aria-checked", isNone);
|
||||
spreadOddButton.setAttribute("aria-checked", isOdd);
|
||||
spreadEvenButton.setAttribute("aria-checked", isEven);
|
||||
}
|
||||
this.eventBus._on("spreadmodechanged", spreadModeChanged);
|
||||
|
||||
|
|
@ -317,10 +317,9 @@ class SecondaryToolbar {
|
|||
return;
|
||||
}
|
||||
this.opened = true;
|
||||
this.#setMaxHeight();
|
||||
|
||||
this.toggleButton.classList.add("toggled");
|
||||
this.toggleButton.setAttribute("aria-expanded", "true");
|
||||
// NOTE
|
||||
this.toolbar.classList.remove("fn__hidden");
|
||||
}
|
||||
|
||||
|
|
@ -329,6 +328,7 @@ class SecondaryToolbar {
|
|||
return;
|
||||
}
|
||||
this.opened = false;
|
||||
// NOTE
|
||||
this.toolbar.classList.add("fn__hidden");
|
||||
this.toggleButton.classList.remove("toggled");
|
||||
this.toggleButton.setAttribute("aria-expanded", "false");
|
||||
|
|
@ -341,22 +341,6 @@ class SecondaryToolbar {
|
|||
this.open();
|
||||
}
|
||||
}
|
||||
|
||||
#setMaxHeight() {
|
||||
if (!this.opened) {
|
||||
return; // Only adjust the 'max-height' if the toolbar is visible.
|
||||
}
|
||||
this.containerHeight = this.mainContainer.clientHeight;
|
||||
|
||||
if (this.containerHeight === this.previousContainerHeight) {
|
||||
return;
|
||||
}
|
||||
this.toolbarButtonContainer.style.maxHeight = `${
|
||||
this.containerHeight - SCROLLBAR_PADDING
|
||||
}px`;
|
||||
|
||||
this.previousContainerHeight = this.containerHeight;
|
||||
}
|
||||
}
|
||||
|
||||
export { SecondaryToolbar };
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */
|
||||
|
||||
const PDF_ROLE_TO_HTML_ROLE = {
|
||||
// Document level structure types
|
||||
Document: null, // There's a "document" role, but it doesn't make sense here.
|
||||
|
|
@ -126,7 +128,7 @@ class StructTreeLayerBuilder {
|
|||
this._setAttributes(node.children[0], element);
|
||||
} else {
|
||||
for (const kid of node.children) {
|
||||
element.appendChild(this._walk(kid));
|
||||
element.append(this._walk(kid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./pdf_find_controller").PDFFindController} PDFFindController */
|
||||
|
||||
/**
|
||||
* @typedef {Object} TextHighlighterOptions
|
||||
* @property {PDFFindController} findController
|
||||
|
|
@ -172,8 +176,8 @@ class TextHighlighter {
|
|||
let div = textDivs[divIdx];
|
||||
if (div.nodeType === Node.TEXT_NODE) {
|
||||
const span = document.createElement("span");
|
||||
div.parentNode.insertBefore(span, div);
|
||||
span.appendChild(div);
|
||||
div.before(span);
|
||||
span.append(div);
|
||||
textDivs[divIdx] = span;
|
||||
div = span;
|
||||
}
|
||||
|
|
@ -185,11 +189,11 @@ class TextHighlighter {
|
|||
if (className) {
|
||||
const span = document.createElement("span");
|
||||
span.className = `${className} appended`;
|
||||
span.appendChild(node);
|
||||
div.appendChild(span);
|
||||
span.append(node);
|
||||
div.append(span);
|
||||
return className.includes("selected") ? span.offsetLeft : 0;
|
||||
}
|
||||
div.appendChild(node);
|
||||
div.append(node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,16 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||
/** @typedef {import("./text_highlighter").TextHighlighter} TextHighlighter */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
||||
|
||||
import { renderTextLayer } from "./pdfjs";
|
||||
import { getHighlight } from '../anno'
|
||||
|
||||
const EXPAND_DIVS_TIMEOUT = 300; // ms
|
||||
|
||||
/**
|
||||
* @typedef {Object} TextLayerBuilderOptions
|
||||
* @property {HTMLDivElement} textLayerDiv - The text layer container.
|
||||
|
|
@ -26,8 +31,7 @@ const EXPAND_DIVS_TIMEOUT = 300; // ms
|
|||
* @property {PageViewport} viewport - The viewport of the text layer.
|
||||
* @property {TextHighlighter} highlighter - Optional object that will handle
|
||||
* highlighting text from the find controller.
|
||||
* @property {boolean} enhanceTextSelection - Option to turn on improved
|
||||
* text selection.
|
||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -42,7 +46,7 @@ class TextLayerBuilder {
|
|||
pageIndex,
|
||||
viewport,
|
||||
highlighter = null,
|
||||
enhanceTextSelection = false,
|
||||
accessibilityManager = null,
|
||||
}) {
|
||||
this.textLayerDiv = textLayerDiv;
|
||||
this.eventBus = eventBus;
|
||||
|
|
@ -55,22 +59,17 @@ class TextLayerBuilder {
|
|||
this.textDivs = [];
|
||||
this.textLayerRenderTask = null;
|
||||
this.highlighter = highlighter;
|
||||
this.enhanceTextSelection = enhanceTextSelection;
|
||||
this.accessibilityManager = accessibilityManager;
|
||||
|
||||
this._bindMouse();
|
||||
this.#bindMouse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_finishRendering() {
|
||||
#finishRendering() {
|
||||
this.renderingDone = true;
|
||||
|
||||
if (!this.enhanceTextSelection) {
|
||||
const endOfContent = document.createElement("div");
|
||||
endOfContent.className = "endOfContent";
|
||||
this.textLayerDiv.appendChild(endOfContent);
|
||||
}
|
||||
const endOfContent = document.createElement("div");
|
||||
endOfContent.className = "endOfContent";
|
||||
this.textLayerDiv.append(endOfContent);
|
||||
|
||||
this.eventBus.dispatch("textlayerrendered", {
|
||||
source: this,
|
||||
|
|
@ -95,6 +94,7 @@ class TextLayerBuilder {
|
|||
|
||||
this.textDivs.length = 0;
|
||||
this.highlighter?.setTextMapping(this.textDivs, this.textContentItemsStr);
|
||||
this.accessibilityManager?.setTextMapping(this.textDivs);
|
||||
|
||||
const textLayerFrag = document.createDocumentFragment();
|
||||
this.textLayerRenderTask = renderTextLayer({
|
||||
|
|
@ -105,13 +105,13 @@ class TextLayerBuilder {
|
|||
textDivs: this.textDivs,
|
||||
textContentItemsStr: this.textContentItemsStr,
|
||||
timeout,
|
||||
enhanceTextSelection: this.enhanceTextSelection,
|
||||
});
|
||||
this.textLayerRenderTask.promise.then(
|
||||
() => {
|
||||
this.textLayerDiv.appendChild(textLayerFrag);
|
||||
this._finishRendering();
|
||||
this.textLayerDiv.append(textLayerFrag);
|
||||
this.#finishRendering();
|
||||
this.highlighter?.enable();
|
||||
this.accessibilityManager?.enable();
|
||||
},
|
||||
function (reason) {
|
||||
// Cancelled or failed to render text layer; skipping errors.
|
||||
|
|
@ -128,6 +128,7 @@ class TextLayerBuilder {
|
|||
this.textLayerRenderTask = null;
|
||||
}
|
||||
this.highlighter?.disable();
|
||||
this.accessibilityManager?.disable();
|
||||
}
|
||||
|
||||
setTextContentStream(readableStream) {
|
||||
|
|
@ -144,26 +145,11 @@ class TextLayerBuilder {
|
|||
* Improves text selection by adding an additional div where the mouse was
|
||||
* clicked. This reduces flickering of the content if the mouse is slowly
|
||||
* dragged up or down.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_bindMouse() {
|
||||
#bindMouse() {
|
||||
const div = this.textLayerDiv;
|
||||
let expandDivsTimer = null;
|
||||
|
||||
div.addEventListener("mousedown", evt => {
|
||||
if (this.enhanceTextSelection && this.textLayerRenderTask) {
|
||||
this.textLayerRenderTask.expandTextDivs(true);
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) &&
|
||||
expandDivsTimer
|
||||
) {
|
||||
clearTimeout(expandDivsTimer);
|
||||
expandDivsTimer = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const end = div.querySelector(".endOfContent");
|
||||
if (!end) {
|
||||
return;
|
||||
|
|
@ -175,11 +161,9 @@ class TextLayerBuilder {
|
|||
// However it does not work when selection is started on empty space.
|
||||
let adjustTop = evt.target !== div;
|
||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||
adjustTop =
|
||||
adjustTop &&
|
||||
window
|
||||
.getComputedStyle(end)
|
||||
.getPropertyValue("-moz-user-select") !== "none";
|
||||
adjustTop &&=
|
||||
getComputedStyle(end).getPropertyValue("-moz-user-select") !==
|
||||
"none";
|
||||
}
|
||||
if (adjustTop) {
|
||||
const divBounds = div.getBoundingClientRect();
|
||||
|
|
@ -191,20 +175,6 @@ class TextLayerBuilder {
|
|||
});
|
||||
|
||||
div.addEventListener("mouseup", () => {
|
||||
if (this.enhanceTextSelection && this.textLayerRenderTask) {
|
||||
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
|
||||
expandDivsTimer = setTimeout(() => {
|
||||
if (this.textLayerRenderTask) {
|
||||
this.textLayerRenderTask.expandTextDivs(false);
|
||||
}
|
||||
expandDivsTimer = null;
|
||||
}, EXPAND_DIVS_TIMEOUT);
|
||||
} else {
|
||||
this.textLayerRenderTask.expandTextDivs(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const end = div.querySelector(".endOfContent");
|
||||
if (!end) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -17,12 +17,14 @@ import {
|
|||
animationStarted,
|
||||
DEFAULT_SCALE,
|
||||
DEFAULT_SCALE_VALUE,
|
||||
docStyle,
|
||||
MAX_SCALE,
|
||||
MIN_SCALE,
|
||||
noContextMenuHandler,
|
||||
} from './ui_utils.js'
|
||||
} from "./ui_utils.js";
|
||||
import { AnnotationEditorType } from "./pdfjs";
|
||||
|
||||
const PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading'
|
||||
const PAGE_NUMBER_LOADING_INDICATOR = "visiblePageIsLoading";
|
||||
|
||||
/**
|
||||
* @typedef {Object} ToolbarOptions
|
||||
|
|
@ -40,38 +42,60 @@ const PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading'
|
|||
* @property {HTMLButtonElement} zoomOut - Button to zoom out the pages.
|
||||
* @property {HTMLButtonElement} viewFind - Button to open find bar.
|
||||
* @property {HTMLButtonElement} openFile - Button to open a new document.
|
||||
* @property {HTMLButtonElement} presentationModeButton - Button to switch to
|
||||
* presentation mode.
|
||||
* @property {HTMLButtonElement} editorFreeTextButton - Button to switch to
|
||||
* FreeText editing.
|
||||
* @property {HTMLButtonElement} download - Button to download the document.
|
||||
* @property {HTMLAnchorElement} viewBookmark - Button to obtain a bookmark link
|
||||
* to the current location in the document.
|
||||
*/
|
||||
|
||||
class Toolbar {
|
||||
#wasLocalized = false;
|
||||
|
||||
/**
|
||||
* @param {ToolbarOptions} options
|
||||
* @param {EventBus} eventBus
|
||||
* @param {IL10n} l10n - Localization service.
|
||||
*/
|
||||
constructor (options, eventBus, l10n) {
|
||||
this.toolbar = options.container
|
||||
this.eventBus = eventBus
|
||||
this.l10n = l10n
|
||||
constructor(options, eventBus, l10n) {
|
||||
this.toolbar = options.container;
|
||||
this.eventBus = eventBus;
|
||||
this.l10n = l10n;
|
||||
this.buttons = [
|
||||
{element: options.previous, eventName: 'previouspage'},
|
||||
{element: options.next, eventName: 'nextpage'},
|
||||
{element: options.zoomIn, eventName: 'zoomin'},
|
||||
{element: options.zoomOut, eventName: 'zoomout'},
|
||||
{ element: options.previous, eventName: "previouspage" },
|
||||
{ element: options.next, eventName: "nextpage" },
|
||||
{ element: options.zoomIn, eventName: "zoomin" },
|
||||
{ element: options.zoomOut, eventName: "zoomout" },
|
||||
// NOTE
|
||||
// {element: options.openFile, eventName: 'openfile'},
|
||||
// {element: options.print, eventName: 'print'},
|
||||
{
|
||||
element: options.presentationModeButton,
|
||||
eventName: 'presentationmode',
|
||||
},
|
||||
// {element: options.download, eventName: 'download'},
|
||||
{element: options.viewBookmark, eventName: null},
|
||||
]
|
||||
// { element: options.print, eventName: "print" },
|
||||
// { element: options.download, eventName: "download" },
|
||||
// {
|
||||
// element: options.editorFreeTextButton,
|
||||
// eventName: "switchannotationeditormode",
|
||||
// eventDetails: {
|
||||
// get mode() {
|
||||
// const { classList } = options.editorFreeTextButton;
|
||||
// return classList.contains("toggled")
|
||||
// ? AnnotationEditorType.NONE
|
||||
// : AnnotationEditorType.FREETEXT;
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// element: options.editorInkButton,
|
||||
// eventName: "switchannotationeditormode",
|
||||
// eventDetails: {
|
||||
// get mode() {
|
||||
// const { classList } = options.editorInkButton;
|
||||
// return classList.contains("toggled")
|
||||
// ? AnnotationEditorType.NONE
|
||||
// : AnnotationEditorType.INK;
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
];
|
||||
// NOTE
|
||||
// if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||
// this.buttons.push({ element: options.openFile, eventName: "openfile" });
|
||||
// }
|
||||
this.items = {
|
||||
numPages: options.numPages,
|
||||
pageNumber: options.pageNumber,
|
||||
|
|
@ -81,205 +105,251 @@ class Toolbar {
|
|||
next: options.next,
|
||||
zoomIn: options.zoomIn,
|
||||
zoomOut: options.zoomOut,
|
||||
}
|
||||
|
||||
this._wasLocalized = false
|
||||
this.reset()
|
||||
};
|
||||
|
||||
// Bind the event listeners for click and various other actions.
|
||||
this._bindListeners()
|
||||
this.#bindListeners(options);
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
setPageNumber (pageNumber, pageLabel) {
|
||||
this.pageNumber = pageNumber
|
||||
this.pageLabel = pageLabel
|
||||
this._updateUIState(false)
|
||||
setPageNumber(pageNumber, pageLabel) {
|
||||
this.pageNumber = pageNumber;
|
||||
this.pageLabel = pageLabel;
|
||||
this.#updateUIState(false);
|
||||
}
|
||||
|
||||
setPagesCount (pagesCount, hasPageLabels) {
|
||||
this.pagesCount = pagesCount
|
||||
this.hasPageLabels = hasPageLabels
|
||||
this._updateUIState(true)
|
||||
setPagesCount(pagesCount, hasPageLabels) {
|
||||
this.pagesCount = pagesCount;
|
||||
this.hasPageLabels = hasPageLabels;
|
||||
this.#updateUIState(true);
|
||||
}
|
||||
|
||||
setPageScale (pageScaleValue, pageScale) {
|
||||
this.pageScaleValue = (pageScaleValue || pageScale).toString()
|
||||
this.pageScale = pageScale
|
||||
this._updateUIState(false)
|
||||
setPageScale(pageScaleValue, pageScale) {
|
||||
this.pageScaleValue = (pageScaleValue || pageScale).toString();
|
||||
this.pageScale = pageScale;
|
||||
this.#updateUIState(false);
|
||||
}
|
||||
|
||||
reset () {
|
||||
this.pageNumber = 0
|
||||
this.pageLabel = null
|
||||
this.hasPageLabels = false
|
||||
this.pagesCount = 0
|
||||
this.pageScaleValue = DEFAULT_SCALE_VALUE
|
||||
this.pageScale = DEFAULT_SCALE
|
||||
this._updateUIState(true)
|
||||
this.updateLoadingIndicatorState()
|
||||
reset() {
|
||||
this.pageNumber = 0;
|
||||
this.pageLabel = null;
|
||||
this.hasPageLabels = false;
|
||||
this.pagesCount = 0;
|
||||
this.pageScaleValue = DEFAULT_SCALE_VALUE;
|
||||
this.pageScale = DEFAULT_SCALE;
|
||||
this.#updateUIState(true);
|
||||
this.updateLoadingIndicatorState();
|
||||
|
||||
// Reset the Editor buttons too, since they're document specific.
|
||||
this.eventBus.dispatch("toolbarreset", { source: this });
|
||||
}
|
||||
|
||||
_bindListeners () {
|
||||
const {pageNumber, scaleSelect} = this.items
|
||||
const self = this
|
||||
#bindListeners(options) {
|
||||
const { pageNumber, scaleSelect } = this.items;
|
||||
const self = this;
|
||||
|
||||
// The buttons within the toolbar.
|
||||
for (const {element, eventName} of this.buttons) {
|
||||
element.addEventListener('click', evt => {
|
||||
for (const { element, eventName, eventDetails } of this.buttons) {
|
||||
element.addEventListener("click", evt => {
|
||||
if (eventName !== null) {
|
||||
this.eventBus.dispatch(eventName, {source: this})
|
||||
const details = { source: this };
|
||||
if (eventDetails) {
|
||||
for (const property in eventDetails) {
|
||||
details[property] = eventDetails[property];
|
||||
}
|
||||
}
|
||||
this.eventBus.dispatch(eventName, details);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
// The non-button elements within the toolbar.
|
||||
pageNumber.addEventListener('click', function () {
|
||||
this.select()
|
||||
})
|
||||
pageNumber.addEventListener('change', function () {
|
||||
self.eventBus.dispatch('pagenumberchanged', {
|
||||
pageNumber.addEventListener("click", function () {
|
||||
this.select();
|
||||
});
|
||||
pageNumber.addEventListener("change", function () {
|
||||
self.eventBus.dispatch("pagenumberchanged", {
|
||||
source: self,
|
||||
value: this.value,
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
scaleSelect.addEventListener('change', function () {
|
||||
if (this.value === 'custom') {
|
||||
return
|
||||
scaleSelect.addEventListener("change", function () {
|
||||
if (this.value === "custom") {
|
||||
return;
|
||||
}
|
||||
self.eventBus.dispatch('scalechanged', {
|
||||
self.eventBus.dispatch("scalechanged", {
|
||||
source: self,
|
||||
value: this.value,
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
// Here we depend on browsers dispatching the "click" event *after* the
|
||||
// "change" event, when the <select>-element changes.
|
||||
scaleSelect.addEventListener('click', function (evt) {
|
||||
const target = evt.target
|
||||
scaleSelect.addEventListener("click", function (evt) {
|
||||
const target = evt.target;
|
||||
// Remove focus when an <option>-element was *clicked*, to improve the UX
|
||||
// for mouse users (fixes bug 1300525 and issue 4923).
|
||||
if (
|
||||
this.value === self.pageScaleValue &&
|
||||
target.tagName.toUpperCase() === 'OPTION'
|
||||
target.tagName.toUpperCase() === "OPTION"
|
||||
) {
|
||||
this.blur()
|
||||
this.blur();
|
||||
}
|
||||
})
|
||||
});
|
||||
// Suppress context menus for some controls.
|
||||
scaleSelect.oncontextmenu = noContextMenuHandler
|
||||
scaleSelect.oncontextmenu = noContextMenuHandler;
|
||||
|
||||
this.eventBus._on('localized', () => {
|
||||
this._wasLocalized = true
|
||||
this._adjustScaleWidth()
|
||||
this._updateUIState(true)
|
||||
})
|
||||
this.eventBus._on("localized", () => {
|
||||
this.#wasLocalized = true;
|
||||
this.#adjustScaleWidth();
|
||||
this.#updateUIState(true);
|
||||
});
|
||||
|
||||
// NOTE
|
||||
// this.#bindEditorToolsListener(options);
|
||||
}
|
||||
|
||||
_updateUIState (resetNumPages = false) {
|
||||
if (!this._wasLocalized) {
|
||||
#bindEditorToolsListener({
|
||||
editorFreeTextButton,
|
||||
editorFreeTextParamsToolbar,
|
||||
editorInkButton,
|
||||
editorInkParamsToolbar,
|
||||
}) {
|
||||
const editorModeChanged = (evt, disableButtons = false) => {
|
||||
const editorButtons = [
|
||||
{
|
||||
mode: AnnotationEditorType.FREETEXT,
|
||||
button: editorFreeTextButton,
|
||||
toolbar: editorFreeTextParamsToolbar,
|
||||
},
|
||||
{
|
||||
mode: AnnotationEditorType.INK,
|
||||
button: editorInkButton,
|
||||
toolbar: editorInkParamsToolbar,
|
||||
},
|
||||
];
|
||||
|
||||
for (const { mode, button, toolbar } of editorButtons) {
|
||||
const checked = mode === evt.mode;
|
||||
button.classList.toggle("toggled", checked);
|
||||
button.setAttribute("aria-checked", checked);
|
||||
button.disabled = disableButtons;
|
||||
// NOTE
|
||||
toolbar?.classList.toggle("fn__hidden", !checked);
|
||||
}
|
||||
};
|
||||
this.eventBus._on("annotationeditormodechanged", editorModeChanged);
|
||||
|
||||
this.eventBus._on("toolbarreset", evt => {
|
||||
if (evt.source === this) {
|
||||
editorModeChanged(
|
||||
{ mode: AnnotationEditorType.NONE },
|
||||
/* disableButtons = */ true
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#updateUIState(resetNumPages = false) {
|
||||
if (!this.#wasLocalized) {
|
||||
// Don't update the UI state until we localize the toolbar.
|
||||
return
|
||||
return;
|
||||
}
|
||||
const {pageNumber, pagesCount, pageScaleValue, pageScale, items} = this
|
||||
const { pageNumber, pagesCount, pageScaleValue, pageScale, items } = this;
|
||||
|
||||
if (resetNumPages) {
|
||||
if (this.hasPageLabels) {
|
||||
items.pageNumber.type = 'text'
|
||||
items.pageNumber.type = "text";
|
||||
} else {
|
||||
items.pageNumber.type = 'number'
|
||||
items.pageNumber.type = "number";
|
||||
items.numPages.textContent = "/ " + pagesCount;
|
||||
}
|
||||
items.pageNumber.max = pagesCount
|
||||
items.pageNumber.max = pagesCount;
|
||||
}
|
||||
|
||||
if (this.hasPageLabels) {
|
||||
items.pageNumber.value = this.pageLabel
|
||||
items.pageNumber.value = this.pageLabel;
|
||||
items.numPages.textContent = `(${pageNumber} / ${pagesCount})`
|
||||
} else {
|
||||
items.pageNumber.value = pageNumber
|
||||
items.pageNumber.value = pageNumber;
|
||||
}
|
||||
|
||||
items.previous.disabled = pageNumber <= 1
|
||||
items.next.disabled = pageNumber >= pagesCount
|
||||
items.previous.disabled = pageNumber <= 1;
|
||||
items.next.disabled = pageNumber >= pagesCount;
|
||||
|
||||
items.zoomOut.disabled = pageScale <= MIN_SCALE
|
||||
items.zoomIn.disabled = pageScale >= MAX_SCALE
|
||||
items.zoomOut.disabled = pageScale <= MIN_SCALE;
|
||||
items.zoomIn.disabled = pageScale >= MAX_SCALE;
|
||||
|
||||
let predefinedValueFound = false
|
||||
// NOTE
|
||||
let predefinedValueFound = false;
|
||||
for (const option of items.scaleSelect.options) {
|
||||
if (option.value !== pageScaleValue) {
|
||||
option.selected = false
|
||||
continue
|
||||
option.selected = false;
|
||||
continue;
|
||||
}
|
||||
option.selected = true
|
||||
predefinedValueFound = true
|
||||
option.selected = true;
|
||||
predefinedValueFound = true;
|
||||
}
|
||||
if (!predefinedValueFound) {
|
||||
items.customScaleOption.textContent = `${Math.round(pageScale * 10000) / 100}%`
|
||||
items.customScaleOption.selected = true
|
||||
items.customScaleOption.textContent = `${Math.round(pageScale * 10000) / 100}%`;
|
||||
items.customScaleOption.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
updateLoadingIndicatorState (loading = false) {
|
||||
const pageNumberInput = this.items.pageNumber
|
||||
updateLoadingIndicatorState(loading = false) {
|
||||
const { pageNumber } = this.items;
|
||||
|
||||
pageNumberInput.classList.toggle(PAGE_NUMBER_LOADING_INDICATOR, loading)
|
||||
pageNumber.classList.toggle(PAGE_NUMBER_LOADING_INDICATOR, loading);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the width of the zoom dropdown DOM element if, and only if, it's
|
||||
* too narrow to fit the *longest* of the localized strings.
|
||||
* @private
|
||||
*/
|
||||
async _adjustScaleWidth () {
|
||||
const {items, l10n} = this
|
||||
async #adjustScaleWidth() {
|
||||
const { items, l10n } = this;
|
||||
|
||||
const style = getComputedStyle(items.scaleSelect),
|
||||
scaleSelectContainerWidth = parseInt(
|
||||
style.getPropertyValue('--scale-select-container-width'),
|
||||
10,
|
||||
),
|
||||
scaleSelectOverflow = parseInt(
|
||||
style.getPropertyValue('--scale-select-overflow'),
|
||||
10,
|
||||
)
|
||||
|
||||
// The temporary canvas is used to measure text length in the DOM.
|
||||
let canvas = document.createElement('canvas')
|
||||
if (
|
||||
typeof PDFJSDev === 'undefined' ||
|
||||
PDFJSDev.test('MOZCENTRAL || GENERIC')
|
||||
) {
|
||||
canvas.mozOpaque = true
|
||||
}
|
||||
let ctx = canvas.getContext('2d', {alpha: false})
|
||||
|
||||
await animationStarted
|
||||
ctx.font = `${style.fontSize} ${style.fontFamily}`
|
||||
|
||||
let maxWidth = 0
|
||||
|
||||
for (const predefinedValue of [
|
||||
// NOTE
|
||||
const predefinedValuesPromise = [
|
||||
window.siyuan.languages.pageScaleAuto,
|
||||
window.siyuan.languages.pageScaleActual,
|
||||
window.siyuan.languages.pageScaleFit,
|
||||
window.siyuan.languages.pageScaleWidth]) {
|
||||
const {width} = ctx.measureText(predefinedValue)
|
||||
window.siyuan.languages.pageScaleWidth];
|
||||
|
||||
await animationStarted;
|
||||
|
||||
const style = getComputedStyle(items.scaleSelect),
|
||||
scaleSelectContainerWidth = parseInt(
|
||||
style.getPropertyValue("--scale-select-container-width"),
|
||||
10
|
||||
),
|
||||
scaleSelectOverflow = parseInt(
|
||||
style.getPropertyValue("--scale-select-overflow"),
|
||||
10
|
||||
);
|
||||
|
||||
// The temporary canvas is used to measure text length in the DOM.
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d", { alpha: false });
|
||||
ctx.font = `${style.fontSize} ${style.fontFamily}`;
|
||||
|
||||
let maxWidth = 0;
|
||||
for (const predefinedValue of predefinedValuesPromise) {
|
||||
const { width } = ctx.measureText(predefinedValue);
|
||||
if (width > maxWidth) {
|
||||
maxWidth = width
|
||||
maxWidth = width;
|
||||
}
|
||||
}
|
||||
maxWidth += 2 * scaleSelectOverflow
|
||||
maxWidth += 2 * scaleSelectOverflow;
|
||||
|
||||
if (maxWidth > scaleSelectContainerWidth) {
|
||||
const doc = document.documentElement
|
||||
doc.style.setProperty('--scale-select-container-width', `${maxWidth}px`)
|
||||
docStyle.setProperty("--scale-select-container-width", `${maxWidth}px`);
|
||||
}
|
||||
// Zeroing the width and height cause Firefox to release graphics resources
|
||||
// immediately, which can greatly reduce memory consumption.
|
||||
canvas.width = 0
|
||||
canvas.height = 0
|
||||
canvas = ctx = null
|
||||
canvas.width = 0;
|
||||
canvas.height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export { Toolbar }
|
||||
export { Toolbar };
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@ const MAX_AUTO_SCALE = 1.25;
|
|||
const SCROLLBAR_PADDING = 40;
|
||||
const VERTICAL_PADDING = 5;
|
||||
|
||||
const LOADINGBAR_END_OFFSET_VAR = "--loadingBar-end-offset";
|
||||
|
||||
const RenderingStates = {
|
||||
INITIAL: 0,
|
||||
RUNNING: 1,
|
||||
|
|
@ -48,15 +46,17 @@ const SidebarView = {
|
|||
LAYERS: 4,
|
||||
};
|
||||
|
||||
const RendererType = {
|
||||
CANVAS: "canvas",
|
||||
SVG: "svg",
|
||||
};
|
||||
const RendererType =
|
||||
typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || GENERIC")
|
||||
? {
|
||||
CANVAS: "canvas",
|
||||
SVG: "svg",
|
||||
}
|
||||
: null;
|
||||
|
||||
const TextLayerMode = {
|
||||
DISABLE: 0,
|
||||
ENABLE: 1,
|
||||
ENABLE_ENHANCE: 2,
|
||||
};
|
||||
|
||||
const ScrollMode = {
|
||||
|
|
@ -129,7 +129,7 @@ function scrollIntoView(element, spot, scrollMatches = false) {
|
|||
(scrollMatches &&
|
||||
(parent.classList.contains("markedContent") ||
|
||||
getComputedStyle(parent).overflow === "hidden"))
|
||||
) {
|
||||
) {
|
||||
offsetY += parent.offsetTop;
|
||||
offsetX += parent.offsetLeft;
|
||||
|
||||
|
|
@ -465,12 +465,12 @@ function backtrackBeforeAllVisibleElements(index, views, top) {
|
|||
* @returns {Object} `{ first, last, views: [{ id, x, y, view, percent }] }`
|
||||
*/
|
||||
function getVisibleElements({
|
||||
scrollEl,
|
||||
views,
|
||||
sortByVisibility = false,
|
||||
horizontal = false,
|
||||
rtl = false,
|
||||
}) {
|
||||
scrollEl,
|
||||
views,
|
||||
sortByVisibility = false,
|
||||
horizontal = false,
|
||||
rtl = false,
|
||||
}) {
|
||||
const top = scrollEl.scrollTop,
|
||||
bottom = top + scrollEl.clientHeight;
|
||||
const left = scrollEl.scrollLeft,
|
||||
|
|
@ -590,7 +590,7 @@ function getVisibleElements({
|
|||
}
|
||||
|
||||
const first = visible[0],
|
||||
last = visible[visible.length - 1];
|
||||
last = visible.at(-1);
|
||||
|
||||
if (sortByVisibility) {
|
||||
visible.sort(function (a, b) {
|
||||
|
|
@ -679,49 +679,43 @@ const animationStarted = new Promise(function (resolve) {
|
|||
window.requestAnimationFrame(resolve);
|
||||
});
|
||||
|
||||
const docStyle =
|
||||
typeof PDFJSDev !== "undefined" &&
|
||||
PDFJSDev.test("LIB") &&
|
||||
typeof document === "undefined"
|
||||
? null
|
||||
: document.documentElement.style;
|
||||
|
||||
function clamp(v, min, max) {
|
||||
return Math.min(Math.max(v, min), max);
|
||||
}
|
||||
|
||||
class ProgressBar {
|
||||
constructor(element, { height, width, units } = {}) {
|
||||
this.visible = true;
|
||||
#classList = null;
|
||||
|
||||
// Fetch the sub-elements for later.
|
||||
this.div = element.querySelector("#loadingBar .progress");
|
||||
// Get the loading bar element, so it can be resized to fit the viewer.
|
||||
this.bar = this.div.parentNode;
|
||||
#percent = 0;
|
||||
|
||||
// Get options, with sensible defaults.
|
||||
this.height = height || 100;
|
||||
this.width = width || 100;
|
||||
this.units = units || "%";
|
||||
#visible = true;
|
||||
|
||||
// Initialize heights.
|
||||
this.div.style.height = this.height + this.units;
|
||||
this.percent = 0;
|
||||
}
|
||||
|
||||
_updateBar() {
|
||||
if (this._indeterminate) {
|
||||
this.div.classList.add("indeterminate");
|
||||
this.div.style.width = this.width + this.units;
|
||||
return;
|
||||
}
|
||||
|
||||
this.div.classList.remove("indeterminate");
|
||||
const progressSize = (this.width * this._percent) / 100;
|
||||
this.div.style.width = progressSize + this.units;
|
||||
constructor(id) {
|
||||
const bar = document.getElementById(id);
|
||||
this.#classList = bar.classList;
|
||||
}
|
||||
|
||||
get percent() {
|
||||
return this._percent;
|
||||
return this.#percent;
|
||||
}
|
||||
|
||||
set percent(val) {
|
||||
this._indeterminate = isNaN(val);
|
||||
this._percent = clamp(val, 0, 100);
|
||||
this._updateBar();
|
||||
this.#percent = clamp(val, 0, 100);
|
||||
|
||||
if (isNaN(val)) {
|
||||
this.#classList.add("indeterminate");
|
||||
return;
|
||||
}
|
||||
this.#classList.remove("indeterminate");
|
||||
|
||||
docStyle.setProperty("--progressBar-percent", `${this.#percent}%`);
|
||||
}
|
||||
|
||||
setWidth(viewer) {
|
||||
|
|
@ -731,25 +725,26 @@ class ProgressBar {
|
|||
const container = viewer.parentNode;
|
||||
const scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
|
||||
if (scrollbarWidth > 0) {
|
||||
const doc = document.documentElement;
|
||||
doc.style.setProperty(LOADINGBAR_END_OFFSET_VAR, `${scrollbarWidth}px`);
|
||||
docStyle.setProperty("--progressBar-end-offset", `${scrollbarWidth}px`);
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (!this.visible) {
|
||||
if (!this.#visible) {
|
||||
return;
|
||||
}
|
||||
this.visible = false;
|
||||
this.bar.classList.add("fn__hidden");
|
||||
this.#visible = false;
|
||||
// NOTE
|
||||
this.#classList.add("fn__hidden");
|
||||
}
|
||||
|
||||
show() {
|
||||
if (this.visible) {
|
||||
if (this.#visible) {
|
||||
return;
|
||||
}
|
||||
this.visible = true;
|
||||
this.bar.classList.remove("fn__hidden");
|
||||
this.#visible = true;
|
||||
// NOTE
|
||||
this.#classList.remove("fn__hidden");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -844,6 +839,7 @@ export {
|
|||
DEFAULT_SCALE,
|
||||
DEFAULT_SCALE_DELTA,
|
||||
DEFAULT_SCALE_VALUE,
|
||||
docStyle,
|
||||
getActiveOrFocusedElement,
|
||||
getPageSizeInches,
|
||||
getVisibleElements,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RenderingStates, ScrollMode, SpreadMode } from './ui_utils.js'
|
||||
import { AppOptions } from './app_options.js'
|
||||
import { LinkTarget } from './pdf_link_service.js'
|
||||
import { PDFViewerApplication } from './app.js'
|
||||
import { initAnno } from '../anno'
|
||||
|
||||
|
|
@ -24,18 +26,22 @@ const pdfjsVersion =
|
|||
const pdfjsBuild =
|
||||
typeof PDFJSDev !== 'undefined' ? PDFJSDev.eval('BUNDLE_BUILD') : void 0
|
||||
|
||||
const AppConstants =
|
||||
typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')
|
||||
? {LinkTarget, RenderingStates, ScrollMode, SpreadMode}
|
||||
: null
|
||||
|
||||
window.PDFViewerApplication = PDFViewerApplication
|
||||
window.PDFViewerApplicationConstants = AppConstants
|
||||
window.PDFViewerApplicationOptions = AppOptions
|
||||
|
||||
if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('CHROME')) {
|
||||
var defaultUrl; // eslint-disable-line no-var
|
||||
|
||||
(function rewriteUrlClosure () {
|
||||
// Run this code outside DOMContentLoaded to make sure that the URL
|
||||
// is rewritten as soon as possible.
|
||||
const queryString = document.location.search.slice(1)
|
||||
const m = /(^|&)file=([^&]*)/.exec(queryString)
|
||||
defaultUrl = m ? decodeURIComponent(m[2]) : ''
|
||||
const defaultUrl = m ? decodeURIComponent(m[2]) : ''
|
||||
|
||||
// Example: chrome-extension://.../http://example.com/file.pdf
|
||||
const humanReadableUrl = '/' + defaultUrl + location.hash
|
||||
|
|
@ -44,27 +50,34 @@ if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('CHROME')) {
|
|||
// eslint-disable-next-line no-undef
|
||||
chrome.runtime.sendMessage('showPageAction')
|
||||
}
|
||||
|
||||
AppOptions.set('defaultUrl', defaultUrl)
|
||||
})()
|
||||
}
|
||||
|
||||
function getViewerConfiguration (element) {
|
||||
let errorWrapper = null
|
||||
if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('MOZCENTRAL')) {
|
||||
errorWrapper = {
|
||||
container: element.querySelector('#errorWrapper'),
|
||||
errorMessage: element.querySelector('#errorMessage'),
|
||||
closeButton: element.querySelector('#errorClose'),
|
||||
errorMoreInfo: element.querySelector('#errorMoreInfo'),
|
||||
moreInfoButton: element.querySelector('#errorShowMore'),
|
||||
lessInfoButton: element.querySelector('#errorShowLess'),
|
||||
}
|
||||
}
|
||||
// NOTE
|
||||
// if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('MOZCENTRAL')) {
|
||||
// require('./firefoxcom.js')
|
||||
// require('./firefox_print_service.js')
|
||||
// }
|
||||
// if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('GENERIC')) {
|
||||
// require('./genericcom.js')
|
||||
// }
|
||||
// if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('CHROME')) {
|
||||
// require('./chromecom.js')
|
||||
// }
|
||||
// if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('CHROME || GENERIC')) {
|
||||
// require('./pdf_print_service.js')
|
||||
// }
|
||||
|
||||
// NOTE
|
||||
function getViewerConfiguration (element) {
|
||||
return {
|
||||
appContainer: element,
|
||||
mainContainer: element.querySelector('#viewerContainer'),
|
||||
viewerContainer: element.querySelector('#viewer'),
|
||||
toolbar: {
|
||||
// NOTE
|
||||
rectAnno: element.querySelector('#rectAnno'),
|
||||
container: element.querySelector('#toolbarViewer'),
|
||||
numPages: element.querySelector('#numPages'),
|
||||
|
|
@ -76,23 +89,28 @@ function getViewerConfiguration (element) {
|
|||
zoomIn: element.querySelector('#zoomIn'),
|
||||
zoomOut: element.querySelector('#zoomOut'),
|
||||
viewFind: element.querySelector('#viewFind'),
|
||||
openFile: element.querySelector('#openFile'),
|
||||
openFile:
|
||||
typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')
|
||||
? element.querySelector('#openFile')
|
||||
: null,
|
||||
print: element.querySelector('#print'),
|
||||
presentationModeButton: element.querySelector('#presentationMode'),
|
||||
editorFreeTextButton: element.querySelector('#editorFreeText'),
|
||||
editorFreeTextParamsToolbar: element.querySelector('#editorFreeTextParamsToolbar'),
|
||||
editorInkButton: element.querySelector('#editorInk'),
|
||||
editorInkParamsToolbar: element.querySelector('#editorInkParamsToolbar'),
|
||||
download: element.querySelector('#download'),
|
||||
viewBookmark: element.querySelector('#viewBookmark'),
|
||||
},
|
||||
secondaryToolbar: {
|
||||
toolbar: element.querySelector('#secondaryToolbar'),
|
||||
toggleButton: element.querySelector('#secondaryToolbarToggle'),
|
||||
toolbarButtonContainer: element.querySelector(
|
||||
'#secondaryToolbarButtonContainer'),
|
||||
presentationModeButton: element.querySelector(
|
||||
'#secondaryPresentationMode'),
|
||||
openFileButton: element.querySelector('#secondaryOpenFile'),
|
||||
presentationModeButton: element.querySelector('#presentationMode'),
|
||||
openFileButton:
|
||||
typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')
|
||||
? element.querySelector('#secondaryOpenFile')
|
||||
: null,
|
||||
printButton: element.querySelector('#secondaryPrint'),
|
||||
downloadButton: element.querySelector('#secondaryDownload'),
|
||||
viewBookmarkButton: element.querySelector('#secondaryViewBookmark'),
|
||||
viewBookmarkButton: element.querySelector('#viewBookmark'),
|
||||
firstPageButton: element.querySelector('#firstPage'),
|
||||
lastPageButton: element.querySelector('#lastPage'),
|
||||
pageRotateCwButton: element.querySelector('#pageRotateCw'),
|
||||
|
|
@ -111,7 +129,7 @@ function getViewerConfiguration (element) {
|
|||
sidebar: {
|
||||
// Divs (and sidebar button)
|
||||
outerContainer: element.querySelector('#outerContainer'),
|
||||
viewerContainer: element.querySelector('#viewerContainer'),
|
||||
sidebarContainer: element.querySelector('#sidebarContainer'),
|
||||
toggleButton: element.querySelector('#sidebarToggle'),
|
||||
// Buttons
|
||||
thumbnailButton: element.querySelector('#viewThumbnail'),
|
||||
|
|
@ -124,8 +142,7 @@ function getViewerConfiguration (element) {
|
|||
attachmentsView: element.querySelector('#attachmentsView'),
|
||||
layersView: element.querySelector('#layersView'),
|
||||
// View-specific options
|
||||
outlineOptionsContainer: element.querySelector(
|
||||
'#outlineOptionsContainer'),
|
||||
outlineOptionsContainer: element.querySelector('#outlineOptionsContainer'),
|
||||
currentOutlineItemButton: element.querySelector('#currentOutlineItem'),
|
||||
},
|
||||
sidebarResizer: {
|
||||
|
|
@ -146,16 +163,14 @@ function getViewerConfiguration (element) {
|
|||
findNextButton: element.querySelector('#findNext'),
|
||||
},
|
||||
passwordOverlay: {
|
||||
overlayName: 'passwordOverlay',
|
||||
container: element.querySelector('#passwordOverlay'),
|
||||
dialog: element.querySelector('#passwordDialog'),
|
||||
label: element.querySelector('#passwordText'),
|
||||
input: element.querySelector('#password'),
|
||||
submitButton: element.querySelector('#passwordSubmit'),
|
||||
cancelButton: element.querySelector('#passwordCancel'),
|
||||
},
|
||||
documentProperties: {
|
||||
overlayName: 'documentPropertiesOverlay',
|
||||
container: element.querySelector('#documentPropertiesOverlay'),
|
||||
dialog: element.querySelector('#documentPropertiesDialog'),
|
||||
closeButton: element.querySelector('#documentPropertiesClose'),
|
||||
fields: {
|
||||
fileName: element.querySelector('#fileNameField'),
|
||||
|
|
@ -174,9 +189,29 @@ function getViewerConfiguration (element) {
|
|||
linearized: element.querySelector('#linearizedField'),
|
||||
},
|
||||
},
|
||||
errorWrapper,
|
||||
annotationEditorParams: {
|
||||
editorFreeTextFontSize: element.querySelector('#editorFreeTextFontSize'),
|
||||
editorFreeTextColor: element.querySelector('#editorFreeTextColor'),
|
||||
editorInkColor: element.querySelector('#editorInkColor'),
|
||||
editorInkThickness: element.querySelector('#editorInkThickness'),
|
||||
editorInkOpacity: element.querySelector('#editorInkOpacity'),
|
||||
},
|
||||
errorWrapper:
|
||||
typeof PDFJSDev === 'undefined' || !PDFJSDev.test('MOZCENTRAL')
|
||||
? {
|
||||
container: element.querySelector('#errorWrapper'),
|
||||
errorMessage: element.querySelector('#errorMessage'),
|
||||
closeButton: element.querySelector('#errorClose'),
|
||||
errorMoreInfo: element.querySelector('#errorMoreInfo'),
|
||||
moreInfoButton: element.querySelector('#errorShowMore'),
|
||||
lessInfoButton: element.querySelector('#errorShowLess'),
|
||||
}
|
||||
: null,
|
||||
printContainer: element.querySelector('#printContainer'),
|
||||
openFileInputName: 'fileInput',
|
||||
openFileInput:
|
||||
typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')
|
||||
? element.querySelector('#fileInput')
|
||||
: null,
|
||||
debuggerScriptPath: './debugger.js',
|
||||
}
|
||||
}
|
||||
|
|
@ -189,10 +224,6 @@ function webViewerLoad (file, element, pdfPage, annoId) {
|
|||
if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('PRODUCTION')) {
|
||||
config.file = file
|
||||
} else {
|
||||
if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('CHROME')) {
|
||||
AppOptions.set('defaultUrl', defaultUrl)
|
||||
}
|
||||
|
||||
if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('GENERIC')) {
|
||||
// Give custom implementations of the default viewer a simpler way to
|
||||
// set various `AppOptions`, by dispatching an event once all viewer
|
||||
|
|
@ -221,8 +252,20 @@ function webViewerLoad (file, element, pdfPage, annoId) {
|
|||
|
||||
// Block the "load" event until all pages are loaded, to ensure that printing
|
||||
// works in Firefox; see https://bugzilla.mozilla.org/show_bug.cgi?id=1618553
|
||||
if (document.blockUnblockOnload) {
|
||||
document.blockUnblockOnload(true)
|
||||
}
|
||||
|
||||
export { PDFViewerApplication, webViewerLoad }
|
||||
document.blockUnblockOnload?.(true)
|
||||
// NOTE
|
||||
// if (
|
||||
// document.readyState === 'interactive' ||
|
||||
// document.readyState === 'complete'
|
||||
// ) {
|
||||
// webViewerLoad()
|
||||
// } else {
|
||||
// document.addEventListener('DOMContentLoaded', webViewerLoad, true)
|
||||
// }
|
||||
|
||||
// NOTE
|
||||
export {
|
||||
PDFViewerApplication,
|
||||
webViewerLoad
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
||||
|
||||
import { XfaLayer } from "./pdfjs";
|
||||
|
||||
/**
|
||||
|
|
@ -65,7 +70,7 @@ class XfaLayerBuilder {
|
|||
|
||||
// Create an xfa layer div and render the form
|
||||
const div = document.createElement("div");
|
||||
this.pageDiv.appendChild(div);
|
||||
this.pageDiv.append(div);
|
||||
parameters.div = div;
|
||||
|
||||
const result = XfaLayer.render(parameters);
|
||||
|
|
@ -94,7 +99,7 @@ class XfaLayerBuilder {
|
|||
}
|
||||
// Create an xfa layer div and render the form
|
||||
this.div = document.createElement("div");
|
||||
this.pageDiv.appendChild(this.div);
|
||||
this.pageDiv.append(this.div);
|
||||
parameters.div = this.div;
|
||||
return XfaLayer.render(parameters);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -544,7 +544,7 @@ export class Wnd {
|
|||
return;
|
||||
}
|
||||
if (model instanceof Asset) {
|
||||
if (model.pdfObject) {
|
||||
if (model.pdfObject && model.pdfObject.pdfLoadingTask) {
|
||||
model.pdfObject.pdfLoadingTask.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
app/stage/protyle/js/pdf/pdf.js
vendored
6
app/stage/protyle/js/pdf/pdf.js
vendored
File diff suppressed because one or more lines are too long
6
app/stage/protyle/js/pdf/pdf.worker.js
vendored
6
app/stage/protyle/js/pdf/pdf.worker.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue