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