Compare commits

..

No commits in common. "2a68724b5342f8ac8e038f6ed2e1a39b2132d7d8" and "b14f6960710127adccba3a5fca5906c333259da5" have entirely different histories.

34 changed files with 190 additions and 663 deletions

View File

@ -72,10 +72,6 @@
"type": "boolean",
"default": false
},
"enableHighlightFloatingButton": {
"type": "boolean",
"default": false
},
"highlightEditorColors": {
"type": "string",
"default": "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F"

View File

@ -863,17 +863,11 @@ async function parseDefaultPreferences(dir) {
"./" + DEFAULT_PREFERENCES_DIR + dir + "app_options.mjs"
);
const browserPrefs = AppOptions.getAll(
OptionKind.BROWSER,
/* defaultOnly = */ true
);
const browserPrefs = AppOptions.getAll(OptionKind.BROWSER);
if (Object.keys(browserPrefs).length === 0) {
throw new Error("No browser preferences found.");
}
const prefs = AppOptions.getAll(
OptionKind.PREFERENCE,
/* defaultOnly = */ true
);
const prefs = AppOptions.getAll(OptionKind.PREFERENCE);
if (Object.keys(prefs).length === 0) {
throw new Error("No default preferences found.");
}
@ -2228,15 +2222,25 @@ gulp.task(
])
.pipe(gulp.dest(DIST_DIR + "legacy/build/")),
gulp
.src(MINIFIED_DIR + "build/{pdf,pdf.worker,pdf.sandbox}.min.mjs")
.src(MINIFIED_DIR + "build/pdf.min.mjs")
.pipe(gulp.dest(DIST_DIR + "build/")),
gulp
.src(MINIFIED_DIR + "build/pdf.worker.min.mjs")
.pipe(gulp.dest(DIST_DIR + "build/")),
gulp
.src(MINIFIED_DIR + "build/pdf.sandbox.min.mjs")
.pipe(gulp.dest(DIST_DIR + "build/")),
gulp
.src(MINIFIED_DIR + "image_decoders/pdf.image_decoders.min.mjs")
.pipe(gulp.dest(DIST_DIR + "image_decoders/")),
gulp
.src(
MINIFIED_LEGACY_DIR + "build/{pdf,pdf.worker,pdf.sandbox}.min.mjs"
)
.src(MINIFIED_LEGACY_DIR + "build/pdf.min.mjs")
.pipe(gulp.dest(DIST_DIR + "legacy/build/")),
gulp
.src(MINIFIED_LEGACY_DIR + "build/pdf.worker.min.mjs")
.pipe(gulp.dest(DIST_DIR + "legacy/build/")),
gulp
.src(MINIFIED_LEGACY_DIR + "build/pdf.sandbox.min.mjs")
.pipe(gulp.dest(DIST_DIR + "legacy/build/")),
gulp
.src(

View File

@ -318,8 +318,6 @@ pdfjs-editor-stamp-button-label = Add or edit images
pdfjs-editor-highlight-button =
.title = Highlight
pdfjs-editor-highlight-button-label = Highlight
pdfjs-highlight-floating-button =
.title = Highlight
## Remove button for the various kind of editor.

View File

@ -445,10 +445,20 @@ class Catalog {
continue;
}
groupRefs.put(groupRef);
groups.push(this.#readOptionalContentGroup(groupRef));
const group = this.xref.fetch(groupRef);
groups.push({
id: groupRef.toString(),
name:
typeof group.get("Name") === "string"
? stringToPDFString(group.get("Name"))
: null,
intent:
typeof group.get("Intent") === "string"
? stringToPDFString(group.get("Intent"))
: null,
});
}
config = this.#readOptionalContentConfig(defaultConfig, groupRefs);
config = this._readOptionalContentConfig(defaultConfig, groupRefs);
config.groups = groups;
} catch (ex) {
if (ex instanceof MissingDataException) {
@ -459,65 +469,7 @@ class Catalog {
return shadow(this, "optionalContentConfig", config);
}
#readOptionalContentGroup(groupRef) {
const group = this.xref.fetch(groupRef);
const obj = {
id: groupRef.toString(),
name: null,
intent: null,
usage: {
print: null,
view: null,
},
};
const name = group.get("Name");
if (typeof name === "string") {
obj.name = stringToPDFString(name);
}
let intent = group.getArray("Intent");
if (!Array.isArray(intent)) {
intent = [intent];
}
if (intent.every(i => i instanceof Name)) {
obj.intent = intent.map(i => i.name);
}
const usage = group.get("Usage");
if (!(usage instanceof Dict)) {
return obj;
}
const usageObj = obj.usage;
const print = usage.get("Print");
if (print instanceof Dict) {
const printState = print.get("PrintState");
if (printState instanceof Name) {
switch (printState.name) {
case "ON":
case "OFF":
usageObj.print = { printState: printState.name };
}
}
}
const view = usage.get("View");
if (view instanceof Dict) {
const viewState = view.get("ViewState");
if (viewState instanceof Name) {
switch (viewState.name) {
case "ON":
case "OFF":
usageObj.view = { viewState: viewState.name };
}
}
}
return obj;
}
#readOptionalContentConfig(config, contentGroupRefs) {
_readOptionalContentConfig(config, contentGroupRefs) {
function parseOnOff(refs) {
const onParsed = [];
if (Array.isArray(refs)) {

View File

@ -949,26 +949,12 @@ class PDFDocumentProxy {
}
/**
* @typedef {Object} GetOptionalContentConfigParameters
* @property {string} [intent] - Determines the optional content groups that
* are visible by default; valid values are:
* - 'display' (viewable groups).
* - 'print' (printable groups).
* - 'any' (all groups).
* The default value is 'display'.
*/
/**
* @param {GetOptionalContentConfigParameters} [params] - Optional content
* config parameters.
* @returns {Promise<OptionalContentConfig>} A promise that is resolved with
* an {@link OptionalContentConfig} that contains all the optional content
* groups (assuming that the document has any).
*/
getOptionalContentConfig({ intent = "display" } = {}) {
const { renderingIntent } = this._transport.getRenderingIntent(intent);
return this._transport.getOptionalContentConfig(renderingIntent);
getOptionalContentConfig() {
return this._transport.getOptionalContentConfig();
}
/**
@ -1354,14 +1340,17 @@ class PDFPageProxy {
}
/**
* @param {GetAnnotationsParameters} [params] - Annotation parameters.
* @param {GetAnnotationsParameters} params - Annotation parameters.
* @returns {Promise<Array<any>>} A promise that is resolved with an
* {Array} of the annotation objects.
*/
getAnnotations({ intent = "display" } = {}) {
const { renderingIntent } = this._transport.getRenderingIntent(intent);
const intentArgs = this._transport.getRenderingIntent(intent);
return this._transport.getAnnotations(this._pageIndex, renderingIntent);
return this._transport.getAnnotations(
this._pageIndex,
intentArgs.renderingIntent
);
}
/**
@ -1422,20 +1411,20 @@ class PDFPageProxy {
annotationMode,
printAnnotationStorage
);
const { renderingIntent, cacheKey } = intentArgs;
// If there was a pending destroy, cancel it so no cleanup happens during
// this call to render...
this.#pendingCleanup = false;
// ... and ensure that a delayed cleanup is always aborted.
this.#abortDelayedCleanup();
optionalContentConfigPromise ||=
this._transport.getOptionalContentConfig(renderingIntent);
if (!optionalContentConfigPromise) {
optionalContentConfigPromise = this._transport.getOptionalContentConfig();
}
let intentState = this._intentStates.get(cacheKey);
let intentState = this._intentStates.get(intentArgs.cacheKey);
if (!intentState) {
intentState = Object.create(null);
this._intentStates.set(cacheKey, intentState);
this._intentStates.set(intentArgs.cacheKey, intentState);
}
// Ensure that a pending `streamReader` cancel timeout is always aborted.
@ -1444,7 +1433,9 @@ class PDFPageProxy {
intentState.streamReaderCancelTimeout = null;
}
const intentPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);
const intentPrint = !!(
intentArgs.renderingIntent & RenderingIntentFlag.PRINT
);
// If there's no displayReadyCapability yet, then the operatorList
// was never requested before. Make the request and create the promise.
@ -1521,12 +1512,6 @@ class PDFPageProxy {
}
this._stats?.time("Rendering");
if (!(optionalContentConfig.renderingIntent & renderingIntent)) {
throw new Error(
"Must use the same `intent`-argument when calling the `PDFPageProxy.render` " +
"and `PDFDocumentProxy.getOptionalContentConfig` methods."
);
}
internalRenderTask.initializeGraphics({
transparency,
optionalContentConfig,
@ -3009,10 +2994,10 @@ class WorkerTransport {
return this.messageHandler.sendWithPromise("GetOutline", null);
}
getOptionalContentConfig(renderingIntent) {
return this.#cacheSimpleMethod("GetOptionalContentConfig").then(
data => new OptionalContentConfig(data, renderingIntent)
);
getOptionalContentConfig() {
return this.messageHandler
.sendWithPromise("GetOptionalContentConfig", null)
.then(results => new OptionalContentConfig(results));
}
getPermissions() {

View File

@ -66,7 +66,6 @@ class DrawLayer {
#createSVG(box) {
const svg = DrawLayer._svgFactory.create(1, 1, /* skipDimensions = */ true);
this.#parent.append(svg);
svg.setAttribute("aria-hidden", true);
DrawLayer.#setBox(svg, box);
return svg;

View File

@ -93,7 +93,6 @@ class ColorPicker {
button.addEventListener("keydown", this.#boundKeyDown);
const swatch = (this.#buttonSwatch = document.createElement("span"));
swatch.className = "swatch";
swatch.setAttribute("aria-hidden", true);
swatch.style.backgroundColor = this.#defaultColor;
button.append(swatch);
return button;

View File

@ -61,8 +61,6 @@ class HighlightEditor extends AnnotationEditor {
#outlineId = null;
#text = "";
#thickness;
#methodOfCreation = "";
@ -106,7 +104,6 @@ class HighlightEditor extends AnnotationEditor {
this.#opacity = params.opacity || HighlightEditor._defaultOpacity;
this.#boxes = params.boxes || null;
this.#methodOfCreation = params.methodOfCreation || "";
this.#text = params.text || "";
this._isDraggable = false;
if (params.highlightId > -1) {
@ -561,13 +558,6 @@ class HighlightEditor extends AnnotationEditor {
}
const div = super.render();
if (this.#text) {
const mark = document.createElement("mark");
div.append(mark);
mark.append(document.createTextNode(this.#text));
// The text is invisible but it's still visible by a screen reader.
mark.className = "visuallyHidden";
}
if (this.#isFreeHighlight) {
div.classList.add("free");
} else {
@ -575,7 +565,6 @@ class HighlightEditor extends AnnotationEditor {
}
const highlightDiv = (this.#highlightDiv = document.createElement("div"));
div.append(highlightDiv);
highlightDiv.setAttribute("aria-hidden", "true");
highlightDiv.className = "internal";
highlightDiv.style.clipPath = this.#clipPathId;
const [parentWidth, parentHeight] = this.parentDimensions;

View File

@ -31,7 +31,6 @@ class EditorToolbar {
render() {
const editToolbar = (this.#toolbar = document.createElement("div"));
editToolbar.className = "editToolbar";
editToolbar.setAttribute("role", "toolbar");
editToolbar.addEventListener("contextmenu", noContextMenu);
editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown);
@ -135,80 +134,4 @@ class EditorToolbar {
}
}
class HighlightToolbar {
#buttons = null;
#toolbar = null;
#uiManager;
constructor(uiManager) {
this.#uiManager = uiManager;
}
#render() {
const editToolbar = (this.#toolbar = document.createElement("div"));
editToolbar.className = "editToolbar";
editToolbar.setAttribute("role", "toolbar");
editToolbar.addEventListener("contextmenu", noContextMenu);
const buttons = (this.#buttons = document.createElement("div"));
buttons.className = "buttons";
editToolbar.append(buttons);
this.#addHighlightButton();
return editToolbar;
}
#getLastPoint(boxes, isLTR) {
let lastY = 0;
let lastX = 0;
for (const box of boxes) {
const y = box.y + box.height;
if (y < lastY) {
continue;
}
const x = box.x + (isLTR ? box.width : 0);
if (y > lastY) {
lastX = x;
lastY = y;
continue;
}
if (isLTR) {
if (x > lastX) {
lastX = x;
}
} else if (x < lastX) {
lastX = x;
}
}
return [isLTR ? 1 - lastX : lastX, lastY];
}
show(parent, boxes, isLTR) {
const [x, y] = this.#getLastPoint(boxes, isLTR);
const { style } = (this.#toolbar ||= this.#render());
parent.append(this.#toolbar);
style.insetInlineEnd = `${100 * x}%`;
style.top = `calc(${100 * y}% + var(--editor-toolbar-vert-offset))`;
}
hide() {
this.#toolbar.remove();
}
#addHighlightButton() {
const button = document.createElement("button");
button.className = "highlightButton";
button.tabIndex = 0;
button.setAttribute("data-l10n-id", `pdfjs-highlight-floating-button`);
button.addEventListener("contextmenu", noContextMenu);
button.addEventListener("click", () => {
this.#uiManager.highlightSelection("floating_button");
});
this.#buttons.append(button);
}
}
export { EditorToolbar, HighlightToolbar };
export { EditorToolbar };

View File

@ -33,7 +33,6 @@ import {
getRGB,
PixelsPerInch,
} from "../display_utils.js";
import { HighlightToolbar } from "./toolbar.js";
function bindEvents(obj, element, names) {
for (const name of names) {
@ -556,8 +555,6 @@ class AnnotationEditorUIManager {
#editorsToRescale = new Set();
#enableHighlightFloatingButton = false;
#filterFactory = null;
#focusMainContainerTimeoutId = null;
@ -566,8 +563,6 @@ class AnnotationEditorUIManager {
#highlightWhenShiftUp = false;
#highlightToolbar = null;
#idManager = new IdManager();
#isEnabled = false;
@ -776,7 +771,6 @@ class AnnotationEditorUIManager {
pdfDocument,
pageColors,
highlightColors,
enableHighlightFloatingButton,
mlManager
) {
this.#container = container;
@ -788,12 +782,10 @@ class AnnotationEditorUIManager {
this._eventBus._on("scalechanging", this.#boundOnScaleChanging);
this._eventBus._on("rotationchanging", this.#boundOnRotationChanging);
this.#addSelectionListener();
this.#addKeyboardManager();
this.#annotationStorage = pdfDocument.annotationStorage;
this.#filterFactory = pdfDocument.filterFactory;
this.#pageColors = pageColors;
this.#highlightColors = highlightColors || null;
this.#enableHighlightFloatingButton = enableHighlightFloatingButton;
this.#mlManager = mlManager || null;
this.viewParameters = {
realScale: PixelsPerInch.PDF_TO_CSS_UNITS,
@ -829,8 +821,6 @@ class AnnotationEditorUIManager {
this.#selectedEditors.clear();
this.#commandManager.destroy();
this.#altTextManager?.destroy();
this.#highlightToolbar?.hide();
this.#highlightToolbar = null;
if (this.#focusMainContainerTimeoutId) {
clearTimeout(this.#focusMainContainerTimeoutId);
this.#focusMainContainerTimeoutId = null;
@ -956,32 +946,24 @@ class AnnotationEditorUIManager {
this.viewParameters.rotation = pagesRotation;
}
#getAnchorElementForSelection({ anchorNode }) {
return anchorNode.nodeType === Node.TEXT_NODE
? anchorNode.parentElement
: anchorNode;
}
highlightSelection(methodOfCreation = "") {
const selection = document.getSelection();
if (!selection || selection.isCollapsed) {
return;
}
const { anchorNode, anchorOffset, focusNode, focusOffset } = selection;
const text = selection.toString();
const anchorElement = this.#getAnchorElementForSelection(selection);
const anchorElement =
anchorNode.nodeType === Node.TEXT_NODE
? anchorNode.parentElement
: anchorNode;
const textLayer = anchorElement.closest(".textLayer");
const boxes = this.getSelectionBoxes(textLayer);
if (!boxes) {
return;
}
selection.empty();
if (this.#mode === AnnotationEditorType.NONE) {
this._eventBus.dispatch("showannotationeditorui", {
source: this,
mode: AnnotationEditorType.HIGHLIGHT,
});
this.showAllEditors("highlight", true, /* updateButton = */ true);
}
for (const layer of this.#allLayers.values()) {
if (layer.hasTextLayer(textLayer)) {
@ -992,28 +974,12 @@ class AnnotationEditorUIManager {
anchorOffset,
focusNode,
focusOffset,
text,
});
break;
}
}
}
#displayHighlightToolbar() {
const selection = document.getSelection();
if (!selection || selection.isCollapsed) {
return;
}
const anchorElement = this.#getAnchorElementForSelection(selection);
const textLayer = anchorElement.closest(".textLayer");
const boxes = this.getSelectionBoxes(textLayer);
if (!boxes) {
return;
}
this.#highlightToolbar ||= new HighlightToolbar(this);
this.#highlightToolbar.show(textLayer, boxes, this.direction === "ltr");
}
/**
* Add an editor in the annotation storage.
* @param {AnnotationEditor} editor
@ -1032,7 +998,6 @@ class AnnotationEditorUIManager {
const selection = document.getSelection();
if (!selection || selection.isCollapsed) {
if (this.#selectedTextNode) {
this.#highlightToolbar?.hide();
this.#selectedTextNode = null;
this.#dispatchUpdateStates({
hasSelectedText: false,
@ -1045,11 +1010,12 @@ class AnnotationEditorUIManager {
return;
}
const anchorElement = this.#getAnchorElementForSelection(selection);
const textLayer = anchorElement.closest(".textLayer");
if (!textLayer) {
const anchorElement =
anchorNode.nodeType === Node.TEXT_NODE
? anchorNode.parentElement
: anchorNode;
if (!anchorElement.closest(".textLayer")) {
if (this.#selectedTextNode) {
this.#highlightToolbar?.hide();
this.#selectedTextNode = null;
this.#dispatchUpdateStates({
hasSelectedText: false,
@ -1057,22 +1023,16 @@ class AnnotationEditorUIManager {
}
return;
}
this.#highlightToolbar?.hide();
this.#selectedTextNode = anchorNode;
this.#dispatchUpdateStates({
hasSelectedText: true,
});
if (
this.#mode !== AnnotationEditorType.HIGHLIGHT &&
this.#mode !== AnnotationEditorType.NONE
) {
if (this.#mode !== AnnotationEditorType.HIGHLIGHT) {
return;
}
if (this.#mode === AnnotationEditorType.HIGHLIGHT) {
this.showAllEditors("highlight", true, /* updateButton = */ true);
}
this.#highlightWhenShiftUp = this.isShiftKeyDown;
if (!this.isShiftKeyDown) {
@ -1084,7 +1044,7 @@ class AnnotationEditorUIManager {
window.removeEventListener("pointerup", pointerup);
window.removeEventListener("blur", pointerup);
if (e.type === "pointerup") {
this.#onSelectEnd("main_toolbar");
this.highlightSelection("main_toolbar");
}
};
window.addEventListener("pointerup", pointerup);
@ -1092,14 +1052,6 @@ class AnnotationEditorUIManager {
}
}
#onSelectEnd(methodOfCreation = "") {
if (this.#mode === AnnotationEditorType.HIGHLIGHT) {
this.highlightSelection(methodOfCreation);
} else if (this.#enableHighlightFloatingButton) {
this.#displayHighlightToolbar();
}
}
#addSelectionListener() {
document.addEventListener("selectionchange", this.#boundSelectionChange);
}
@ -1122,7 +1074,7 @@ class AnnotationEditorUIManager {
this.isShiftKeyDown = false;
if (this.#highlightWhenShiftUp) {
this.#highlightWhenShiftUp = false;
this.#onSelectEnd("main_toolbar");
this.highlightSelection("main_toolbar");
}
if (!this.hasSelection) {
return;
@ -1298,10 +1250,7 @@ class AnnotationEditorUIManager {
if (!this.isShiftKeyDown && event.key === "Shift") {
this.isShiftKeyDown = true;
}
if (
this.#mode !== AnnotationEditorType.NONE &&
!this.isEditorHandlingKeyboard
) {
if (!this.isEditorHandlingKeyboard) {
AnnotationEditorUIManager._keyboardManager.exec(this, event);
}
}
@ -1315,7 +1264,7 @@ class AnnotationEditorUIManager {
this.isShiftKeyDown = false;
if (this.#highlightWhenShiftUp) {
this.#highlightWhenShiftUp = false;
this.#onSelectEnd("main_toolbar");
this.highlightSelection("main_toolbar");
}
}
}
@ -1384,6 +1333,7 @@ class AnnotationEditorUIManager {
setEditingState(isEditing) {
if (isEditing) {
this.#addFocusManager();
this.#addKeyboardManager();
this.#addCopyPasteListeners();
this.#dispatchUpdateStates({
isEditing: this.#mode !== AnnotationEditorType.NONE,
@ -1394,6 +1344,7 @@ class AnnotationEditorUIManager {
});
} else {
this.#removeFocusManager();
this.#removeKeyboardManager();
this.#removeCopyPasteListeners();
this.#dispatchUpdateStates({
isEditing: false,

View File

@ -424,22 +424,21 @@ class PDFNodeStreamFsFullReader extends BaseFullReader {
path = path.replace(/^\//, "");
}
fs.promises.lstat(path).then(
stat => {
// Setting right content length.
this._contentLength = stat.size;
this._setReadableStream(fs.createReadStream(path));
this._headersCapability.resolve();
},
error => {
fs.lstat(path, (error, stat) => {
if (error) {
if (error.code === "ENOENT") {
error = new MissingPDFException(`Missing PDF "${path}".`);
}
this._storedError = error;
this._headersCapability.reject(error);
return;
}
);
// Setting right content length.
this._contentLength = stat.size;
this._setReadableStream(fs.createReadStream(path));
this._headersCapability.resolve();
});
}
}

View File

@ -71,7 +71,15 @@ if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("SKIP_BABEL")) {
}
const fetchData = function (url) {
return fs.promises.readFile(url).then(data => new Uint8Array(data));
return new Promise((resolve, reject) => {
fs.readFile(url, (error, data) => {
if (error || !data) {
reject(new Error(error));
return;
}
resolve(new Uint8Array(data));
});
});
};
class NodeFilterFactory extends BaseFilterFactory {}

View File

@ -13,63 +13,33 @@
* limitations under the License.
*/
import {
info,
objectFromMap,
RenderingIntentFlag,
unreachable,
warn,
} from "../shared/util.js";
import { info, objectFromMap, unreachable, warn } from "../shared/util.js";
import { MurmurHash3_64 } from "../shared/murmurhash3.js";
const INTERNAL = Symbol("INTERNAL");
class OptionalContentGroup {
#isDisplay = false;
#isPrint = false;
#userSet = false;
#visible = true;
constructor(renderingIntent, { name, intent, usage }) {
this.#isDisplay = !!(renderingIntent & RenderingIntentFlag.DISPLAY);
this.#isPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);
constructor(name, intent) {
this.name = name;
this.intent = intent;
this.usage = usage;
}
/**
* @type {boolean}
*/
get visible() {
if (this.#userSet) {
return this.#visible;
}
if (!this.#visible) {
return false;
}
const { print, view } = this.usage;
if (this.#isDisplay) {
return view?.viewState !== "OFF";
} else if (this.#isPrint) {
return print?.printState !== "OFF";
}
return true;
}
/**
* @ignore
*/
_setVisible(internal, visible, userSet = false) {
_setVisible(internal, visible) {
if (internal !== INTERNAL) {
unreachable("Internal method `_setVisible` called.");
}
this.#userSet = userSet;
this.#visible = visible;
}
}
@ -83,9 +53,7 @@ class OptionalContentConfig {
#order = null;
constructor(data, renderingIntent = RenderingIntentFlag.DISPLAY) {
this.renderingIntent = renderingIntent;
constructor(data) {
this.name = null;
this.creator = null;
@ -98,7 +66,7 @@ class OptionalContentConfig {
for (const group of data.groups) {
this.#groups.set(
group.id,
new OptionalContentGroup(renderingIntent, group)
new OptionalContentGroup(group.name, group.intent)
);
}
@ -230,44 +198,11 @@ class OptionalContentConfig {
}
setVisibility(id, visible = true) {
const group = this.#groups.get(id);
if (!group) {
if (!this.#groups.has(id)) {
warn(`Optional content group not found: ${id}`);
return;
}
group._setVisible(INTERNAL, !!visible, /* userSet = */ true);
this.#cachedGetHash = null;
}
setOCGState({ state, preserveRB }) {
let operator;
for (const elem of state) {
switch (elem) {
case "ON":
case "OFF":
case "Toggle":
operator = elem;
continue;
}
const group = this.#groups.get(elem);
if (!group) {
continue;
}
switch (operator) {
case "ON":
group._setVisible(INTERNAL, true);
break;
case "OFF":
group._setVisible(INTERNAL, false);
break;
case "Toggle":
group._setVisible(INTERNAL, !group.visible);
break;
}
}
this.#groups.get(id)._setVisible(INTERNAL, !!visible);
this.#cachedGetHash = null;
}

View File

@ -684,9 +684,7 @@ class Driver {
}
task.pdfDoc = doc;
task.optionalContentConfigPromise = doc.getOptionalContentConfig({
intent: task.print ? "print" : "display",
});
task.optionalContentConfigPromise = doc.getOptionalContentConfig();
if (task.optionalContent) {
const entries = Object.entries(task.optionalContent),

View File

@ -35,7 +35,6 @@ async function runTests(results) {
"scripting_spec.mjs",
"stamp_editor_spec.mjs",
"text_field_spec.mjs",
"viewer_spec.mjs",
],
});

View File

@ -46,11 +46,8 @@ const getXY = (page, selector) =>
return `${bbox.x}::${bbox.y}`;
}, selector);
const getSpanRectFromText = async (page, pageNumber, text) => {
await page.waitForSelector(
`.page[data-page-number="${pageNumber}"] > .textLayer .endOfContent`
);
return page.evaluate(
const getSpanRectFromText = (page, pageNumber, text) =>
page.evaluate(
(number, content) => {
for (const el of document.querySelectorAll(
`.page[data-page-number="${number}"] > .textLayer > span`
@ -65,7 +62,6 @@ const getSpanRectFromText = async (page, pageNumber, text) => {
pageNumber,
text
);
};
describe("Highlight Editor", () => {
describe("Editor must be removed without exception", () => {
@ -1514,46 +1510,4 @@ describe("Highlight Editor", () => {
);
});
});
describe("Highlight from floating highlight button", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait(
"tracemonkey.pdf",
".annotationEditorLayer",
null,
null,
{ highlightEditorColors: "red=#AB0000" }
);
});
afterAll(async () => {
await closePages(pages);
});
it("must check that clicking on the highlight floating button triggers an highlight", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
const rect = await getSpanRectFromText(page, 1, "Abstract");
const x = rect.x + rect.width / 2;
const y = rect.y + rect.height / 2;
await page.mouse.click(x, y, { count: 2, delay: 100 });
await page.waitForSelector(".textLayer .highlightButton");
await page.click(".textLayer .highlightButton");
await page.waitForSelector(getEditorSelector(0));
const usedColor = await page.evaluate(() => {
const highlight = document.querySelector(
`.page[data-page-number = "1"] .canvasWrapper > svg.highlight`
);
return highlight.getAttribute("fill");
});
expect(usedColor).withContext(`In ${browserName}`).toEqual("#AB0000");
})
);
});
});
});

View File

@ -1,58 +0,0 @@
/* Copyright 2024 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { closePages, loadAndWait } from "./test_utils.mjs";
describe("PDF viewer", () => {
describe("Zoom with the mouse wheel", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("empty.pdf", ".textLayer .endOfContent", 1000);
});
afterAll(async () => {
await closePages(pages);
});
it("must check that we can zoom with the mouse wheel and pressed control key", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
if (browserName === "firefox") {
// Skip this test for Firefox, as it's not working correctly.
// See https://github.com/puppeteer/puppeteer/issues/12093.
// TODO: Remove this check once the issue is resolved.
return;
}
await page.keyboard.down("Control");
let zoom = 10;
const zoomGetter = () =>
page.evaluate(
() => window.PDFViewerApplication.pdfViewer.currentScale
);
while (zoom > 0.1) {
await page.mouse.wheel({ deltaY: 100 });
zoom = await zoomGetter();
}
while (zoom < 10) {
await page.mouse.wheel({ deltaY: -100 });
zoom = await zoomGetter();
}
await page.keyboard.up("Control");
})
);
});
});
});

View File

@ -1 +0,0 @@
https://bugzilla.mozilla.org/attachment.cgi?id=9327375

View File

@ -4016,23 +4016,6 @@
"lastPage": 5,
"type": "eq"
},
{
"id": "bug1826783-display",
"file": "pdfs/bug1826783.pdf",
"md5": "93e706efee15dd7b32d32d66f15a3ea2",
"rounds": 1,
"link": true,
"type": "eq"
},
{
"id": "bug1826783-print",
"file": "pdfs/bug1826783.pdf",
"md5": "93e706efee15dd7b32d32d66f15a3ea2",
"rounds": 1,
"link": true,
"type": "eq",
"print": true
},
{
"id": "issue8586",
"file": "pdfs/issue8586.pdf",

View File

@ -1,41 +0,0 @@
/* Copyright 2024 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AppOptions, OptionKind } from "../../web/app_options.js";
import { objectSize } from "../../src/shared/util.js";
describe("AppOptions", function () {
it("checks that getAll returns data, for every OptionKind", function () {
const KIND_NAMES = ["BROWSER", "VIEWER", "API", "WORKER", "PREFERENCE"];
for (const name of KIND_NAMES) {
const kind = OptionKind[name];
expect(typeof kind).toEqual("number");
const options = AppOptions.getAll(kind);
expect(objectSize(options)).toBeGreaterThan(0);
}
});
it('checks that the number of "PREFERENCE" options does *not* exceed the maximum in mozilla-central', function () {
// If the following constant is updated then you *MUST* make the same change
// in mozilla-central as well to ensure that preference-fetching works; see
// https://searchfox.org/mozilla-central/source/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs
const MAX_NUMBER_OF_PREFS = 50;
const options = AppOptions.getAll(OptionKind.PREFERENCE);
expect(objectSize(options)).toBeLessThanOrEqual(MAX_NUMBER_OF_PREFS);
});
});

View File

@ -7,7 +7,6 @@
"annotation_spec.js",
"annotation_storage_spec.js",
"api_spec.js",
"app_options_spec.js",
"bidi_spec.js",
"cff_parser_spec.js",
"cmap_spec.js",

View File

@ -50,7 +50,6 @@ async function initializePDFJS(callback) {
"pdfjs-test/unit/annotation_spec.js",
"pdfjs-test/unit/annotation_storage_spec.js",
"pdfjs-test/unit/api_spec.js",
"pdfjs-test/unit/app_options_spec.js",
"pdfjs-test/unit/bidi_spec.js",
"pdfjs-test/unit/cff_parser_spec.js",
"pdfjs-test/unit/cmap_spec.js",

View File

@ -48,19 +48,6 @@
pointer;
}
/* The following class is used to hide an element but keep it available to
* for screen readers. */
.visuallyHidden {
position: absolute;
border: 0;
margin: 0;
padding: 0;
width: 0;
height: 0;
overflow: hidden;
white-space: nowrap;
}
.textLayer.highlighting {
cursor: var(--editorFreeHighlight-editing-cursor);
@ -195,12 +182,10 @@
}
.annotationEditorLayer
:is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),
.textLayer {
:is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor) {
.editToolbar {
--editor-toolbar-delete-image: url(images/editor-toolbar-delete.svg);
--editor-toolbar-bg-color: #f0f0f4;
--editor-toolbar-highlight-image: url(images/toolbarButton-editorHighlight.svg);
--editor-toolbar-fg-color: #2e2e56;
--editor-toolbar-border-color: #8f8f9d;
--editor-toolbar-hover-border-color: var(--editor-toolbar-border-color);
@ -286,25 +271,6 @@
margin-inline: 2px;
}
.highlightButton {
width: var(--editor-toolbar-height);
&::before {
content: "";
mask-image: var(--editor-toolbar-highlight-image);
mask-repeat: no-repeat;
mask-position: center;
display: inline-block;
background-color: var(--editor-toolbar-fg-color);
width: 100%;
height: 100%;
}
&:hover::before {
background-color: var(--editor-toolbar-hover-fg-color);
}
}
.delete {
width: var(--editor-toolbar-height);

View File

@ -424,9 +424,6 @@ const PDFViewerApplication = {
annotationMode: AppOptions.get("annotationMode"),
annotationEditorMode,
annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"),
enableHighlightFloatingButton: AppOptions.get(
"enableHighlightFloatingButton"
),
imageResourcesPath: AppOptions.get("imageResourcesPath"),
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
@ -1799,6 +1796,7 @@ const PDFViewerApplication = {
pagesOverview: this.pdfViewer.getPagesOverview(),
printContainer: this.appConfig.printContainer,
printResolution: AppOptions.get("printResolution"),
optionalContentConfigPromise: this.pdfViewer.optionalContentConfigPromise,
printAnnotationStoragePromise: this._printAnnotationStoragePromise,
});
this.forceRendering();
@ -2024,9 +2022,8 @@ const PDFViewerApplication = {
});
const scroll = (_boundEvents.mainContainerScroll = () => {
if (
this._isCtrlKeyDown ||
(this._lastScrollTop === mainContainer.scrollTop &&
this._lastScrollLeft === mainContainer.scrollLeft)
this._lastScrollTop === mainContainer.scrollTop &&
this._lastScrollLeft === mainContainer.scrollLeft
) {
return;
}

View File

@ -143,14 +143,6 @@ const defaultOptions = {
value: typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING"),
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
},
enableHighlightFloatingButton: {
// We'll probably want to make some experiments before enabling this
// in Firefox release, but it has to be temporary.
// TODO: remove it when unnecessary.
/** @type {boolean} */
value: typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING"),
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
},
enableML: {
/** @type {boolean} */
value: false,
@ -417,64 +409,57 @@ if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
const userOptions = Object.create(null);
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING || LIB")) {
// Ensure that the `defaultOptions` are correctly specified.
for (const name in defaultOptions) {
const { value, kind } = defaultOptions[name];
if (kind & OptionKind.PREFERENCE) {
if (kind === OptionKind.PREFERENCE) {
throw new Error(`Cannot use only "PREFERENCE" kind: ${name}`);
}
if (kind & OptionKind.BROWSER) {
throw new Error(`Cannot mix "PREFERENCE" and "BROWSER" kind: ${name}`);
}
if (compatibilityParams[name] !== undefined) {
throw new Error(
`Should not have compatibility-value for "PREFERENCE" kind: ${name}`
);
}
// Only "simple" preference-values are allowed.
if (
typeof value !== "boolean" &&
typeof value !== "string" &&
!Number.isInteger(value)
) {
throw new Error(`Invalid value for "PREFERENCE" kind: ${name}`);
}
}
}
}
class AppOptions {
constructor() {
throw new Error("Cannot initialize AppOptions.");
}
static getCompat(name) {
return compatibilityParams[name] ?? undefined;
}
static get(name) {
return (
userOptions[name] ??
compatibilityParams[name] ??
defaultOptions[name]?.value ??
undefined
);
const userOption = userOptions[name];
if (userOption !== undefined) {
return userOption;
}
const defaultOption = defaultOptions[name];
if (defaultOption !== undefined) {
return compatibilityParams[name] ?? defaultOption.value;
}
return undefined;
}
static getAll(kind = null, defaultOnly = false) {
static getAll(kind = null) {
const options = Object.create(null);
for (const name in defaultOptions) {
const defaultOption = defaultOptions[name];
if (kind && !(kind & defaultOption.kind)) {
if (kind) {
if (!(kind & defaultOption.kind)) {
continue;
}
options[name] = defaultOnly
? defaultOption.value
: userOptions[name] ?? compatibilityParams[name] ?? defaultOption.value;
if (
(typeof PDFJSDev === "undefined" || PDFJSDev.test("LIB")) &&
kind === OptionKind.PREFERENCE
) {
if (defaultOption.kind & OptionKind.BROWSER) {
throw new Error(`Invalid kind for preference: ${name}`);
}
const value = defaultOption.value,
valueType = typeof value;
if (
valueType === "boolean" ||
valueType === "string" ||
(valueType === "number" && Number.isInteger(value))
) {
options[name] = value;
continue;
}
throw new Error(`Invalid type for preference: ${name}`);
}
}
const userOption = userOptions[name];
options[name] =
userOption !== undefined
? userOption
: compatibilityParams[name] ?? defaultOption.value;
}
return options;
}
@ -508,4 +493,4 @@ class AppOptions {
}
}
export { AppOptions, OptionKind };
export { AppOptions, compatibilityParams, OptionKind };

View File

@ -119,15 +119,15 @@ class FirefoxPrintService {
pagesOverview,
printContainer,
printResolution,
optionalContentConfigPromise = null,
printAnnotationStoragePromise = null,
}) {
this.pdfDocument = pdfDocument;
this.pagesOverview = pagesOverview;
this.printContainer = printContainer;
this._printResolution = printResolution || 150;
this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
intent: "print",
});
this._optionalContentConfigPromise =
optionalContentConfigPromise || pdfDocument.getOptionalContentConfig();
this._printAnnotationStoragePromise =
printAnnotationStoragePromise || Promise.resolve();
}

View File

@ -258,8 +258,14 @@ if (PDFJSDev.test("GECKOVIEW")) {
const hasWillPrint =
pdfViewer.enableScripting &&
!!(await pdfDocument.getJSActions())?.WillPrint;
const hasUnchangedOptionalContent = (
await pdfViewer.optionalContentConfigPromise
).hasInitialVisibility;
result = hasUnchangedAnnotations && !hasWillPrint;
result =
hasUnchangedAnnotations &&
!hasWillPrint &&
hasUnchangedOptionalContent;
} catch {
console.warn("Unable to check if the document can be downloaded.");
}

View File

@ -182,7 +182,7 @@ class PDFLayerViewer extends BaseTreeViewer {
}
const pdfDocument = this._pdfDocument;
const optionalContentConfig = await (promise ||
pdfDocument.getOptionalContentConfig({ intent: "display" }));
pdfDocument.getOptionalContentConfig());
if (pdfDocument !== this._pdfDocument) {
return; // The document was closed while the optional content resolved.

View File

@ -517,7 +517,31 @@ class PDFLinkService {
if (pdfDocument !== this.pdfDocument) {
return; // The document was closed while the optional content resolved.
}
optionalContentConfig.setOCGState(action);
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

View File

@ -41,7 +41,7 @@ import {
} from "./ui_utils.js";
import { AnnotationEditorLayerBuilder } from "./annotation_editor_layer_builder.js";
import { AnnotationLayerBuilder } from "./annotation_layer_builder.js";
import { AppOptions } from "./app_options.js";
import { compatibilityParams } from "./app_options.js";
import { DrawLayerBuilder } from "./draw_layer_builder.js";
import { GenericL10n } from "web-null_l10n";
import { SimpleLinkService } from "./pdf_link_service.js";
@ -83,6 +83,8 @@ import { XfaLayerBuilder } from "./xfa_layer_builder.js";
* the necessary layer-properties.
*/
const MAX_CANVAS_PIXELS = compatibilityParams.maxCanvasPixels || 16777216;
const DEFAULT_LAYER_PROPERTIES =
typeof PDFJSDev === "undefined" || !PDFJSDev.test("COMPONENTS")
? null
@ -150,9 +152,7 @@ class PDFPageView {
this.#annotationMode =
options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
this.imageResourcesPath = options.imageResourcesPath || "";
this.maxCanvasPixels =
options.maxCanvasPixels ??
(AppOptions.getCompat("maxCanvasPixels") || 16777216);
this.maxCanvasPixels = options.maxCanvasPixels ?? MAX_CANVAS_PIXELS;
this.pageColors = options.pageColors || null;
this.eventBus = options.eventBus;

View File

@ -13,12 +13,7 @@
* limitations under the License.
*/
import {
AnnotationMode,
PixelsPerInch,
RenderingCancelledException,
shadow,
} from "pdfjs-lib";
import { AnnotationMode, PixelsPerInch, shadow } from "pdfjs-lib";
import { getXfaHtmlForPrinting } from "./print_utils.js";
let activeService = null;
@ -63,14 +58,7 @@ function renderPage(
optionalContentConfigPromise,
printAnnotationStorage,
};
const renderTask = pdfPage.render(renderContext);
return renderTask.promise.catch(reason => {
if (!(reason instanceof RenderingCancelledException)) {
console.error(reason);
}
throw reason;
});
return pdfPage.render(renderContext).promise;
});
}
@ -80,15 +68,15 @@ class PDFPrintService {
pagesOverview,
printContainer,
printResolution,
optionalContentConfigPromise = null,
printAnnotationStoragePromise = null,
}) {
this.pdfDocument = pdfDocument;
this.pagesOverview = pagesOverview;
this.printContainer = printContainer;
this._printResolution = printResolution || 150;
this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
intent: "print",
});
this._optionalContentConfigPromise =
optionalContentConfigPromise || pdfDocument.getOptionalContentConfig();
this._printAnnotationStoragePromise =
printAnnotationStoragePromise || Promise.resolve();
this.currentPage = -1;

View File

@ -189,9 +189,7 @@ class PDFThumbnailViewer {
return;
}
const firstPagePromise = pdfDocument.getPage(1);
const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
intent: "display",
});
const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig();
firstPagePromise
.then(firstPdfPage => {

View File

@ -214,8 +214,6 @@ class PDFViewer {
#copyCallbackBound = null;
#enableHighlightFloatingButton = false;
#enablePermissions = false;
#mlManager = null;
@ -284,8 +282,6 @@ class PDFViewer {
options.annotationEditorMode ?? AnnotationEditorType.NONE;
this.#annotationEditorHighlightColors =
options.annotationEditorHighlightColors || null;
this.#enableHighlightFloatingButton =
options.enableHighlightFloatingButton === true;
this.imageResourcesPath = options.imageResourcesPath || "";
this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
@ -785,9 +781,7 @@ class PDFViewer {
const pagesCount = pdfDocument.numPages;
const firstPagePromise = pdfDocument.getPage(1);
// Rendering (potentially) depends on this, hence fetching it immediately.
const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
intent: "display",
});
const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig();
const permissionsPromise = this.#enablePermissions
? pdfDocument.getPermissions()
: Promise.resolve();
@ -867,7 +861,6 @@ class PDFViewer {
pdfDocument,
this.pageColors,
this.#annotationEditorHighlightColors,
this.#enableHighlightFloatingButton,
this.#mlManager
);
this.eventBus.dispatch("annotationeditoruimanager", {
@ -1829,7 +1822,7 @@ class PDFViewer {
console.error("optionalContentConfigPromise: Not initialized yet.");
// Prevent issues if the getter is accessed *before* the `onePageRendered`
// promise has resolved; won't (normally) happen in the default viewer.
return this.pdfDocument.getOptionalContentConfig({ intent: "display" });
return this.pdfDocument.getOptionalContentConfig();
}
return this._optionalContentConfigPromise;
}

View File

@ -23,7 +23,7 @@ import { AppOptions, OptionKind } from "./app_options.js";
class BasePreferences {
#defaults = Object.freeze(
typeof PDFJSDev === "undefined"
? AppOptions.getAll(OptionKind.PREFERENCE, /* defaultOnly = */ true)
? AppOptions.getAll(OptionKind.PREFERENCE)
: PDFJSDev.eval("DEFAULT_PREFERENCES")
);
@ -48,7 +48,7 @@ class BasePreferences {
({ browserPrefs, prefs }) => {
const BROWSER_PREFS =
typeof PDFJSDev === "undefined"
? AppOptions.getAll(OptionKind.BROWSER, /* defaultOnly = */ true)
? AppOptions.getAll(OptionKind.BROWSER)
: PDFJSDev.eval("BROWSER_PREFERENCES");
const options = Object.create(null);