mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-22 16:18:08 +02:00
[Editor] Improve a11y for newly added element (#15109)
- In the annotationEditorLayer, reorder the editors in the DOM according the position of the elements on the screen; - add an aria-owns attribute on the "nearest" element in the text layer which points to the added editor.
This commit is contained in:
parent
ad15532235
commit
624b26e1de
15 changed files with 467 additions and 96 deletions
|
@ -601,7 +601,40 @@ function getColorValues(colors) {
|
|||
span.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use binary search to find the index of the first item in a given array which
|
||||
* passes a given condition. The items are expected to be sorted in the sense
|
||||
* that if the condition is true for one item in the array, then it is also true
|
||||
* for all following items.
|
||||
*
|
||||
* @returns {number} Index of the first array element to pass the test,
|
||||
* or |items.length| if no such element exists.
|
||||
*/
|
||||
function binarySearchFirstItem(items, condition, start = 0) {
|
||||
let minIndex = start;
|
||||
let maxIndex = items.length - 1;
|
||||
|
||||
if (maxIndex < 0 || !condition(items[maxIndex])) {
|
||||
return items.length;
|
||||
}
|
||||
if (condition(items[minIndex])) {
|
||||
return minIndex;
|
||||
}
|
||||
|
||||
while (minIndex < maxIndex) {
|
||||
const currentIndex = (minIndex + maxIndex) >> 1;
|
||||
const currentItem = items[currentIndex];
|
||||
if (condition(currentItem)) {
|
||||
maxIndex = currentIndex;
|
||||
} else {
|
||||
minIndex = currentIndex + 1;
|
||||
}
|
||||
}
|
||||
return minIndex; /* === maxIndex */
|
||||
}
|
||||
|
||||
export {
|
||||
binarySearchFirstItem,
|
||||
deprecated,
|
||||
DOMCanvasFactory,
|
||||
DOMCMapReaderFactory,
|
||||
|
|
|
@ -20,8 +20,9 @@
|
|||
/** @typedef {import("../annotation_storage.js").AnnotationStorage} AnnotationStorage */
|
||||
/** @typedef {import("../../web/interfaces").IL10n} IL10n */
|
||||
|
||||
import { AnnotationEditorType, shadow } from "../../shared/util.js";
|
||||
import { bindEvents, KeyboardManager } from "./tools.js";
|
||||
import { AnnotationEditorType } from "../../shared/util.js";
|
||||
import { binarySearchFirstItem } from "../display_utils.js";
|
||||
import { FreeTextEditor } from "./freetext.js";
|
||||
import { InkEditor } from "./ink.js";
|
||||
|
||||
|
@ -50,8 +51,14 @@ class AnnotationEditorLayer {
|
|||
|
||||
#isCleaningUp = false;
|
||||
|
||||
#textLayerMap = new WeakMap();
|
||||
|
||||
#textNodes = new Map();
|
||||
|
||||
#uiManager;
|
||||
|
||||
#waitingEditors = new Set();
|
||||
|
||||
static _initialized = false;
|
||||
|
||||
static _keyboardManager = new KeyboardManager([
|
||||
|
@ -88,6 +95,7 @@ class AnnotationEditorLayer {
|
|||
if (!AnnotationEditorLayer._initialized) {
|
||||
AnnotationEditorLayer._initialized = true;
|
||||
FreeTextEditor.initialize(options.l10n);
|
||||
InkEditor.initialize(options.l10n);
|
||||
|
||||
options.uiManager.registerEditorTypes([FreeTextEditor, InkEditor]);
|
||||
}
|
||||
|
@ -98,11 +106,40 @@ class AnnotationEditorLayer {
|
|||
this.#boundClick = this.click.bind(this);
|
||||
this.#boundMousedown = this.mousedown.bind(this);
|
||||
|
||||
for (const editor of this.#uiManager.getEditors(options.pageIndex)) {
|
||||
this.add(editor);
|
||||
this.#uiManager.addLayer(this);
|
||||
}
|
||||
|
||||
get textLayerElements() {
|
||||
// When zooming the text layer is removed from the DOM and sometimes
|
||||
// it's rebuilt hence the nodes are no longer valid.
|
||||
|
||||
const textLayer = this.div.parentNode
|
||||
.getElementsByClassName("textLayer")
|
||||
.item(0);
|
||||
|
||||
if (!textLayer) {
|
||||
return shadow(this, "textLayerElements", null);
|
||||
}
|
||||
|
||||
this.#uiManager.addLayer(this);
|
||||
let textChildren = this.#textLayerMap.get(textLayer);
|
||||
if (textChildren) {
|
||||
return textChildren;
|
||||
}
|
||||
|
||||
textChildren = textLayer.querySelectorAll(`span[role="presentation"]`);
|
||||
if (textChildren.length === 0) {
|
||||
return shadow(this, "textLayerElements", null);
|
||||
}
|
||||
|
||||
textChildren = Array.from(textChildren);
|
||||
textChildren.sort(AnnotationEditorLayer.#compareElementPositions);
|
||||
this.#textLayerMap.set(textLayer, textChildren);
|
||||
|
||||
return textChildren;
|
||||
}
|
||||
|
||||
get #hasTextLayer() {
|
||||
return !!this.div.parentNode.querySelector(".textLayer .endOfContent");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,6 +267,9 @@ class AnnotationEditorLayer {
|
|||
*/
|
||||
enable() {
|
||||
this.div.style.pointerEvents = "auto";
|
||||
for (const editor of this.#editors.values()) {
|
||||
editor.enableEditing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -237,6 +277,9 @@ class AnnotationEditorLayer {
|
|||
*/
|
||||
disable() {
|
||||
this.div.style.pointerEvents = "none";
|
||||
for (const editor of this.#editors.values()) {
|
||||
editor.disableEditing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -276,6 +319,7 @@ class AnnotationEditorLayer {
|
|||
|
||||
detach(editor) {
|
||||
this.#editors.delete(editor.id);
|
||||
this.removePointerInTextLayer(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -311,12 +355,12 @@ class AnnotationEditorLayer {
|
|||
}
|
||||
|
||||
if (this.#uiManager.isActive(editor)) {
|
||||
editor.parent.setActiveEditor(null);
|
||||
editor.parent?.setActiveEditor(null);
|
||||
}
|
||||
|
||||
this.attach(editor);
|
||||
editor.pageIndex = this.pageIndex;
|
||||
editor.parent.detach(editor);
|
||||
editor.parent?.detach(editor);
|
||||
editor.parent = this;
|
||||
if (editor.div && editor.isAttachedToDOM) {
|
||||
editor.div.remove();
|
||||
|
@ -324,6 +368,147 @@ class AnnotationEditorLayer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the positions of two elements, it must correspond to
|
||||
* the visual ordering.
|
||||
*
|
||||
* @param {HTMLElement} e1
|
||||
* @param {HTMLElement} e2
|
||||
* @returns {number}
|
||||
*/
|
||||
static #compareElementPositions(e1, e2) {
|
||||
const rect1 = e1.getBoundingClientRect();
|
||||
const rect2 = e2.getBoundingClientRect();
|
||||
|
||||
if (rect1.y + rect1.height <= rect2.y) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rect2.y + rect2.height <= rect1.y) {
|
||||
return +1;
|
||||
}
|
||||
|
||||
const centerX1 = rect1.x + rect1.width / 2;
|
||||
const centerX2 = rect2.x + rect2.width / 2;
|
||||
|
||||
return centerX1 - centerX2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when the text layer has finished rendering.
|
||||
*/
|
||||
onTextLayerRendered() {
|
||||
this.#textNodes.clear();
|
||||
for (const editor of this.#waitingEditors) {
|
||||
if (editor.isAttachedToDOM) {
|
||||
this.addPointerInTextLayer(editor);
|
||||
}
|
||||
}
|
||||
this.#waitingEditors.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an aria-owns id from a node in the text layer.
|
||||
* @param {AnnotationEditor} editor
|
||||
*/
|
||||
removePointerInTextLayer(editor) {
|
||||
if (!this.#hasTextLayer) {
|
||||
this.#waitingEditors.delete(editor);
|
||||
return;
|
||||
}
|
||||
|
||||
const { id } = editor;
|
||||
const node = this.#textNodes.get(id);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#textNodes.delete(id);
|
||||
let owns = node.getAttribute("aria-owns");
|
||||
if (owns?.includes(id)) {
|
||||
owns = owns
|
||||
.split(" ")
|
||||
.filter(x => x !== id)
|
||||
.join(" ");
|
||||
if (owns) {
|
||||
node.setAttribute("aria-owns", owns);
|
||||
} else {
|
||||
node.removeAttribute("aria-owns");
|
||||
node.setAttribute("role", "presentation");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the text node which is the nearest and add an aria-owns attribute
|
||||
* in order to correctly position this editor in the text flow.
|
||||
* @param {AnnotationEditor} editor
|
||||
*/
|
||||
addPointerInTextLayer(editor) {
|
||||
if (!this.#hasTextLayer) {
|
||||
// The text layer needs to be there, so we postpone the association.
|
||||
this.#waitingEditors.add(editor);
|
||||
return;
|
||||
}
|
||||
|
||||
this.removePointerInTextLayer(editor);
|
||||
|
||||
const children = this.textLayerElements;
|
||||
if (!children) {
|
||||
return;
|
||||
}
|
||||
const { contentDiv } = editor;
|
||||
const id = editor.getIdForTextLayer();
|
||||
|
||||
const index = binarySearchFirstItem(
|
||||
children,
|
||||
node =>
|
||||
AnnotationEditorLayer.#compareElementPositions(contentDiv, node) < 0
|
||||
);
|
||||
const node = children[Math.max(0, index - 1)];
|
||||
const owns = node.getAttribute("aria-owns");
|
||||
if (!owns?.includes(id)) {
|
||||
node.setAttribute("aria-owns", owns ? `${owns} ${id}` : id);
|
||||
}
|
||||
node.removeAttribute("role");
|
||||
|
||||
this.#textNodes.set(id, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a div in the DOM in order to respect the visual order.
|
||||
* @param {HTMLDivElement} div
|
||||
*/
|
||||
moveDivInDOM(editor) {
|
||||
this.addPointerInTextLayer(editor);
|
||||
|
||||
const { div, contentDiv } = editor;
|
||||
if (!this.div.hasChildNodes()) {
|
||||
this.div.append(div);
|
||||
return;
|
||||
}
|
||||
|
||||
const children = Array.from(this.div.childNodes).filter(
|
||||
node => node !== div
|
||||
);
|
||||
|
||||
if (children.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = binarySearchFirstItem(
|
||||
children,
|
||||
node =>
|
||||
AnnotationEditorLayer.#compareElementPositions(contentDiv, node) < 0
|
||||
);
|
||||
|
||||
if (index === 0) {
|
||||
children[0].before(div);
|
||||
} else {
|
||||
children[index - 1].after(div);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new editor in the current view.
|
||||
* @param {AnnotationEditor} editor
|
||||
|
@ -340,6 +525,7 @@ class AnnotationEditorLayer {
|
|||
editor.isAttachedToDOM = true;
|
||||
}
|
||||
|
||||
this.moveDivInDOM(editor);
|
||||
editor.onceAdded();
|
||||
}
|
||||
|
||||
|
@ -493,6 +679,8 @@ class AnnotationEditorLayer {
|
|||
const endY = event.clientY - rect.y;
|
||||
|
||||
editor.translate(endX - editor.startX, endY - editor.startY);
|
||||
this.moveDivInDOM(editor);
|
||||
editor.div.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -517,13 +705,20 @@ class AnnotationEditorLayer {
|
|||
* Destroy the main editor.
|
||||
*/
|
||||
destroy() {
|
||||
if (this.#uiManager.getActive()?.parent === this) {
|
||||
this.#uiManager.setActiveEditor(null);
|
||||
}
|
||||
|
||||
for (const editor of this.#editors.values()) {
|
||||
this.removePointerInTextLayer(editor);
|
||||
editor.isAttachedToDOM = false;
|
||||
editor.div.remove();
|
||||
editor.parent = null;
|
||||
this.div = null;
|
||||
}
|
||||
this.#textNodes.clear();
|
||||
this.div = null;
|
||||
this.#editors.clear();
|
||||
this.#waitingEditors.clear();
|
||||
this.#uiManager.removeLayer(this);
|
||||
}
|
||||
|
||||
|
@ -548,6 +743,9 @@ class AnnotationEditorLayer {
|
|||
this.viewport = parameters.viewport;
|
||||
bindEvents(this, this.div, ["dragover", "drop", "keydown"]);
|
||||
this.setDimensions();
|
||||
for (const editor of this.#uiManager.getEditors(this.pageIndex)) {
|
||||
this.add(editor);
|
||||
}
|
||||
this.updateMode();
|
||||
}
|
||||
|
||||
|
|
|
@ -220,7 +220,7 @@ class AnnotationEditor {
|
|||
this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360);
|
||||
this.div.className = this.name;
|
||||
this.div.setAttribute("id", this.id);
|
||||
this.div.tabIndex = 100;
|
||||
this.div.tabIndex = 0;
|
||||
|
||||
const [tx, ty] = this.getInitialTranslation();
|
||||
this.translate(tx, ty);
|
||||
|
@ -454,6 +454,26 @@ class AnnotationEditor {
|
|||
*/
|
||||
updateParams(type, value) {}
|
||||
|
||||
/**
|
||||
* When the user disables the editing mode some editors can change some of
|
||||
* their properties.
|
||||
*/
|
||||
disableEditing() {}
|
||||
|
||||
/**
|
||||
* When the user enables the editing mode some editors can change some of
|
||||
* their properties.
|
||||
*/
|
||||
enableEditing() {}
|
||||
|
||||
/**
|
||||
* Get the id to use in aria-owns when a link is done in the text layer.
|
||||
* @returns {string}
|
||||
*/
|
||||
getIdForTextLayer() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get some properties to update in the UI.
|
||||
* @returns {Object}
|
||||
|
@ -461,6 +481,13 @@ class AnnotationEditor {
|
|||
get propertiesToUpdate() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the div which really contains the displayed content.
|
||||
*/
|
||||
get contentDiv() {
|
||||
return this.div;
|
||||
}
|
||||
}
|
||||
|
||||
export { AnnotationEditor };
|
||||
|
|
|
@ -60,7 +60,13 @@ class FreeTextEditor extends AnnotationEditor {
|
|||
}
|
||||
|
||||
static initialize(l10n) {
|
||||
this._l10nPromise = l10n.get("free_text_default_content");
|
||||
this._l10nPromise = new Map(
|
||||
["free_text_default_content", "editor_free_text_aria_label"].map(str => [
|
||||
str,
|
||||
l10n.get(str),
|
||||
])
|
||||
);
|
||||
|
||||
const style = getComputedStyle(document.documentElement);
|
||||
|
||||
if (
|
||||
|
@ -117,7 +123,6 @@ class FreeTextEditor extends AnnotationEditor {
|
|||
];
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
get propertiesToUpdate() {
|
||||
return [
|
||||
[AnnotationEditorParamsType.FREETEXT_SIZE, this.#fontSize],
|
||||
|
@ -204,6 +209,7 @@ class FreeTextEditor extends AnnotationEditor {
|
|||
this.overlayDiv.classList.remove("enabled");
|
||||
this.editorDiv.contentEditable = true;
|
||||
this.div.draggable = false;
|
||||
this.div.removeAttribute("tabIndex");
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
|
@ -213,6 +219,7 @@ class FreeTextEditor extends AnnotationEditor {
|
|||
this.overlayDiv.classList.add("enabled");
|
||||
this.editorDiv.contentEditable = false;
|
||||
this.div.draggable = true;
|
||||
this.div.tabIndex = 0;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
|
@ -300,6 +307,34 @@ class FreeTextEditor extends AnnotationEditor {
|
|||
this.editorDiv.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* onkeydown callback.
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
keyup(event) {
|
||||
if (event.key === "Enter") {
|
||||
this.enableEditMode();
|
||||
this.editorDiv.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
disableEditing() {
|
||||
this.editorDiv.setAttribute("role", "comment");
|
||||
this.editorDiv.removeAttribute("aria-multiline");
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
enableEditing() {
|
||||
this.editorDiv.setAttribute("role", "textbox");
|
||||
this.editorDiv.setAttribute("aria-multiline", true);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
getIdForTextLayer() {
|
||||
return this.editorDiv.id;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
render() {
|
||||
if (this.div) {
|
||||
|
@ -314,12 +349,18 @@ class FreeTextEditor extends AnnotationEditor {
|
|||
|
||||
super.render();
|
||||
this.editorDiv = document.createElement("div");
|
||||
this.editorDiv.tabIndex = 0;
|
||||
this.editorDiv.className = "internal";
|
||||
|
||||
FreeTextEditor._l10nPromise.then(msg =>
|
||||
this.editorDiv.setAttribute("default-content", msg)
|
||||
);
|
||||
this.editorDiv.setAttribute("id", `${this.id}-editor`);
|
||||
this.enableEditing();
|
||||
|
||||
FreeTextEditor._l10nPromise
|
||||
.get("editor_free_text_aria_label")
|
||||
.then(msg => this.editorDiv?.setAttribute("aria-label", msg));
|
||||
|
||||
FreeTextEditor._l10nPromise
|
||||
.get("free_text_default_content")
|
||||
.then(msg => this.editorDiv?.setAttribute("default-content", msg));
|
||||
this.editorDiv.contentEditable = true;
|
||||
|
||||
const { style } = this.editorDiv;
|
||||
|
@ -335,7 +376,7 @@ class FreeTextEditor extends AnnotationEditor {
|
|||
// TODO: implement paste callback.
|
||||
// The goal is to sanitize and have something suitable for this
|
||||
// editor.
|
||||
bindEvents(this, this.div, ["dblclick"]);
|
||||
bindEvents(this, this.div, ["dblclick", "keyup"]);
|
||||
|
||||
if (this.width) {
|
||||
// This editor was created in using copy (ctrl+c).
|
||||
|
@ -354,6 +395,10 @@ class FreeTextEditor extends AnnotationEditor {
|
|||
return this.div;
|
||||
}
|
||||
|
||||
get contentDiv() {
|
||||
return this.editorDiv;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
static deserialize(data, parent) {
|
||||
const editor = super.deserialize(data, parent);
|
||||
|
|
|
@ -58,6 +58,8 @@ class InkEditor extends AnnotationEditor {
|
|||
|
||||
static _defaultThickness = 1;
|
||||
|
||||
static _l10nPromise;
|
||||
|
||||
constructor(params) {
|
||||
super({ ...params, name: "inkEditor" });
|
||||
this.color = params.color || null;
|
||||
|
@ -76,6 +78,15 @@ class InkEditor extends AnnotationEditor {
|
|||
this.#boundCanvasMousedown = this.canvasMousedown.bind(this);
|
||||
}
|
||||
|
||||
static initialize(l10n) {
|
||||
this._l10nPromise = new Map(
|
||||
["editor_ink_canvas_aria_label", "editor_ink_aria_label"].map(str => [
|
||||
str,
|
||||
l10n.get(str),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
static updateDefaultParams(type, value) {
|
||||
switch (type) {
|
||||
case AnnotationEditorParamsType.INK_THICKNESS:
|
||||
|
@ -390,6 +401,10 @@ class InkEditor extends AnnotationEditor {
|
|||
this.#fitToContent();
|
||||
|
||||
this.parent.addInkEditorIfNeeded(/* isCommitting = */ true);
|
||||
|
||||
// When commiting, the position of this editor is changed, hence we must
|
||||
// move it to the right position in the DOM.
|
||||
this.parent.moveDivInDOM(this);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
|
@ -477,6 +492,10 @@ class InkEditor extends AnnotationEditor {
|
|||
this.canvas = document.createElement("canvas");
|
||||
this.canvas.width = this.canvas.height = 0;
|
||||
this.canvas.className = "inkEditorCanvas";
|
||||
|
||||
InkEditor._l10nPromise
|
||||
.get("editor_ink_canvas_aria_label")
|
||||
.then(msg => this.canvas?.setAttribute("aria-label", msg));
|
||||
this.div.append(this.canvas);
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
}
|
||||
|
@ -507,6 +526,11 @@ class InkEditor extends AnnotationEditor {
|
|||
}
|
||||
|
||||
super.render();
|
||||
|
||||
InkEditor._l10nPromise
|
||||
.get("editor_ink_aria_label")
|
||||
.then(msg => this.div?.setAttribute("aria-label", msg));
|
||||
|
||||
const [x, y, w, h] = this.#getInitialBBox();
|
||||
this.setAt(x, y, 0, 0);
|
||||
this.setDims(w, h);
|
||||
|
|
|
@ -426,6 +426,8 @@ class AnnotationEditorUIManager {
|
|||
|
||||
#boundOnPageChanging = this.onPageChanging.bind(this);
|
||||
|
||||
#boundOnTextLayerRendered = this.onTextLayerRendered.bind(this);
|
||||
|
||||
#previousStates = {
|
||||
isEditing: false,
|
||||
isEmpty: true,
|
||||
|
@ -439,11 +441,13 @@ class AnnotationEditorUIManager {
|
|||
this.#eventBus = eventBus;
|
||||
this.#eventBus._on("editingaction", this.#boundOnEditingAction);
|
||||
this.#eventBus._on("pagechanging", this.#boundOnPageChanging);
|
||||
this.#eventBus._on("textlayerrendered", this.#boundOnTextLayerRendered);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.#eventBus._off("editingaction", this.#boundOnEditingAction);
|
||||
this.#eventBus._off("pagechanging", this.#boundOnPageChanging);
|
||||
this.#eventBus._off("textlayerrendered", this.#boundOnTextLayerRendered);
|
||||
for (const layer of this.#allLayers.values()) {
|
||||
layer.destroy();
|
||||
}
|
||||
|
@ -458,6 +462,12 @@ class AnnotationEditorUIManager {
|
|||
this.#currentPageIndex = pageNumber - 1;
|
||||
}
|
||||
|
||||
onTextLayerRendered({ pageNumber }) {
|
||||
const pageIndex = pageNumber - 1;
|
||||
const layer = this.#allLayers.get(pageIndex);
|
||||
layer?.onTextLayerRendered();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an action for a given name.
|
||||
* For example, the user can click on the "Undo" entry in the context menu
|
||||
|
|
20
src/pdf.js
20
src/pdf.js
|
@ -41,15 +41,7 @@ import {
|
|||
VerbosityLevel,
|
||||
} from "./shared/util.js";
|
||||
import {
|
||||
build,
|
||||
getDocument,
|
||||
LoopbackPort,
|
||||
PDFDataRangeTransport,
|
||||
PDFWorker,
|
||||
setPDFNetworkStreamFactory,
|
||||
version,
|
||||
} from "./display/api.js";
|
||||
import {
|
||||
binarySearchFirstItem,
|
||||
getFilenameFromUrl,
|
||||
getPdfFilenameFromUrl,
|
||||
getXfaPageViewport,
|
||||
|
@ -60,6 +52,15 @@ import {
|
|||
PixelsPerInch,
|
||||
RenderingCancelledException,
|
||||
} from "./display/display_utils.js";
|
||||
import {
|
||||
build,
|
||||
getDocument,
|
||||
LoopbackPort,
|
||||
PDFDataRangeTransport,
|
||||
PDFWorker,
|
||||
setPDFNetworkStreamFactory,
|
||||
version,
|
||||
} from "./display/api.js";
|
||||
import { AnnotationEditorLayer } from "./display/editor/annotation_editor_layer.js";
|
||||
import { AnnotationEditorUIManager } from "./display/editor/tools.js";
|
||||
import { AnnotationLayer } from "./display/annotation_layer.js";
|
||||
|
@ -116,6 +117,7 @@ export {
|
|||
AnnotationEditorUIManager,
|
||||
AnnotationLayer,
|
||||
AnnotationMode,
|
||||
binarySearchFirstItem,
|
||||
build,
|
||||
CMapCompressionType,
|
||||
createPromiseCapability,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue