[editor] Add some UI elements in order to set font size & color, and ink thickness & color
This commit is contained in:
parent
4e025e1f08
commit
1a3ef2a0aa
@ -259,3 +259,9 @@ editor_ink.title=Add Ink Annotation
|
|||||||
editor_ink_label=Ink Annotation
|
editor_ink_label=Ink Annotation
|
||||||
|
|
||||||
freetext_default_content=Enter some text…
|
freetext_default_content=Enter some text…
|
||||||
|
|
||||||
|
# Editor Parameters
|
||||||
|
editor_free_text_font_color=Font Color
|
||||||
|
editor_free_text_font_size=Font Size
|
||||||
|
editor_ink_line_color=Line Color
|
||||||
|
editor_ink_line_thickness=Line Thickness
|
||||||
|
@ -3767,7 +3767,7 @@ class InkAnnotation extends MarkupAnnotation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const appearanceBuffer = [
|
const appearanceBuffer = [
|
||||||
`${thickness} w`,
|
`${thickness} w 1 J 1 j`,
|
||||||
`${getPdfColor(color, /* isFill */ false)}`,
|
`${getPdfColor(color, /* isFill */ false)}`,
|
||||||
];
|
];
|
||||||
const buffer = [];
|
const buffer = [];
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
Util,
|
Util,
|
||||||
warn,
|
warn,
|
||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
|
import { getRGB, PixelsPerInch } from "./display_utils.js";
|
||||||
import {
|
import {
|
||||||
getShadingPattern,
|
getShadingPattern,
|
||||||
PathType,
|
PathType,
|
||||||
@ -33,7 +34,6 @@ import {
|
|||||||
} from "./pattern_helper.js";
|
} from "./pattern_helper.js";
|
||||||
import { applyMaskImageData } from "../shared/image_utils.js";
|
import { applyMaskImageData } from "../shared/image_utils.js";
|
||||||
import { isNodeJS } from "../shared/is_node.js";
|
import { isNodeJS } from "../shared/is_node.js";
|
||||||
import { PixelsPerInch } from "./display_utils.js";
|
|
||||||
|
|
||||||
// <canvas> contexts store most of the state we need natively.
|
// <canvas> contexts store most of the state we need natively.
|
||||||
// However, PDF needs a bit more state, which we store here.
|
// However, PDF needs a bit more state, which we store here.
|
||||||
@ -1326,10 +1326,7 @@ class CanvasGraphics {
|
|||||||
// Then for every color in the pdf, if its rounded luminance is the
|
// Then for every color in the pdf, if its rounded luminance is the
|
||||||
// same as the background one then it's replaced by the new
|
// same as the background one then it's replaced by the new
|
||||||
// background color else by the foreground one.
|
// background color else by the foreground one.
|
||||||
const cB = parseInt(defaultBg.slice(1), 16);
|
const [rB, gB, bB] = getRGB(defaultBg);
|
||||||
const rB = (cB && 0xff0000) >> 16;
|
|
||||||
const gB = (cB && 0x00ff00) >> 8;
|
|
||||||
const bB = cB && 0x0000ff;
|
|
||||||
const newComp = x => {
|
const newComp = x => {
|
||||||
x /= 255;
|
x /= 255;
|
||||||
return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
||||||
|
@ -567,6 +567,28 @@ function getXfaPageViewport(xfaPage, { scale = 1, rotation = 0 }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRGB(color) {
|
||||||
|
if (color.startsWith("#")) {
|
||||||
|
const colorRGB = parseInt(color.slice(1), 16);
|
||||||
|
return [
|
||||||
|
(colorRGB & 0xff0000) >> 16,
|
||||||
|
(colorRGB & 0x00ff00) >> 8,
|
||||||
|
colorRGB & 0x0000ff,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color.startsWith("rgb(")) {
|
||||||
|
// getComputedStyle(...).color returns a `rgb(R, G, B)` color.
|
||||||
|
return color
|
||||||
|
.slice(/* "rgb(".length */ 4, -1) // Strip out "rgb(" and ")".
|
||||||
|
.split(",")
|
||||||
|
.map(x => parseInt(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
warn(`Not a valid color format: "${color}"`);
|
||||||
|
return [0, 0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
deprecated,
|
deprecated,
|
||||||
DOMCanvasFactory,
|
DOMCanvasFactory,
|
||||||
@ -575,6 +597,7 @@ export {
|
|||||||
DOMSVGFactory,
|
DOMSVGFactory,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
|
getRGB,
|
||||||
getXfaPageViewport,
|
getXfaPageViewport,
|
||||||
isDataScheme,
|
isDataScheme,
|
||||||
isPdfFile,
|
isPdfFile,
|
||||||
|
@ -78,6 +78,8 @@ class AnnotationEditorLayer {
|
|||||||
if (!AnnotationEditorLayer._initialized) {
|
if (!AnnotationEditorLayer._initialized) {
|
||||||
AnnotationEditorLayer._initialized = true;
|
AnnotationEditorLayer._initialized = true;
|
||||||
FreeTextEditor.initialize(options.l10n);
|
FreeTextEditor.initialize(options.l10n);
|
||||||
|
|
||||||
|
options.uiManager.registerEditorTypes([FreeTextEditor, InkEditor]);
|
||||||
}
|
}
|
||||||
this.#uiManager = options.uiManager;
|
this.#uiManager = options.uiManager;
|
||||||
this.annotationStorage = options.annotationStorage;
|
this.annotationStorage = options.annotationStorage;
|
||||||
@ -98,14 +100,22 @@ class AnnotationEditorLayer {
|
|||||||
* @param {number} mode
|
* @param {number} mode
|
||||||
*/
|
*/
|
||||||
updateMode(mode) {
|
updateMode(mode) {
|
||||||
if (mode === AnnotationEditorType.INK) {
|
switch (mode) {
|
||||||
// We want to have the ink editor covering all of the page without having
|
case AnnotationEditorType.INK:
|
||||||
// to click to create it: it must be here when we start to draw.
|
// We want to have the ink editor covering all of the page without
|
||||||
|
// having to click to create it: it must be here when we start to draw.
|
||||||
this.div.addEventListener("mouseover", this.#boundMouseover);
|
this.div.addEventListener("mouseover", this.#boundMouseover);
|
||||||
this.div.removeEventListener("click", this.#boundClick);
|
this.div.removeEventListener("click", this.#boundClick);
|
||||||
} else {
|
break;
|
||||||
|
case AnnotationEditorType.FREETEXT:
|
||||||
this.div.removeEventListener("mouseover", this.#boundMouseover);
|
this.div.removeEventListener("mouseover", this.#boundMouseover);
|
||||||
|
this.div.addEventListener("click", this.#boundClick);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.div.removeEventListener("mouseover", this.#boundMouseover);
|
||||||
|
this.div.removeEventListener("click", this.#boundClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setActiveEditor(null);
|
this.setActiveEditor(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,13 +140,10 @@ class AnnotationEditorLayer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add some commands into the CommandManager (undo/redo stuff).
|
* Add some commands into the CommandManager (undo/redo stuff).
|
||||||
* @param {function} cmd
|
* @param {Object} params
|
||||||
* @param {function} undo
|
|
||||||
* @param {boolean} mustExec - If true the command is executed after having
|
|
||||||
* been added.
|
|
||||||
*/
|
*/
|
||||||
addCommands(cmd, undo, mustExec) {
|
addCommands(params) {
|
||||||
this.#uiManager.addCommands(cmd, undo, mustExec);
|
this.#uiManager.addCommands(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -232,7 +239,10 @@ class AnnotationEditorLayer {
|
|||||||
this.unselectAll();
|
this.unselectAll();
|
||||||
this.div.removeEventListener("click", this.#boundClick);
|
this.div.removeEventListener("click", this.#boundClick);
|
||||||
} else {
|
} else {
|
||||||
this.#uiManager.allowClick = false;
|
// When in Ink mode, setting the editor to null allows the
|
||||||
|
// user to have to make one click in order to start drawing.
|
||||||
|
this.#uiManager.allowClick =
|
||||||
|
this.#uiManager.getMode() === AnnotationEditorType.INK;
|
||||||
this.div.addEventListener("click", this.#boundClick);
|
this.div.addEventListener("click", this.#boundClick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -332,7 +342,7 @@ class AnnotationEditorLayer {
|
|||||||
editor.remove();
|
editor.remove();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addCommands(cmd, undo, true);
|
this.addCommands({ cmd, undo, mustExec: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -347,7 +357,7 @@ class AnnotationEditorLayer {
|
|||||||
editor.remove();
|
editor.remove();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addCommands(cmd, undo, false);
|
this.addCommands({ cmd, undo, mustExec: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -372,6 +372,21 @@ class AnnotationEditor {
|
|||||||
this.div.classList.remove("selectedEditor");
|
this.div.classList.remove("selectedEditor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update some parameters which have been changed through the UI.
|
||||||
|
* @param {number} type
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
|
updateParams(type, value) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get some properties to update in the UI.
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
get propertiesToUpdate() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { AnnotationEditor };
|
export { AnnotationEditor };
|
||||||
|
@ -14,12 +14,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AnnotationEditorParamsType,
|
||||||
AnnotationEditorType,
|
AnnotationEditorType,
|
||||||
assert,
|
assert,
|
||||||
LINE_FACTOR,
|
LINE_FACTOR,
|
||||||
} from "../../shared/util.js";
|
} from "../../shared/util.js";
|
||||||
import { AnnotationEditor } from "./editor.js";
|
import { AnnotationEditor } from "./editor.js";
|
||||||
import { bindEvents } from "./tools.js";
|
import { bindEvents } from "./tools.js";
|
||||||
|
import { getRGB } from "../display_utils.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic text editor in order to create a FreeTex annotation.
|
* Basic text editor in order to create a FreeTex annotation.
|
||||||
@ -41,10 +43,14 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
static _internalPadding = 0;
|
static _internalPadding = 0;
|
||||||
|
|
||||||
|
static _defaultFontSize = 10;
|
||||||
|
|
||||||
|
static _defaultColor = "CanvasText";
|
||||||
|
|
||||||
constructor(params) {
|
constructor(params) {
|
||||||
super({ ...params, name: "freeTextEditor" });
|
super({ ...params, name: "freeTextEditor" });
|
||||||
this.#color = params.color || "CanvasText";
|
this.#color = params.color || FreeTextEditor._defaultColor;
|
||||||
this.#fontSize = params.fontSize || 10;
|
this.#fontSize = params.fontSize || FreeTextEditor._defaultFontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
static initialize(l10n) {
|
static initialize(l10n) {
|
||||||
@ -89,6 +95,94 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
return editor;
|
return editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static updateDefaultParams(type, value) {
|
||||||
|
switch (type) {
|
||||||
|
case AnnotationEditorParamsType.FREETEXT_SIZE:
|
||||||
|
FreeTextEditor._defaultFontSize = value;
|
||||||
|
break;
|
||||||
|
case AnnotationEditorParamsType.FREETEXT_COLOR:
|
||||||
|
FreeTextEditor._defaultColor = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
updateParams(type, value) {
|
||||||
|
switch (type) {
|
||||||
|
case AnnotationEditorParamsType.FREETEXT_SIZE:
|
||||||
|
this.#updateFontSize(value);
|
||||||
|
break;
|
||||||
|
case AnnotationEditorParamsType.FREETEXT_COLOR:
|
||||||
|
this.#updateColor(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get defaultPropertiesToUpdate() {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
AnnotationEditorParamsType.FREETEXT_SIZE,
|
||||||
|
FreeTextEditor._defaultFontSize,
|
||||||
|
],
|
||||||
|
[AnnotationEditorParamsType.FREETEXT_COLOR, FreeTextEditor._defaultColor],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
get propertiesToUpdate() {
|
||||||
|
return [
|
||||||
|
[AnnotationEditorParamsType.FREETEXT_SIZE, this.#fontSize],
|
||||||
|
[AnnotationEditorParamsType.FREETEXT_COLOR, this.#color],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the font size and make this action as undoable.
|
||||||
|
* @param {number} fontSize
|
||||||
|
*/
|
||||||
|
#updateFontSize(fontSize) {
|
||||||
|
const setFontsize = size => {
|
||||||
|
this.editorDiv.style.fontSize = `calc(${size}px * var(--scale-factor))`;
|
||||||
|
this.translate(0, -(size - this.#fontSize) * this.parent.scaleFactor);
|
||||||
|
this.#fontSize = size;
|
||||||
|
};
|
||||||
|
const savedFontsize = this.#fontSize;
|
||||||
|
this.parent.addCommands({
|
||||||
|
cmd: () => {
|
||||||
|
setFontsize(fontSize);
|
||||||
|
},
|
||||||
|
undo: () => {
|
||||||
|
setFontsize(savedFontsize);
|
||||||
|
},
|
||||||
|
mustExec: true,
|
||||||
|
type: AnnotationEditorParamsType.FREETEXT_SIZE,
|
||||||
|
overwriteIfSameType: true,
|
||||||
|
keepUndo: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the color and make this action undoable.
|
||||||
|
* @param {string} color
|
||||||
|
*/
|
||||||
|
#updateColor(color) {
|
||||||
|
const savedColor = this.#color;
|
||||||
|
this.parent.addCommands({
|
||||||
|
cmd: () => {
|
||||||
|
this.#color = color;
|
||||||
|
this.editorDiv.style.color = color;
|
||||||
|
},
|
||||||
|
undo: () => {
|
||||||
|
this.#color = savedColor;
|
||||||
|
this.editorDiv.style.color = savedColor;
|
||||||
|
},
|
||||||
|
mustExec: true,
|
||||||
|
type: AnnotationEditorParamsType.FREETEXT_COLOR,
|
||||||
|
overwriteIfSameType: true,
|
||||||
|
keepUndo: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
getInitialTranslation() {
|
getInitialTranslation() {
|
||||||
// The start of the base line is where the user clicked.
|
// The start of the base line is where the user clicked.
|
||||||
@ -116,6 +210,7 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
enableEditMode() {
|
enableEditMode() {
|
||||||
super.enableEditMode();
|
super.enableEditMode();
|
||||||
this.overlayDiv.classList.remove("enabled");
|
this.overlayDiv.classList.remove("enabled");
|
||||||
|
this.editorDiv.contentEditable = true;
|
||||||
this.div.draggable = false;
|
this.div.draggable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +218,7 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
disableEditMode() {
|
disableEditMode() {
|
||||||
super.disableEditMode();
|
super.disableEditMode();
|
||||||
this.overlayDiv.classList.add("enabled");
|
this.overlayDiv.classList.add("enabled");
|
||||||
|
this.editorDiv.contentEditable = false;
|
||||||
this.div.draggable = true;
|
this.div.draggable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +319,7 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
this.editorDiv.contentEditable = true;
|
this.editorDiv.contentEditable = true;
|
||||||
|
|
||||||
const { style } = this.editorDiv;
|
const { style } = this.editorDiv;
|
||||||
style.fontSize = `${this.#fontSize}%`;
|
style.fontSize = `calc(${this.#fontSize}px * var(--scale-factor))`;
|
||||||
style.color = this.#color;
|
style.color = this.#color;
|
||||||
|
|
||||||
this.div.append(this.editorDiv);
|
this.div.append(this.editorDiv);
|
||||||
@ -248,6 +344,7 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
);
|
);
|
||||||
// eslint-disable-next-line no-unsanitized/property
|
// eslint-disable-next-line no-unsanitized/property
|
||||||
this.editorDiv.innerHTML = this.#contentHTML;
|
this.editorDiv.innerHTML = this.#contentHTML;
|
||||||
|
this.div.draggable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.div;
|
return this.div;
|
||||||
@ -258,9 +355,12 @@ class FreeTextEditor extends AnnotationEditor {
|
|||||||
const padding = FreeTextEditor._internalPadding * this.parent.scaleFactor;
|
const padding = FreeTextEditor._internalPadding * this.parent.scaleFactor;
|
||||||
const rect = this.getRect(padding, padding);
|
const rect = this.getRect(padding, padding);
|
||||||
|
|
||||||
|
// We don't use this.#color directly because it can be CanvasText.
|
||||||
|
const color = getRGB(getComputedStyle(this.editorDiv).color);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
annotationType: AnnotationEditorType.FREETEXT,
|
annotationType: AnnotationEditorType.FREETEXT,
|
||||||
color: [0, 0, 0],
|
color,
|
||||||
fontSize: this.#fontSize,
|
fontSize: this.#fontSize,
|
||||||
value: this.#content,
|
value: this.#content,
|
||||||
pageIndex: this.parent.pageIndex,
|
pageIndex: this.parent.pageIndex,
|
||||||
|
@ -13,9 +13,14 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AnnotationEditorType, Util } from "../../shared/util.js";
|
import {
|
||||||
|
AnnotationEditorParamsType,
|
||||||
|
AnnotationEditorType,
|
||||||
|
Util,
|
||||||
|
} from "../../shared/util.js";
|
||||||
import { AnnotationEditor } from "./editor.js";
|
import { AnnotationEditor } from "./editor.js";
|
||||||
import { fitCurve } from "./fit_curve/fit_curve.js";
|
import { fitCurve } from "./fit_curve/fit_curve.js";
|
||||||
|
import { getRGB } from "../display_utils.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic draw editor in order to generate an Ink annotation.
|
* Basic draw editor in order to generate an Ink annotation.
|
||||||
@ -43,10 +48,14 @@ class InkEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
#realHeight = 0;
|
#realHeight = 0;
|
||||||
|
|
||||||
|
static _defaultThickness = 1;
|
||||||
|
|
||||||
|
static _defaultColor = "CanvasText";
|
||||||
|
|
||||||
constructor(params) {
|
constructor(params) {
|
||||||
super({ ...params, name: "inkEditor" });
|
super({ ...params, name: "inkEditor" });
|
||||||
this.color = params.color || "CanvasText";
|
this.color = params.color || InkEditor._defaultColor;
|
||||||
this.thickness = params.thickness || 1;
|
this.thickness = params.thickness || InkEditor._defaultThickness;
|
||||||
this.paths = [];
|
this.paths = [];
|
||||||
this.bezierPath2D = [];
|
this.bezierPath2D = [];
|
||||||
this.currentPath = [];
|
this.currentPath = [];
|
||||||
@ -89,6 +98,88 @@ class InkEditor extends AnnotationEditor {
|
|||||||
return editor;
|
return editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static updateDefaultParams(type, value) {
|
||||||
|
switch (type) {
|
||||||
|
case AnnotationEditorParamsType.INK_THICKNESS:
|
||||||
|
InkEditor._defaultThickness = value;
|
||||||
|
break;
|
||||||
|
case AnnotationEditorParamsType.INK_COLOR:
|
||||||
|
InkEditor._defaultColor = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
updateParams(type, value) {
|
||||||
|
switch (type) {
|
||||||
|
case AnnotationEditorParamsType.INK_THICKNESS:
|
||||||
|
this.#updateThickness(value);
|
||||||
|
break;
|
||||||
|
case AnnotationEditorParamsType.INK_COLOR:
|
||||||
|
this.#updateColor(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get defaultPropertiesToUpdate() {
|
||||||
|
return [
|
||||||
|
[AnnotationEditorParamsType.INK_THICKNESS, InkEditor._defaultThickness],
|
||||||
|
[AnnotationEditorParamsType.INK_COLOR, InkEditor._defaultColor],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
get propertiesToUpdate() {
|
||||||
|
return [
|
||||||
|
[AnnotationEditorParamsType.INK_THICKNESS, this.thickness],
|
||||||
|
[AnnotationEditorParamsType.INK_COLOR, this.color],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the thickness and make this action undoable.
|
||||||
|
* @param {number} thickness
|
||||||
|
*/
|
||||||
|
#updateThickness(thickness) {
|
||||||
|
const savedThickness = this.thickness;
|
||||||
|
this.parent.addCommands({
|
||||||
|
cmd: () => {
|
||||||
|
this.thickness = thickness;
|
||||||
|
this.#fitToContent();
|
||||||
|
},
|
||||||
|
undo: () => {
|
||||||
|
this.thickness = savedThickness;
|
||||||
|
this.#fitToContent();
|
||||||
|
},
|
||||||
|
mustExec: true,
|
||||||
|
type: AnnotationEditorParamsType.INK_THICKNESS,
|
||||||
|
overwriteIfSameType: true,
|
||||||
|
keepUndo: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the color and make this action undoable.
|
||||||
|
* @param {string} color
|
||||||
|
*/
|
||||||
|
#updateColor(color) {
|
||||||
|
const savedColor = this.color;
|
||||||
|
this.parent.addCommands({
|
||||||
|
cmd: () => {
|
||||||
|
this.color = color;
|
||||||
|
this.#redraw();
|
||||||
|
},
|
||||||
|
undo: () => {
|
||||||
|
this.color = savedColor;
|
||||||
|
this.#redraw();
|
||||||
|
},
|
||||||
|
mustExec: true,
|
||||||
|
type: AnnotationEditorParamsType.INK_COLOR,
|
||||||
|
overwriteIfSameType: true,
|
||||||
|
keepUndo: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
rebuild() {
|
rebuild() {
|
||||||
if (this.div === null) {
|
if (this.div === null) {
|
||||||
@ -186,7 +277,7 @@ class InkEditor extends AnnotationEditor {
|
|||||||
this.ctx.lineWidth =
|
this.ctx.lineWidth =
|
||||||
(this.thickness * this.parent.scaleFactor) / this.scaleFactor;
|
(this.thickness * this.parent.scaleFactor) / this.scaleFactor;
|
||||||
this.ctx.lineCap = "round";
|
this.ctx.lineCap = "round";
|
||||||
this.ctx.lineJoin = "miter";
|
this.ctx.lineJoin = "round";
|
||||||
this.ctx.miterLimit = 10;
|
this.ctx.miterLimit = 10;
|
||||||
this.ctx.strokeStyle = this.color;
|
this.ctx.strokeStyle = this.color;
|
||||||
}
|
}
|
||||||
@ -263,7 +354,7 @@ class InkEditor extends AnnotationEditor {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.parent.addCommands(cmd, undo, true);
|
this.parent.addCommands({ cmd, undo, mustExec: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -755,9 +846,12 @@ class InkEditor extends AnnotationEditor {
|
|||||||
const height =
|
const height =
|
||||||
this.rotation % 180 === 0 ? rect[3] - rect[1] : rect[2] - rect[0];
|
this.rotation % 180 === 0 ? rect[3] - rect[1] : rect[2] - rect[0];
|
||||||
|
|
||||||
|
// We don't use this.color directly because it can be CanvasText.
|
||||||
|
const color = getRGB(this.ctx.strokeStyle);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
annotationType: AnnotationEditorType.INK,
|
annotationType: AnnotationEditorType.INK,
|
||||||
color: [0, 0, 0],
|
color,
|
||||||
thickness: this.thickness,
|
thickness: this.thickness,
|
||||||
paths: this.#serializePaths(
|
paths: this.#serializePaths(
|
||||||
this.scaleFactor / this.parent.scaleFactor,
|
this.scaleFactor / this.parent.scaleFactor,
|
||||||
|
@ -64,9 +64,36 @@ class CommandManager {
|
|||||||
* @param {function} cmd
|
* @param {function} cmd
|
||||||
* @param {function} undo
|
* @param {function} undo
|
||||||
* @param {boolean} mustExec
|
* @param {boolean} mustExec
|
||||||
|
* @param {number} type
|
||||||
|
* @param {boolean} overwriteIfSameType
|
||||||
|
* @param {boolean} keepUndo
|
||||||
*/
|
*/
|
||||||
add(cmd, undo, mustExec) {
|
add({
|
||||||
const save = [cmd, undo];
|
cmd,
|
||||||
|
undo,
|
||||||
|
mustExec,
|
||||||
|
type = NaN,
|
||||||
|
overwriteIfSameType = false,
|
||||||
|
keepUndo = false,
|
||||||
|
}) {
|
||||||
|
const save = { cmd, undo, type };
|
||||||
|
if (
|
||||||
|
overwriteIfSameType &&
|
||||||
|
!isNaN(this.#position) &&
|
||||||
|
this.#commands[this.#position].type === type
|
||||||
|
) {
|
||||||
|
// For example when we change a color we don't want to
|
||||||
|
// be able to undo all the steps, hence we only want to
|
||||||
|
// keep the last undoable action in this sequence of actions.
|
||||||
|
if (keepUndo) {
|
||||||
|
save.undo = this.#commands[this.#position].undo;
|
||||||
|
}
|
||||||
|
this.#commands[this.#position] = save;
|
||||||
|
if (mustExec) {
|
||||||
|
cmd();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const next = (this.#position + 1) % this.#maxSize;
|
const next = (this.#position + 1) % this.#maxSize;
|
||||||
if (next !== this.#start) {
|
if (next !== this.#start) {
|
||||||
if (this.#start < next) {
|
if (this.#start < next) {
|
||||||
@ -94,7 +121,7 @@ class CommandManager {
|
|||||||
// Nothing to undo.
|
// Nothing to undo.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#commands[this.#position][1]();
|
this.#commands[this.#position].undo();
|
||||||
if (this.#position === this.#start) {
|
if (this.#position === this.#start) {
|
||||||
this.#position = NaN;
|
this.#position = NaN;
|
||||||
} else {
|
} else {
|
||||||
@ -108,7 +135,7 @@ class CommandManager {
|
|||||||
redo() {
|
redo() {
|
||||||
if (isNaN(this.#position)) {
|
if (isNaN(this.#position)) {
|
||||||
if (this.#start < this.#commands.length) {
|
if (this.#start < this.#commands.length) {
|
||||||
this.#commands[this.#start][0]();
|
this.#commands[this.#start].cmd();
|
||||||
this.#position = this.#start;
|
this.#position = this.#start;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -116,7 +143,7 @@ class CommandManager {
|
|||||||
|
|
||||||
const next = (this.#position + 1) % this.#maxSize;
|
const next = (this.#position + 1) % this.#maxSize;
|
||||||
if (next !== this.#start && next < this.#commands.length) {
|
if (next !== this.#start && next < this.#commands.length) {
|
||||||
this.#commands[next][0]();
|
this.#commands[next].cmd();
|
||||||
this.#position = next;
|
this.#position = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,6 +300,10 @@ class AnnotationEditorUIManager {
|
|||||||
|
|
||||||
#commandManager = new CommandManager();
|
#commandManager = new CommandManager();
|
||||||
|
|
||||||
|
#editorTypes = null;
|
||||||
|
|
||||||
|
#eventBus = null;
|
||||||
|
|
||||||
#idManager = new IdManager();
|
#idManager = new IdManager();
|
||||||
|
|
||||||
#isAllSelected = false;
|
#isAllSelected = false;
|
||||||
@ -281,6 +312,26 @@ class AnnotationEditorUIManager {
|
|||||||
|
|
||||||
#mode = AnnotationEditorType.NONE;
|
#mode = AnnotationEditorType.NONE;
|
||||||
|
|
||||||
|
#previousActiveEditor = null;
|
||||||
|
|
||||||
|
constructor(eventBus) {
|
||||||
|
this.#eventBus = eventBus;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dispatchUpdateUI(details) {
|
||||||
|
this.#eventBus?.dispatch("annotationeditorparamschanged", {
|
||||||
|
source: this,
|
||||||
|
details,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerEditorTypes(types) {
|
||||||
|
this.#editorTypes = types;
|
||||||
|
for (const editorType of this.#editorTypes) {
|
||||||
|
this.#dispatchUpdateUI(editorType.defaultPropertiesToUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an id.
|
* Get an id.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
@ -326,6 +377,21 @@ class AnnotationEditorUIManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a parameter in the current editor or globally.
|
||||||
|
* @param {number} type
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
|
updateParams(type, value) {
|
||||||
|
(this.#activeEditor || this.#previousActiveEditor)?.updateParams(
|
||||||
|
type,
|
||||||
|
value
|
||||||
|
);
|
||||||
|
for (const editorType of this.#editorTypes) {
|
||||||
|
editorType.updateDefaultParams(type, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable all the layers.
|
* Enable all the layers.
|
||||||
*/
|
*/
|
||||||
@ -395,7 +461,24 @@ class AnnotationEditorUIManager {
|
|||||||
* @param {AnnotationEditor} editor
|
* @param {AnnotationEditor} editor
|
||||||
*/
|
*/
|
||||||
setActiveEditor(editor) {
|
setActiveEditor(editor) {
|
||||||
|
if (this.#activeEditor === editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#previousActiveEditor = this.#activeEditor;
|
||||||
|
|
||||||
this.#activeEditor = editor;
|
this.#activeEditor = editor;
|
||||||
|
if (editor) {
|
||||||
|
this.#dispatchUpdateUI(editor.propertiesToUpdate);
|
||||||
|
} else {
|
||||||
|
if (this.#previousActiveEditor) {
|
||||||
|
this.#dispatchUpdateUI(this.#previousActiveEditor.propertiesToUpdate);
|
||||||
|
} else {
|
||||||
|
for (const editorType of this.#editorTypes) {
|
||||||
|
this.#dispatchUpdateUI(editorType.defaultPropertiesToUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -414,12 +497,10 @@ class AnnotationEditorUIManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a command to execute (cmd) and another one to undo it.
|
* Add a command to execute (cmd) and another one to undo it.
|
||||||
* @param {function} cmd
|
* @param {Object} params
|
||||||
* @param {function} undo
|
|
||||||
* @param {boolean} mustExec
|
|
||||||
*/
|
*/
|
||||||
addCommands(cmd, undo, mustExec) {
|
addCommands(params) {
|
||||||
this.#commandManager.add(cmd, undo, mustExec);
|
this.#commandManager.add(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -468,7 +549,7 @@ class AnnotationEditorUIManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addCommands(cmd, undo, true);
|
this.addCommands({ cmd, undo, mustExec: true });
|
||||||
} else {
|
} else {
|
||||||
if (!this.#activeEditor) {
|
if (!this.#activeEditor) {
|
||||||
return;
|
return;
|
||||||
@ -482,7 +563,7 @@ class AnnotationEditorUIManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addCommands(cmd, undo, true);
|
this.addCommands({ cmd, undo, mustExec: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -509,7 +590,7 @@ class AnnotationEditorUIManager {
|
|||||||
layer.addOrRebuild(editor);
|
layer.addOrRebuild(editor);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addCommands(cmd, undo, true);
|
this.addCommands({ cmd, undo, mustExec: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -530,7 +611,7 @@ class AnnotationEditorUIManager {
|
|||||||
editor.remove();
|
editor.remove();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addCommands(cmd, undo, true);
|
this.addCommands({ cmd, undo, mustExec: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
/** @typedef {import("./display/text_layer").TextLayerRenderTask} TextLayerRenderTask */
|
/** @typedef {import("./display/text_layer").TextLayerRenderTask} TextLayerRenderTask */
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AnnotationEditorParamsType,
|
||||||
AnnotationEditorType,
|
AnnotationEditorType,
|
||||||
AnnotationMode,
|
AnnotationMode,
|
||||||
CMapCompressionType,
|
CMapCompressionType,
|
||||||
@ -110,6 +111,7 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")) {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
AnnotationEditorLayer,
|
AnnotationEditorLayer,
|
||||||
|
AnnotationEditorParamsType,
|
||||||
AnnotationEditorType,
|
AnnotationEditorType,
|
||||||
AnnotationEditorUIManager,
|
AnnotationEditorUIManager,
|
||||||
AnnotationLayer,
|
AnnotationLayer,
|
||||||
|
@ -60,6 +60,13 @@ const AnnotationEditorType = {
|
|||||||
INK: 15,
|
INK: 15,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AnnotationEditorParamsType = {
|
||||||
|
FREETEXT_SIZE: 0,
|
||||||
|
FREETEXT_COLOR: 1,
|
||||||
|
INK_COLOR: 2,
|
||||||
|
INK_THICKNESS: 3,
|
||||||
|
};
|
||||||
|
|
||||||
// Permission flags from Table 22, Section 7.6.3.2 of the PDF specification.
|
// Permission flags from Table 22, Section 7.6.3.2 of the PDF specification.
|
||||||
const PermissionFlag = {
|
const PermissionFlag = {
|
||||||
PRINT: 0x04,
|
PRINT: 0x04,
|
||||||
@ -1146,6 +1153,7 @@ export {
|
|||||||
AbortException,
|
AbortException,
|
||||||
AnnotationActionEventType,
|
AnnotationActionEventType,
|
||||||
AnnotationBorderStyleType,
|
AnnotationBorderStyleType,
|
||||||
|
AnnotationEditorParamsType,
|
||||||
AnnotationEditorPrefix,
|
AnnotationEditorPrefix,
|
||||||
AnnotationEditorType,
|
AnnotationEditorType,
|
||||||
AnnotationFieldFlag,
|
AnnotationFieldFlag,
|
||||||
|
@ -94,6 +94,7 @@ describe("Editor", () => {
|
|||||||
const content = await page.$eval(`${editorPrefix}0`, el =>
|
const content = await page.$eval(`${editorPrefix}0`, el =>
|
||||||
el.innerText.trimEnd()
|
el.innerText.trimEnd()
|
||||||
);
|
);
|
||||||
|
|
||||||
let pastedContent = await page.$eval(`${editorPrefix}2`, el =>
|
let pastedContent = await page.$eval(`${editorPrefix}2`, el =>
|
||||||
el.innerText.trimEnd()
|
el.innerText.trimEnd()
|
||||||
);
|
);
|
||||||
|
@ -4217,8 +4217,8 @@ describe("annotation", function () {
|
|||||||
const appearance = data.dependencies[0].data;
|
const appearance = data.dependencies[0].data;
|
||||||
expect(appearance).toEqual(
|
expect(appearance).toEqual(
|
||||||
"2 0 obj\n" +
|
"2 0 obj\n" +
|
||||||
"<< /FormType 1 /Subtype /Form /Type /XObject /BBox [0 0 44 44] /Length 121>> stream\n" +
|
"<< /FormType 1 /Subtype /Form /Type /XObject /BBox [0 0 44 44] /Length 129>> stream\n" +
|
||||||
"1 w\n" +
|
"1 w 1 J 1 j\n" +
|
||||||
"0 G\n" +
|
"0 G\n" +
|
||||||
"10 11 m\n" +
|
"10 11 m\n" +
|
||||||
"12 13 14 15 16 17 c\n" +
|
"12 13 14 15 16 17 c\n" +
|
||||||
@ -4243,8 +4243,8 @@ describe("annotation", function () {
|
|||||||
annotationType: AnnotationEditorType.INK,
|
annotationType: AnnotationEditorType.INK,
|
||||||
rect: [12, 34, 56, 78],
|
rect: [12, 34, 56, 78],
|
||||||
rotation: 0,
|
rotation: 0,
|
||||||
thickness: 1,
|
thickness: 3,
|
||||||
color: [0, 0, 0],
|
color: [0, 255, 0],
|
||||||
paths: [
|
paths: [
|
||||||
{
|
{
|
||||||
bezier: [1, 2, 3, 4, 5, 6, 7, 8],
|
bezier: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
@ -4264,10 +4264,12 @@ describe("annotation", function () {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(operatorList.argsArray.length).toEqual(6);
|
expect(operatorList.argsArray.length).toEqual(8);
|
||||||
expect(operatorList.fnArray).toEqual([
|
expect(operatorList.fnArray).toEqual([
|
||||||
OPS.beginAnnotation,
|
OPS.beginAnnotation,
|
||||||
OPS.setLineWidth,
|
OPS.setLineWidth,
|
||||||
|
OPS.setLineCap,
|
||||||
|
OPS.setLineJoin,
|
||||||
OPS.setStrokeRGBColor,
|
OPS.setStrokeRGBColor,
|
||||||
OPS.constructPath,
|
OPS.constructPath,
|
||||||
OPS.stroke,
|
OPS.stroke,
|
||||||
@ -4275,16 +4277,20 @@ describe("annotation", function () {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Linewidth.
|
// Linewidth.
|
||||||
expect(operatorList.argsArray[1]).toEqual([1]);
|
expect(operatorList.argsArray[1]).toEqual([3]);
|
||||||
|
// LineCap.
|
||||||
|
expect(operatorList.argsArray[2]).toEqual([1]);
|
||||||
|
// LineJoin.
|
||||||
|
expect(operatorList.argsArray[3]).toEqual([1]);
|
||||||
// Color.
|
// Color.
|
||||||
expect(operatorList.argsArray[2]).toEqual(
|
expect(operatorList.argsArray[4]).toEqual(
|
||||||
new Uint8ClampedArray([0, 0, 0])
|
new Uint8ClampedArray([0, 255, 0])
|
||||||
);
|
);
|
||||||
// Path.
|
// Path.
|
||||||
expect(operatorList.argsArray[3][0]).toEqual([OPS.moveTo, OPS.curveTo]);
|
expect(operatorList.argsArray[5][0]).toEqual([OPS.moveTo, OPS.curveTo]);
|
||||||
expect(operatorList.argsArray[3][1]).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
|
expect(operatorList.argsArray[5][1]).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||||
// Min-max.
|
// Min-max.
|
||||||
expect(operatorList.argsArray[3][2]).toEqual([1, 1, 2, 2]);
|
expect(operatorList.argsArray[5][2]).toEqual([1, 1, 2, 2]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
84
web/annotation_editor_params.js
Normal file
84
web/annotation_editor_params.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/* Copyright 2022 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 { AnnotationEditorParamsType } from "pdfjs-lib";
|
||||||
|
|
||||||
|
class AnnotationEditorParams {
|
||||||
|
/**
|
||||||
|
* @param {AnnotationEditorParamsOptions} options
|
||||||
|
* @param {EventBus} eventBus
|
||||||
|
*/
|
||||||
|
constructor(options, eventBus) {
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.#bindListeners(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
#bindListeners({
|
||||||
|
editorFreeTextFontSize,
|
||||||
|
editorFreeTextColor,
|
||||||
|
editorInkColor,
|
||||||
|
editorInkThickness,
|
||||||
|
}) {
|
||||||
|
editorFreeTextFontSize.addEventListener("input", evt => {
|
||||||
|
this.eventBus.dispatch("switchannotationeditorparams", {
|
||||||
|
source: this,
|
||||||
|
type: AnnotationEditorParamsType.FREETEXT_SIZE,
|
||||||
|
value: editorFreeTextFontSize.valueAsNumber,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editorFreeTextColor.addEventListener("input", evt => {
|
||||||
|
this.eventBus.dispatch("switchannotationeditorparams", {
|
||||||
|
source: this,
|
||||||
|
type: AnnotationEditorParamsType.FREETEXT_COLOR,
|
||||||
|
value: editorFreeTextColor.value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editorInkColor.addEventListener("input", evt => {
|
||||||
|
this.eventBus.dispatch("switchannotationeditorparams", {
|
||||||
|
source: this,
|
||||||
|
type: AnnotationEditorParamsType.INK_COLOR,
|
||||||
|
value: editorInkColor.value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editorInkThickness.addEventListener("input", evt => {
|
||||||
|
this.eventBus.dispatch("switchannotationeditorparams", {
|
||||||
|
source: this,
|
||||||
|
type: AnnotationEditorParamsType.INK_THICKNESS,
|
||||||
|
value: editorInkThickness.valueAsNumber,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventBus._on("annotationeditorparamschanged", evt => {
|
||||||
|
for (const [type, value] of evt.details) {
|
||||||
|
switch (type) {
|
||||||
|
case AnnotationEditorParamsType.FREETEXT_SIZE:
|
||||||
|
editorFreeTextFontSize.value = value;
|
||||||
|
break;
|
||||||
|
case AnnotationEditorParamsType.FREETEXT_COLOR:
|
||||||
|
editorFreeTextColor.value = value;
|
||||||
|
break;
|
||||||
|
case AnnotationEditorParamsType.INK_COLOR:
|
||||||
|
editorInkColor.value = value;
|
||||||
|
break;
|
||||||
|
case AnnotationEditorParamsType.INK_THICKNESS:
|
||||||
|
editorInkThickness.value = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { AnnotationEditorParams };
|
14
web/app.js
14
web/app.js
@ -56,6 +56,7 @@ import {
|
|||||||
} from "pdfjs-lib";
|
} from "pdfjs-lib";
|
||||||
import { CursorTool, PDFCursorTools } from "./pdf_cursor_tools.js";
|
import { CursorTool, PDFCursorTools } from "./pdf_cursor_tools.js";
|
||||||
import { LinkTarget, PDFLinkService } from "./pdf_link_service.js";
|
import { LinkTarget, PDFLinkService } from "./pdf_link_service.js";
|
||||||
|
import { AnnotationEditorParams } from "./annotation_editor_params.js";
|
||||||
import { OverlayManager } from "./overlay_manager.js";
|
import { OverlayManager } from "./overlay_manager.js";
|
||||||
import { PasswordPrompt } from "./password_prompt.js";
|
import { PasswordPrompt } from "./password_prompt.js";
|
||||||
import { PDFAttachmentViewer } from "./pdf_attachment_viewer.js";
|
import { PDFAttachmentViewer } from "./pdf_attachment_viewer.js";
|
||||||
@ -237,6 +238,8 @@ const PDFViewerApplication = {
|
|||||||
eventBus: null,
|
eventBus: null,
|
||||||
/** @type {IL10n} */
|
/** @type {IL10n} */
|
||||||
l10n: null,
|
l10n: null,
|
||||||
|
/** @type {AnnotationEditorParams} */
|
||||||
|
annotationEditorParams: null,
|
||||||
isInitialViewSet: false,
|
isInitialViewSet: false,
|
||||||
downloadComplete: false,
|
downloadComplete: false,
|
||||||
isViewerEmbedded: window.parent !== window,
|
isViewerEmbedded: window.parent !== window,
|
||||||
@ -568,6 +571,10 @@ const PDFViewerApplication = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (annotationEditorEnabled) {
|
if (annotationEditorEnabled) {
|
||||||
|
this.annotationEditorParams = new AnnotationEditorParams(
|
||||||
|
appConfig.annotationEditorParams,
|
||||||
|
eventBus
|
||||||
|
);
|
||||||
for (const element of [
|
for (const element of [
|
||||||
document.getElementById("editorModeButtons"),
|
document.getElementById("editorModeButtons"),
|
||||||
document.getElementById("editorModeSeparator"),
|
document.getElementById("editorModeSeparator"),
|
||||||
@ -1907,6 +1914,10 @@ const PDFViewerApplication = {
|
|||||||
"switchannotationeditormode",
|
"switchannotationeditormode",
|
||||||
webViewerSwitchAnnotationEditorMode
|
webViewerSwitchAnnotationEditorMode
|
||||||
);
|
);
|
||||||
|
eventBus._on(
|
||||||
|
"switchannotationeditorparams",
|
||||||
|
webViewerSwitchAnnotationEditorParams
|
||||||
|
);
|
||||||
eventBus._on("print", webViewerPrint);
|
eventBus._on("print", webViewerPrint);
|
||||||
eventBus._on("download", webViewerDownload);
|
eventBus._on("download", webViewerDownload);
|
||||||
eventBus._on("firstpage", webViewerFirstPage);
|
eventBus._on("firstpage", webViewerFirstPage);
|
||||||
@ -2491,6 +2502,9 @@ function webViewerPresentationMode() {
|
|||||||
function webViewerSwitchAnnotationEditorMode(evt) {
|
function webViewerSwitchAnnotationEditorMode(evt) {
|
||||||
PDFViewerApplication.pdfViewer.annotationEditorMode = evt.mode;
|
PDFViewerApplication.pdfViewer.annotationEditorMode = evt.mode;
|
||||||
}
|
}
|
||||||
|
function webViewerSwitchAnnotationEditorParams(evt) {
|
||||||
|
PDFViewerApplication.pdfViewer.annotationEditorParams = evt;
|
||||||
|
}
|
||||||
function webViewerPrint() {
|
function webViewerPrint() {
|
||||||
PDFViewerApplication.triggerPrinting();
|
PDFViewerApplication.triggerPrinting();
|
||||||
}
|
}
|
||||||
|
@ -720,7 +720,9 @@ class BaseViewer {
|
|||||||
mode: annotationEditorMode,
|
mode: annotationEditorMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#annotationEditorUIManager = new AnnotationEditorUIManager();
|
this.#annotationEditorUIManager = new AnnotationEditorUIManager(
|
||||||
|
this.eventBus
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2170,6 +2172,14 @@ class BaseViewer {
|
|||||||
|
|
||||||
this.#annotationEditorUIManager.updateMode(mode);
|
this.#annotationEditorUIManager.updateMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line accessor-pairs
|
||||||
|
set annotationEditorParams({ type, value }) {
|
||||||
|
if (!this.#annotationEditorUIManager) {
|
||||||
|
throw new Error(`The AnnotationEditor is not enabled.`);
|
||||||
|
}
|
||||||
|
this.#annotationEditorUIManager.updateParams(type, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { BaseViewer, PagesCountLimit, PDFPageViewBuffer };
|
export { BaseViewer, PagesCountLimit, PDFPageViewBuffer };
|
||||||
|
@ -104,7 +104,9 @@ class Toolbar {
|
|||||||
zoomOut: options.zoomOut,
|
zoomOut: options.zoomOut,
|
||||||
editorNoneButton: options.editorNoneButton,
|
editorNoneButton: options.editorNoneButton,
|
||||||
editorFreeTextButton: options.editorFreeTextButton,
|
editorFreeTextButton: options.editorFreeTextButton,
|
||||||
|
editorFreeTextParamsToolbar: options.editorFreeTextParamsToolbar,
|
||||||
editorInkButton: options.editorInkButton,
|
editorInkButton: options.editorInkButton,
|
||||||
|
editorInkParamsToolbar: options.editorInkParamsToolbar,
|
||||||
};
|
};
|
||||||
|
|
||||||
this._wasLocalized = false;
|
this._wasLocalized = false;
|
||||||
@ -212,20 +214,33 @@ class Toolbar {
|
|||||||
#bindEditorToolsListener({
|
#bindEditorToolsListener({
|
||||||
editorNoneButton,
|
editorNoneButton,
|
||||||
editorFreeTextButton,
|
editorFreeTextButton,
|
||||||
|
editorFreeTextParamsToolbar,
|
||||||
editorInkButton,
|
editorInkButton,
|
||||||
|
editorInkParamsToolbar,
|
||||||
}) {
|
}) {
|
||||||
const editorModeChanged = (evt, disableButtons = false) => {
|
const editorModeChanged = (evt, disableButtons = false) => {
|
||||||
const editorButtons = [
|
const editorButtons = [
|
||||||
[AnnotationEditorType.NONE, editorNoneButton],
|
{ mode: AnnotationEditorType.NONE, button: editorNoneButton },
|
||||||
[AnnotationEditorType.FREETEXT, editorFreeTextButton],
|
{
|
||||||
[AnnotationEditorType.INK, editorInkButton],
|
mode: AnnotationEditorType.FREETEXT,
|
||||||
|
button: editorFreeTextButton,
|
||||||
|
toolbar: editorFreeTextParamsToolbar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode: AnnotationEditorType.INK,
|
||||||
|
button: editorInkButton,
|
||||||
|
toolbar: editorInkParamsToolbar,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const [mode, button] of editorButtons) {
|
for (const { mode, button, toolbar } of editorButtons) {
|
||||||
const checked = mode === evt.mode;
|
const checked = mode === evt.mode;
|
||||||
button.classList.toggle("toggled", checked);
|
button.classList.toggle("toggled", checked);
|
||||||
button.setAttribute("aria-checked", checked);
|
button.setAttribute("aria-checked", checked);
|
||||||
button.disabled = disableButtons;
|
button.disabled = disableButtons;
|
||||||
|
if (toolbar) {
|
||||||
|
toolbar.classList.toggle("hidden", !checked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.eventBus._on("annotationeditormodechanged", editorModeChanged);
|
this.eventBus._on("annotationeditormodechanged", editorModeChanged);
|
||||||
|
@ -335,7 +335,8 @@ select {
|
|||||||
|
|
||||||
#toolbarContainer,
|
#toolbarContainer,
|
||||||
.findbar,
|
.findbar,
|
||||||
.secondaryToolbar {
|
.secondaryToolbar,
|
||||||
|
.editorParamsToolbar {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
background-color: var(--toolbar-bg-color);
|
background-color: var(--toolbar-bg-color);
|
||||||
@ -415,7 +416,8 @@ select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.findbar,
|
.findbar,
|
||||||
.secondaryToolbar {
|
.secondaryToolbar,
|
||||||
|
.editorParamsToolbar {
|
||||||
top: 32px;
|
top: 32px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
@ -487,7 +489,8 @@ select {
|
|||||||
background-color: rgba(255, 102, 102, 1);
|
background-color: rgba(255, 102, 102, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondaryToolbar {
|
.secondaryToolbar,
|
||||||
|
.editorParamsToolbar {
|
||||||
padding: 6px 0 10px;
|
padding: 6px 0 10px;
|
||||||
inset-inline-end: 4px;
|
inset-inline-end: 4px;
|
||||||
height: auto;
|
height: auto;
|
||||||
@ -495,6 +498,50 @@ select {
|
|||||||
background-color: var(--doorhanger-bg-color);
|
background-color: var(--doorhanger-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editorParamsToolbarContainer {
|
||||||
|
width: 220px;
|
||||||
|
margin-bottom: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorParamsToolbarContainer > .editorParamsSetter {
|
||||||
|
min-height: 26px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-inline: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorParamsToolbarContainer .editorParamsLabel {
|
||||||
|
padding-inline-end: 10px;
|
||||||
|
flex: none;
|
||||||
|
color: var(--main-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorParamsToolbarContainer .editorParamsColor {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorParamsToolbarContainer .editorParamsSlider {
|
||||||
|
background-color: transparent;
|
||||||
|
width: 90px;
|
||||||
|
flex: 0 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorParamsToolbarContainer .editorParamsSlider::-moz-range-progress {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
.editorParamsToolbarContainer .editorParamsSlider::-moz-range-track,
|
||||||
|
.editorParamsToolbarContainer
|
||||||
|
.editorParamsSlider::-webkit-slider-runnable-track {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
.editorParamsToolbarContainer .editorParamsSlider::-moz-range-thumb,
|
||||||
|
.editorParamsToolbarContainer .editorParamsSlider::-webkit-slider-thumb {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
#secondaryToolbarButtonContainer {
|
#secondaryToolbarButtonContainer {
|
||||||
max-width: 220px;
|
max-width: 220px;
|
||||||
min-height: 26px;
|
min-height: 26px;
|
||||||
@ -503,6 +550,16 @@ select {
|
|||||||
margin-bottom: -4px;
|
margin-bottom: -4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#editorInkParamsToolbar {
|
||||||
|
inset-inline-end: 40px;
|
||||||
|
background-color: var(--toolbar-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#editorFreeTextParamsToolbar {
|
||||||
|
inset-inline-end: 68px;
|
||||||
|
background-color: var(--toolbar-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
.doorHanger,
|
.doorHanger,
|
||||||
.doorHangerRight {
|
.doorHangerRight {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
@ -147,6 +147,32 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||||||
</div>
|
</div>
|
||||||
</div> <!-- findbar -->
|
</div> <!-- findbar -->
|
||||||
|
|
||||||
|
<div class="editorParamsToolbar hidden doorHangerRight" id="editorFreeTextParamsToolbar">
|
||||||
|
<div class="editorParamsToolbarContainer">
|
||||||
|
<div class="editorParamsSetter">
|
||||||
|
<label for="editorFreeTextColor" class="editorParamsLabel" data-l10n-id="editor_free_text_font_color">Font Color</label>
|
||||||
|
<input type="color" id="editorFreeTextColor" name="fontColor" class="editorParamsColor" tabindex="100">
|
||||||
|
</div>
|
||||||
|
<div class="editorParamsSetter">
|
||||||
|
<label for="editorFreeTextFontSize" class="editorParamsLabel" data-l10n-id="editor_free_text_font_size">Font Size</label>
|
||||||
|
<input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" name="fontSize" value="10" min="5" max="100" step="1" tabindex="101">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="editorParamsToolbar hidden doorHangerRight" id="editorInkParamsToolbar">
|
||||||
|
<div class="editorParamsToolbarContainer">
|
||||||
|
<div class="editorParamsSetter">
|
||||||
|
<label for="editorInkColor" class="editorParamsLabel" data-l10n-id="editor_ink_line_color">Line Color</label>
|
||||||
|
<input type="color" id="editorInkColor" name="inkColor" class="editorParamsColor" tabindex="102">
|
||||||
|
</div>
|
||||||
|
<div class="editorParamsSetter">
|
||||||
|
<label for="editorInkThickness" class="editorParamsLabel" data-l10n-id="editor_ink_line_thickness">Line Thickness</label>
|
||||||
|
<input type="range" id="editorInkThickness" class="editorParamsSlider" name="lineThickness" value="1" min="1" max="20" step="1" tabindex="103">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="secondaryToolbar" class="secondaryToolbar hidden doorHangerRight">
|
<div id="secondaryToolbar" class="secondaryToolbar hidden doorHangerRight">
|
||||||
<div id="secondaryToolbarButtonContainer">
|
<div id="secondaryToolbarButtonContainer">
|
||||||
<button id="secondaryPresentationMode" class="secondaryToolbarButton visibleLargeView" title="Switch to Presentation Mode" tabindex="51" data-l10n-id="presentation_mode">
|
<button id="secondaryPresentationMode" class="secondaryToolbarButton visibleLargeView" title="Switch to Presentation Mode" tabindex="51" data-l10n-id="presentation_mode">
|
||||||
|
@ -95,7 +95,11 @@ function getViewerConfiguration() {
|
|||||||
print: document.getElementById("print"),
|
print: document.getElementById("print"),
|
||||||
editorNoneButton: document.getElementById("editorNone"),
|
editorNoneButton: document.getElementById("editorNone"),
|
||||||
editorFreeTextButton: document.getElementById("editorFreeText"),
|
editorFreeTextButton: document.getElementById("editorFreeText"),
|
||||||
|
editorFreeTextParamsToolbar: document.getElementById(
|
||||||
|
"editorFreeTextParamsToolbar"
|
||||||
|
),
|
||||||
editorInkButton: document.getElementById("editorInk"),
|
editorInkButton: document.getElementById("editorInk"),
|
||||||
|
editorInkParamsToolbar: document.getElementById("editorInkParamsToolbar"),
|
||||||
presentationModeButton: document.getElementById("presentationMode"),
|
presentationModeButton: document.getElementById("presentationMode"),
|
||||||
download: document.getElementById("download"),
|
download: document.getElementById("download"),
|
||||||
viewBookmark: document.getElementById("viewBookmark"),
|
viewBookmark: document.getElementById("viewBookmark"),
|
||||||
@ -193,6 +197,12 @@ function getViewerConfiguration() {
|
|||||||
linearized: document.getElementById("linearizedField"),
|
linearized: document.getElementById("linearizedField"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
annotationEditorParams: {
|
||||||
|
editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"),
|
||||||
|
editorFreeTextColor: document.getElementById("editorFreeTextColor"),
|
||||||
|
editorInkColor: document.getElementById("editorInkColor"),
|
||||||
|
editorInkThickness: document.getElementById("editorInkThickness"),
|
||||||
|
},
|
||||||
errorWrapper,
|
errorWrapper,
|
||||||
printContainer: document.getElementById("printContainer"),
|
printContainer: document.getElementById("printContainer"),
|
||||||
openFileInput:
|
openFileInput:
|
||||||
|
Loading…
Reference in New Issue
Block a user