mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-22 16:18:08 +02:00
[Editor] Add the possibility to change line opacity in Ink editor
This commit is contained in:
parent
45b9e8417d
commit
7831a100b3
10 changed files with 215 additions and 19 deletions
|
@ -3757,7 +3757,7 @@ class InkAnnotation extends MarkupAnnotation {
|
|||
}
|
||||
|
||||
static async createNewAppearanceStream(annotation, xref, params) {
|
||||
const { color, rect, rotation, paths, thickness } = annotation;
|
||||
const { color, rect, rotation, paths, thickness, opacity } = annotation;
|
||||
const [x1, y1, x2, y2] = rect;
|
||||
let w = x2 - x1;
|
||||
let h = y2 - y1;
|
||||
|
@ -3770,6 +3770,11 @@ class InkAnnotation extends MarkupAnnotation {
|
|||
`${thickness} w 1 J 1 j`,
|
||||
`${getPdfColor(color, /* isFill */ false)}`,
|
||||
];
|
||||
|
||||
if (opacity !== 1) {
|
||||
appearanceBuffer.push("/R0 gs");
|
||||
}
|
||||
|
||||
const buffer = [];
|
||||
for (const { bezier } of paths) {
|
||||
buffer.length = 0;
|
||||
|
@ -3800,6 +3805,17 @@ class InkAnnotation extends MarkupAnnotation {
|
|||
appearanceStreamDict.set("Matrix", matrix);
|
||||
}
|
||||
|
||||
if (opacity !== 1) {
|
||||
const resources = new Dict(xref);
|
||||
const extGState = new Dict(xref);
|
||||
const r0 = new Dict(xref);
|
||||
r0.set("CA", opacity);
|
||||
r0.set("Type", Name.get("ExtGState"));
|
||||
extGState.set("R0", r0);
|
||||
resources.set("ExtGState", extGState);
|
||||
appearanceStreamDict.set("Resources", resources);
|
||||
}
|
||||
|
||||
const ap = new StringStream(appearance);
|
||||
ap.dict = appearanceStreamDict;
|
||||
|
||||
|
|
|
@ -585,6 +585,14 @@ function getRGB(color) {
|
|||
.map(x => parseInt(x));
|
||||
}
|
||||
|
||||
if (color.startsWith("rgba(")) {
|
||||
return color
|
||||
.slice(/* "rgba(".length */ 5, -1) // Strip out "rgba(" and ")".
|
||||
.split(",")
|
||||
.map(x => parseInt(x))
|
||||
.slice(0, 3);
|
||||
}
|
||||
|
||||
warn(`Not a valid color format: "${color}"`);
|
||||
return [0, 0, 0];
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
} from "../../shared/util.js";
|
||||
import { AnnotationEditor } from "./editor.js";
|
||||
import { fitCurve } from "pdfjs-fitCurve";
|
||||
import { opacityToHex } from "./tools.js";
|
||||
|
||||
// The dimensions of the resizer is 15x15:
|
||||
// https://searchfox.org/mozilla-central/rev/1ce190047b9556c3c10ab4de70a0e61d893e2954/toolkit/content/minimal-xul.css#136-137
|
||||
|
@ -48,14 +49,20 @@ class InkEditor extends AnnotationEditor {
|
|||
|
||||
#isCanvasInitialized = false;
|
||||
|
||||
#lastPoint = null;
|
||||
|
||||
#observer = null;
|
||||
|
||||
#realWidth = 0;
|
||||
|
||||
#realHeight = 0;
|
||||
|
||||
#requestFrameCallback = null;
|
||||
|
||||
static _defaultColor = null;
|
||||
|
||||
static _defaultOpacity = 1;
|
||||
|
||||
static _defaultThickness = 1;
|
||||
|
||||
static _l10nPromise;
|
||||
|
@ -64,6 +71,7 @@ class InkEditor extends AnnotationEditor {
|
|||
super({ ...params, name: "inkEditor" });
|
||||
this.color = params.color || null;
|
||||
this.thickness = params.thickness || null;
|
||||
this.opacity = params.opacity || null;
|
||||
this.paths = [];
|
||||
this.bezierPath2D = [];
|
||||
this.currentPath = [];
|
||||
|
@ -90,6 +98,9 @@ class InkEditor extends AnnotationEditor {
|
|||
case AnnotationEditorParamsType.INK_COLOR:
|
||||
InkEditor._defaultColor = value;
|
||||
break;
|
||||
case AnnotationEditorParamsType.INK_OPACITY:
|
||||
InkEditor._defaultOpacity = value / 100;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,6 +113,9 @@ class InkEditor extends AnnotationEditor {
|
|||
case AnnotationEditorParamsType.INK_COLOR:
|
||||
this.#updateColor(value);
|
||||
break;
|
||||
case AnnotationEditorParamsType.INK_OPACITY:
|
||||
this.#updateOpacity(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,6 +126,10 @@ class InkEditor extends AnnotationEditor {
|
|||
AnnotationEditorParamsType.INK_COLOR,
|
||||
InkEditor._defaultColor || AnnotationEditor._defaultLineColor,
|
||||
],
|
||||
[
|
||||
AnnotationEditorParamsType.INK_OPACITY,
|
||||
Math.round(InkEditor._defaultOpacity * 100),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -128,6 +146,10 @@ class InkEditor extends AnnotationEditor {
|
|||
InkEditor._defaultColor ||
|
||||
AnnotationEditor._defaultLineColor,
|
||||
],
|
||||
[
|
||||
AnnotationEditorParamsType.INK_OPACITY,
|
||||
Math.round(100 * (this.opacity ?? InkEditor._defaultOpacity)),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -175,6 +197,29 @@ class InkEditor extends AnnotationEditor {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the opacity and make this action undoable.
|
||||
* @param {number} opacity
|
||||
*/
|
||||
#updateOpacity(opacity) {
|
||||
opacity /= 100;
|
||||
const savedOpacity = this.opacity;
|
||||
this.parent.addCommands({
|
||||
cmd: () => {
|
||||
this.opacity = opacity;
|
||||
this.#redraw();
|
||||
},
|
||||
undo: () => {
|
||||
this.opacity = savedOpacity;
|
||||
this.#redraw();
|
||||
},
|
||||
mustExec: true,
|
||||
type: AnnotationEditorParamsType.INK_OPACITY,
|
||||
overwriteIfSameType: true,
|
||||
keepUndo: true,
|
||||
});
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
rebuild() {
|
||||
super.rebuild();
|
||||
|
@ -282,7 +327,7 @@ class InkEditor extends AnnotationEditor {
|
|||
this.ctx.lineCap = "round";
|
||||
this.ctx.lineJoin = "round";
|
||||
this.ctx.miterLimit = 10;
|
||||
this.ctx.strokeStyle = this.color;
|
||||
this.ctx.strokeStyle = `${this.color}${opacityToHex(this.opacity)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -298,11 +343,35 @@ class InkEditor extends AnnotationEditor {
|
|||
this.thickness ||= InkEditor._defaultThickness;
|
||||
this.color ||=
|
||||
InkEditor._defaultColor || AnnotationEditor._defaultLineColor;
|
||||
this.opacity ??= InkEditor._defaultOpacity;
|
||||
}
|
||||
this.currentPath.push([x, y]);
|
||||
this.#lastPoint = null;
|
||||
this.#setStroke();
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(x, y);
|
||||
|
||||
this.#requestFrameCallback = () => {
|
||||
if (!this.#requestFrameCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#lastPoint) {
|
||||
if (this.isEmpty()) {
|
||||
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
} else {
|
||||
this.#redraw();
|
||||
}
|
||||
|
||||
this.ctx.lineTo(...this.#lastPoint);
|
||||
this.#lastPoint = null;
|
||||
this.ctx.stroke();
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(this.#requestFrameCallback);
|
||||
};
|
||||
window.requestAnimationFrame(this.#requestFrameCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -311,9 +380,12 @@ class InkEditor extends AnnotationEditor {
|
|||
* @param {number} y
|
||||
*/
|
||||
#draw(x, y) {
|
||||
const [lastX, lastY] = this.currentPath.at(-1);
|
||||
if (x === lastX && y === lastY) {
|
||||
return;
|
||||
}
|
||||
this.currentPath.push([x, y]);
|
||||
this.ctx.lineTo(x, y);
|
||||
this.ctx.stroke();
|
||||
this.#lastPoint = [x, y];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -322,20 +394,22 @@ class InkEditor extends AnnotationEditor {
|
|||
* @param {number} y
|
||||
*/
|
||||
#stopDrawing(x, y) {
|
||||
this.ctx.closePath();
|
||||
this.#requestFrameCallback = null;
|
||||
|
||||
x = Math.min(Math.max(x, 0), this.canvas.width);
|
||||
y = Math.min(Math.max(y, 0), this.canvas.height);
|
||||
|
||||
this.currentPath.push([x, y]);
|
||||
const [lastX, lastY] = this.currentPath.at(-1);
|
||||
if (x !== lastX || y !== lastY) {
|
||||
this.currentPath.push([x, y]);
|
||||
}
|
||||
|
||||
// Interpolate the path entered by the user with some
|
||||
// Bezier's curves in order to have a smoother path and
|
||||
// to reduce the data size used to draw it in the PDF.
|
||||
let bezier;
|
||||
if (
|
||||
this.currentPath.length !== 2 ||
|
||||
this.currentPath[0][0] !== x ||
|
||||
this.currentPath[0][1] !== y
|
||||
) {
|
||||
if (this.currentPath.length !== 1) {
|
||||
bezier = fitCurve(this.currentPath, 30, null);
|
||||
} else {
|
||||
// We have only one point finally.
|
||||
|
@ -372,17 +446,15 @@ class InkEditor extends AnnotationEditor {
|
|||
* Redraw all the paths.
|
||||
*/
|
||||
#redraw() {
|
||||
this.#setStroke();
|
||||
|
||||
if (this.isEmpty()) {
|
||||
this.#updateTransform();
|
||||
return;
|
||||
}
|
||||
this.#setStroke();
|
||||
|
||||
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
|
||||
const { ctx, height, width } = this;
|
||||
const { canvas, ctx } = this;
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ctx.clearRect(0, 0, width * parentWidth, height * parentHeight);
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
this.#updateTransform();
|
||||
for (const path of this.bezierPath2D) {
|
||||
ctx.stroke(path);
|
||||
|
@ -919,6 +991,7 @@ class InkEditor extends AnnotationEditor {
|
|||
|
||||
editor.thickness = data.thickness;
|
||||
editor.color = Util.makeHexColor(...data.color);
|
||||
editor.opacity = data.opacity;
|
||||
|
||||
const [pageWidth, pageHeight] = parent.pageDimensions;
|
||||
const width = editor.width * pageWidth;
|
||||
|
@ -980,6 +1053,7 @@ class InkEditor extends AnnotationEditor {
|
|||
annotationType: AnnotationEditorType.INK,
|
||||
color,
|
||||
thickness: this.thickness,
|
||||
opacity: this.opacity,
|
||||
paths: this.#serializePaths(
|
||||
this.scaleFactor / this.parent.scaleFactor,
|
||||
this.translationX,
|
||||
|
|
|
@ -30,6 +30,18 @@ function bindEvents(obj, element, names) {
|
|||
element.addEventListener(name, obj[name].bind(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a number between 0 and 100 into an hex number between 0 and 255.
|
||||
* @param {number} opacity
|
||||
* @return {string}
|
||||
*/
|
||||
function opacityToHex(opacity) {
|
||||
return Math.round(Math.min(255, Math.max(1, 255 * opacity)))
|
||||
.toString(16)
|
||||
.padStart(2, "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to create some unique ids for the different editors.
|
||||
*/
|
||||
|
@ -1025,4 +1037,5 @@ export {
|
|||
ColorManager,
|
||||
CommandManager,
|
||||
KeyboardManager,
|
||||
opacityToHex,
|
||||
};
|
||||
|
|
|
@ -62,10 +62,12 @@ const AnnotationEditorType = {
|
|||
};
|
||||
|
||||
const AnnotationEditorParamsType = {
|
||||
FREETEXT_SIZE: 0,
|
||||
FREETEXT_COLOR: 1,
|
||||
INK_COLOR: 2,
|
||||
INK_THICKNESS: 3,
|
||||
FREETEXT_SIZE: 1,
|
||||
FREETEXT_COLOR: 2,
|
||||
FREETEXT_OPACITY: 3,
|
||||
INK_COLOR: 11,
|
||||
INK_THICKNESS: 12,
|
||||
INK_OPACITY: 13,
|
||||
};
|
||||
|
||||
// Permission flags from Table 22, Section 7.6.3.2 of the PDF specification.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue