Merge pull request #16445 from calixteman/smoothing
[Editor] Improve curve smoothing for Ink tool (bug 1789443)
This commit is contained in:
commit
7cfe00a190
@ -34,7 +34,7 @@
|
||||
// Plugins
|
||||
"import/extensions": ["error", "always", { "ignorePackages": true, }],
|
||||
"import/no-unresolved": ["error", {
|
||||
"ignore": ["pdfjs", "pdfjs-lib", "pdfjs-web", "pdfjs-fitCurve", "web"]
|
||||
"ignore": ["pdfjs", "pdfjs-lib", "pdfjs-web", "web"]
|
||||
}],
|
||||
"mozilla/avoid-removeChild": "error",
|
||||
"mozilla/use-includes-instead-of-indexOf": "error",
|
||||
|
135
gulpfile.js
135
gulpfile.js
@ -232,7 +232,6 @@ function createWebpackConfig(
|
||||
pdfjs: "src",
|
||||
"pdfjs-web": "web",
|
||||
"pdfjs-lib": "web/pdfjs",
|
||||
"pdfjs-fitCurve": "src/display/editor/fit_curve",
|
||||
};
|
||||
const viewerAlias = {
|
||||
"web-annotation_editor_params": "web/annotation_editor_params.js",
|
||||
@ -571,26 +570,6 @@ function createImageDecodersBundle(defines) {
|
||||
.pipe(replaceJSRootName(imageDecodersAMDName, "pdfjsImageDecoders"));
|
||||
}
|
||||
|
||||
function createFitCurveBundle(defines) {
|
||||
const fitCurveOutputName = "fit_curve.js";
|
||||
|
||||
const fitCurveFileConfig = createWebpackConfig(
|
||||
defines,
|
||||
{
|
||||
filename: fitCurveOutputName,
|
||||
library: {
|
||||
type: "module",
|
||||
},
|
||||
},
|
||||
{
|
||||
disableVersionInfo: true,
|
||||
}
|
||||
);
|
||||
return gulp
|
||||
.src("src/display/editor/fit_curve.js")
|
||||
.pipe(webpack2Stream(fitCurveFileConfig));
|
||||
}
|
||||
|
||||
function createCMapBundle() {
|
||||
return gulp.src(["external/bcmaps/*.bcmap", "external/bcmaps/LICENSE"], {
|
||||
base: "external/bcmaps",
|
||||
@ -1551,7 +1530,6 @@ function buildLibHelper(bundleDefines, inputStream, outputDir) {
|
||||
defines: bundleDefines,
|
||||
map: {
|
||||
"pdfjs-lib": "../pdf",
|
||||
"pdfjs-fitCurve": "./fit_curve",
|
||||
},
|
||||
};
|
||||
const licenseHeaderLibre = fs
|
||||
@ -1690,90 +1668,54 @@ function setTestEnv(done) {
|
||||
done();
|
||||
}
|
||||
|
||||
gulp.task("dev-fitCurve", function createDevFitCurve() {
|
||||
console.log();
|
||||
console.log("### Building development fitCurve");
|
||||
|
||||
const defines = builder.merge(DEFINES, { GENERIC: true, TESTING: true });
|
||||
const fitCurveDir = BUILD_DIR + "dev-fitCurve/";
|
||||
|
||||
rimraf.sync(fitCurveDir);
|
||||
|
||||
return createFitCurveBundle(defines).pipe(gulp.dest(fitCurveDir));
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
"test",
|
||||
gulp.series(
|
||||
setTestEnv,
|
||||
"generic",
|
||||
"components",
|
||||
"dev-fitCurve",
|
||||
function runTest() {
|
||||
return streamqueue(
|
||||
{ objectMode: true },
|
||||
createTestSource("unit"),
|
||||
createTestSource("browser"),
|
||||
createTestSource("integration")
|
||||
);
|
||||
}
|
||||
)
|
||||
gulp.series(setTestEnv, "generic", "components", function runTest() {
|
||||
return streamqueue(
|
||||
{ objectMode: true },
|
||||
createTestSource("unit"),
|
||||
createTestSource("browser"),
|
||||
createTestSource("integration")
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"bottest",
|
||||
gulp.series(
|
||||
setTestEnv,
|
||||
"generic",
|
||||
"components",
|
||||
"dev-fitCurve",
|
||||
function runBotTest() {
|
||||
return streamqueue(
|
||||
{ objectMode: true },
|
||||
createTestSource("unit", { bot: true }),
|
||||
createTestSource("font", { bot: true }),
|
||||
createTestSource("browser", { bot: true }),
|
||||
createTestSource("integration")
|
||||
);
|
||||
}
|
||||
)
|
||||
gulp.series(setTestEnv, "generic", "components", function runBotTest() {
|
||||
return streamqueue(
|
||||
{ objectMode: true },
|
||||
createTestSource("unit", { bot: true }),
|
||||
createTestSource("font", { bot: true }),
|
||||
createTestSource("browser", { bot: true }),
|
||||
createTestSource("integration")
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"xfatest",
|
||||
gulp.series(
|
||||
setTestEnv,
|
||||
"generic",
|
||||
"components",
|
||||
"dev-fitCurve",
|
||||
function runXfaTest() {
|
||||
return streamqueue(
|
||||
{ objectMode: true },
|
||||
createTestSource("unit"),
|
||||
createTestSource("browser", { xfaOnly: true }),
|
||||
createTestSource("integration")
|
||||
);
|
||||
}
|
||||
)
|
||||
gulp.series(setTestEnv, "generic", "components", function runXfaTest() {
|
||||
return streamqueue(
|
||||
{ objectMode: true },
|
||||
createTestSource("unit"),
|
||||
createTestSource("browser", { xfaOnly: true }),
|
||||
createTestSource("integration")
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"botxfatest",
|
||||
gulp.series(
|
||||
setTestEnv,
|
||||
"generic",
|
||||
"components",
|
||||
"dev-fitCurve",
|
||||
function runBotXfaTest() {
|
||||
return streamqueue(
|
||||
{ objectMode: true },
|
||||
createTestSource("unit", { bot: true }),
|
||||
createTestSource("font", { bot: true }),
|
||||
createTestSource("browser", { bot: true, xfaOnly: true }),
|
||||
createTestSource("integration")
|
||||
);
|
||||
}
|
||||
)
|
||||
gulp.series(setTestEnv, "generic", "components", function runBotXfaTest() {
|
||||
return streamqueue(
|
||||
{ objectMode: true },
|
||||
createTestSource("unit", { bot: true }),
|
||||
createTestSource("font", { bot: true }),
|
||||
createTestSource("browser", { bot: true, xfaOnly: true }),
|
||||
createTestSource("integration")
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
@ -1800,7 +1742,7 @@ gulp.task(
|
||||
|
||||
gulp.task(
|
||||
"unittest",
|
||||
gulp.series(setTestEnv, "generic", "dev-fitCurve", function runUnitTest() {
|
||||
gulp.series(setTestEnv, "generic", function runUnitTest() {
|
||||
return createTestSource("unit");
|
||||
})
|
||||
);
|
||||
@ -2028,13 +1970,6 @@ gulp.task(
|
||||
gulp.task(
|
||||
"server",
|
||||
gulp.parallel(
|
||||
function watchDevFitCurve() {
|
||||
gulp.watch(
|
||||
["src/display/editor/*"],
|
||||
{ ignoreInitial: false },
|
||||
gulp.series("dev-fitCurve")
|
||||
);
|
||||
},
|
||||
function watchDevSandbox() {
|
||||
gulp.watch(
|
||||
[
|
||||
|
13
package-lock.json
generated
13
package-lock.json
generated
@ -32,7 +32,6 @@
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-sort-exports": "^0.8.0",
|
||||
"eslint-plugin-unicorn": "^47.0.0",
|
||||
"fit-curve": "^0.2.0",
|
||||
"globals": "^13.20.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-postcss": "^9.0.1",
|
||||
@ -7065,12 +7064,6 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/fit-curve": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fit-curve/-/fit-curve-0.2.0.tgz",
|
||||
"integrity": "sha512-op7ofeL13getbqL5J5ACeNxTlLWzusn4/jjEjSVA1sS7PfXWumdtNITvLuTjSobis+jZzMil2rsJ3Vhw7OxQyQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/flagged-respawn": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz",
|
||||
@ -12732,6 +12725,7 @@
|
||||
},
|
||||
"node_modules/npm/node_modules/lodash._baseindexof": {
|
||||
"version": "3.1.0",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@ -12747,16 +12741,19 @@
|
||||
},
|
||||
"node_modules/npm/node_modules/lodash._bindcallback": {
|
||||
"version": "3.0.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/npm/node_modules/lodash._cacheindexof": {
|
||||
"version": "3.0.2",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/npm/node_modules/lodash._createcache": {
|
||||
"version": "3.1.2",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -12771,6 +12768,7 @@
|
||||
},
|
||||
"node_modules/npm/node_modules/lodash._getnative": {
|
||||
"version": "3.9.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@ -12788,6 +12786,7 @@
|
||||
},
|
||||
"node_modules/npm/node_modules/lodash.restparam": {
|
||||
"version": "3.6.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@ -25,7 +25,6 @@
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-sort-exports": "^0.8.0",
|
||||
"eslint-plugin-unicorn": "^47.0.0",
|
||||
"fit-curve": "^0.2.0",
|
||||
"globals": "^13.20.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-postcss": "^9.0.1",
|
||||
|
@ -1,20 +0,0 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
const fitCurve = require(PDFJSDev.test("LIB")
|
||||
? "fit-curve"
|
||||
: "fit-curve/src/fit-curve.js");
|
||||
|
||||
export { fitCurve };
|
@ -19,7 +19,6 @@ import {
|
||||
Util,
|
||||
} 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:
|
||||
@ -37,6 +36,8 @@ class InkEditor extends AnnotationEditor {
|
||||
|
||||
#baseWidth = 0;
|
||||
|
||||
#boundCanvasContextMenu = this.canvasContextMenu.bind(this);
|
||||
|
||||
#boundCanvasPointermove = this.canvasPointermove.bind(this);
|
||||
|
||||
#boundCanvasPointerleave = this.canvasPointerleave.bind(this);
|
||||
@ -45,11 +46,13 @@ class InkEditor extends AnnotationEditor {
|
||||
|
||||
#boundCanvasPointerdown = this.canvasPointerdown.bind(this);
|
||||
|
||||
#currentPath2D = new Path2D();
|
||||
|
||||
#disableEditing = false;
|
||||
|
||||
#isCanvasInitialized = false;
|
||||
#hasSomethingToDraw = false;
|
||||
|
||||
#lastPoint = null;
|
||||
#isCanvasInitialized = false;
|
||||
|
||||
#observer = null;
|
||||
|
||||
@ -76,6 +79,7 @@ class InkEditor extends AnnotationEditor {
|
||||
this.opacity = params.opacity || null;
|
||||
this.paths = [];
|
||||
this.bezierPath2D = [];
|
||||
this.allRawPaths = [];
|
||||
this.currentPath = [];
|
||||
this.scaleFactor = 1;
|
||||
this.translationX = this.translationY = 0;
|
||||
@ -294,7 +298,6 @@ class InkEditor extends AnnotationEditor {
|
||||
super.enableEditMode();
|
||||
this.div.draggable = false;
|
||||
this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown);
|
||||
this.canvas.addEventListener("pointerup", this.#boundCanvasPointerup);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
@ -311,7 +314,6 @@ class InkEditor extends AnnotationEditor {
|
||||
"pointerdown",
|
||||
this.#boundCanvasPointerdown
|
||||
);
|
||||
this.canvas.removeEventListener("pointerup", this.#boundCanvasPointerup);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
@ -362,6 +364,15 @@ class InkEditor extends AnnotationEditor {
|
||||
* @param {number} y
|
||||
*/
|
||||
#startDrawing(x, y) {
|
||||
this.canvas.addEventListener("contextmenu", this.#boundCanvasContextMenu);
|
||||
this.canvas.addEventListener("pointerleave", this.#boundCanvasPointerleave);
|
||||
this.canvas.addEventListener("pointermove", this.#boundCanvasPointermove);
|
||||
this.canvas.addEventListener("pointerup", this.#boundCanvasPointerup);
|
||||
this.canvas.removeEventListener(
|
||||
"pointerdown",
|
||||
this.#boundCanvasPointerdown
|
||||
);
|
||||
|
||||
this.isEditing = true;
|
||||
if (!this.#isCanvasInitialized) {
|
||||
this.#isCanvasInitialized = true;
|
||||
@ -372,30 +383,14 @@ class InkEditor extends AnnotationEditor {
|
||||
this.opacity ??= InkEditor._defaultOpacity;
|
||||
}
|
||||
this.currentPath.push([x, y]);
|
||||
this.#lastPoint = null;
|
||||
this.#hasSomethingToDraw = false;
|
||||
this.#setStroke();
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(x, y);
|
||||
|
||||
this.#requestFrameCallback = () => {
|
||||
if (!this.#requestFrameCallback) {
|
||||
return;
|
||||
this.#drawPoints();
|
||||
if (this.#requestFrameCallback) {
|
||||
window.requestAnimationFrame(this.#requestFrameCallback);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@ -407,11 +402,40 @@ class InkEditor extends AnnotationEditor {
|
||||
*/
|
||||
#draw(x, y) {
|
||||
const [lastX, lastY] = this.currentPath.at(-1);
|
||||
if (x === lastX && y === lastY) {
|
||||
if (this.currentPath.length > 1 && x === lastX && y === lastY) {
|
||||
return;
|
||||
}
|
||||
this.currentPath.push([x, y]);
|
||||
this.#lastPoint = [x, y];
|
||||
const currentPath = this.currentPath;
|
||||
let path2D = this.#currentPath2D;
|
||||
currentPath.push([x, y]);
|
||||
this.#hasSomethingToDraw = true;
|
||||
|
||||
if (currentPath.length <= 2) {
|
||||
path2D.moveTo(...currentPath[0]);
|
||||
path2D.lineTo(x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPath.length === 3) {
|
||||
this.#currentPath2D = path2D = new Path2D();
|
||||
path2D.moveTo(...currentPath[0]);
|
||||
}
|
||||
|
||||
this.#makeBezierCurve(
|
||||
path2D,
|
||||
...currentPath.at(-3),
|
||||
...currentPath.at(-2),
|
||||
x,
|
||||
y
|
||||
);
|
||||
}
|
||||
|
||||
#endPath() {
|
||||
if (this.currentPath.length === 0) {
|
||||
return;
|
||||
}
|
||||
const lastPoint = this.currentPath.at(-1);
|
||||
this.#currentPath2D.lineTo(...lastPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -420,38 +444,39 @@ 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);
|
||||
|
||||
const [lastX, lastY] = this.currentPath.at(-1);
|
||||
if (x !== lastX || y !== lastY) {
|
||||
this.currentPath.push([x, y]);
|
||||
}
|
||||
this.#draw(x, y);
|
||||
this.#endPath();
|
||||
|
||||
// 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 !== 1) {
|
||||
bezier = fitCurve(this.currentPath, 30, null);
|
||||
bezier = this.#generateBezierPoints();
|
||||
} else {
|
||||
// We have only one point finally.
|
||||
const xy = [x, y];
|
||||
bezier = [[xy, xy.slice(), xy.slice(), xy]];
|
||||
}
|
||||
const path2D = InkEditor.#buildPath2D(bezier);
|
||||
this.currentPath.length = 0;
|
||||
const path2D = this.#currentPath2D;
|
||||
const currentPath = this.currentPath;
|
||||
this.currentPath = [];
|
||||
this.#currentPath2D = new Path2D();
|
||||
|
||||
const cmd = () => {
|
||||
this.allRawPaths.push(currentPath);
|
||||
this.paths.push(bezier);
|
||||
this.bezierPath2D.push(path2D);
|
||||
this.rebuild();
|
||||
};
|
||||
|
||||
const undo = () => {
|
||||
this.allRawPaths.pop();
|
||||
this.paths.pop();
|
||||
this.bezierPath2D.pop();
|
||||
if (this.paths.length === 0) {
|
||||
@ -468,6 +493,95 @@ class InkEditor extends AnnotationEditor {
|
||||
this.addCommands({ cmd, undo, mustExec: true });
|
||||
}
|
||||
|
||||
#drawPoints() {
|
||||
if (!this.#hasSomethingToDraw) {
|
||||
return;
|
||||
}
|
||||
this.#hasSomethingToDraw = false;
|
||||
|
||||
const thickness = Math.ceil(this.thickness * this.parentScale);
|
||||
const lastPoints = this.currentPath.slice(-3);
|
||||
const x = lastPoints.map(xy => xy[0]);
|
||||
const y = lastPoints.map(xy => xy[1]);
|
||||
const xMin = Math.min(...x) - thickness;
|
||||
const xMax = Math.max(...x) + thickness;
|
||||
const yMin = Math.min(...y) - thickness;
|
||||
const yMax = Math.max(...y) + thickness;
|
||||
|
||||
const { ctx } = this;
|
||||
ctx.save();
|
||||
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||
// In Chrome, the clip() method doesn't work as expected.
|
||||
ctx.clearRect(xMin, yMin, xMax - xMin, yMax - yMin);
|
||||
ctx.beginPath();
|
||||
ctx.rect(xMin, yMin, xMax - xMin, yMax - yMin);
|
||||
ctx.clip();
|
||||
} else {
|
||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
for (const path of this.bezierPath2D) {
|
||||
ctx.stroke(path);
|
||||
}
|
||||
ctx.stroke(this.#currentPath2D);
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
#makeBezierCurve(path2D, x0, y0, x1, y1, x2, y2) {
|
||||
const prevX = (x0 + x1) / 2;
|
||||
const prevY = (y0 + y1) / 2;
|
||||
const x3 = (x1 + x2) / 2;
|
||||
const y3 = (y1 + y2) / 2;
|
||||
|
||||
path2D.bezierCurveTo(
|
||||
prevX + (2 * (x1 - prevX)) / 3,
|
||||
prevY + (2 * (y1 - prevY)) / 3,
|
||||
x3 + (2 * (x1 - x3)) / 3,
|
||||
y3 + (2 * (y1 - y3)) / 3,
|
||||
x3,
|
||||
y3
|
||||
);
|
||||
}
|
||||
|
||||
#generateBezierPoints() {
|
||||
const path = this.currentPath;
|
||||
if (path.length <= 2) {
|
||||
return [[path[0], path[0], path.at(-1), path.at(-1)]];
|
||||
}
|
||||
|
||||
const bezierPoints = [];
|
||||
let i;
|
||||
let [x0, y0] = path[0];
|
||||
for (i = 1; i < path.length - 2; i++) {
|
||||
const [x1, y1] = path[i];
|
||||
const [x2, y2] = path[i + 1];
|
||||
const x3 = (x1 + x2) / 2;
|
||||
const y3 = (y1 + y2) / 2;
|
||||
|
||||
// The quadratic is: [[x0, y0], [x1, y1], [x3, y3]].
|
||||
// Convert the quadratic to a cubic
|
||||
// (see https://fontforge.org/docs/techref/bezier.html#converting-truetype-to-postscript)
|
||||
const control1 = [x0 + (2 * (x1 - x0)) / 3, y0 + (2 * (y1 - y0)) / 3];
|
||||
const control2 = [x3 + (2 * (x1 - x3)) / 3, y3 + (2 * (y1 - y3)) / 3];
|
||||
|
||||
bezierPoints.push([[x0, y0], control1, control2, [x3, y3]]);
|
||||
|
||||
[x0, y0] = [x3, y3];
|
||||
}
|
||||
|
||||
const [x1, y1] = path[i];
|
||||
const [x2, y2] = path[i + 1];
|
||||
|
||||
// The quadratic is: [[x0, y0], [x1, y1], [x2, y2]].
|
||||
const control1 = [x0 + (2 * (x1 - x0)) / 3, y0 + (2 * (y1 - y0)) / 3];
|
||||
const control2 = [x2 + (2 * (x1 - x2)) / 3, y2 + (2 * (y1 - y2)) / 3];
|
||||
|
||||
bezierPoints.push([[x0, y0], control1, control2, [x2, y2]]);
|
||||
return bezierPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redraw all the paths.
|
||||
*/
|
||||
@ -482,6 +596,7 @@ class InkEditor extends AnnotationEditor {
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
this.#updateTransform();
|
||||
|
||||
for (const path of this.bezierPath2D) {
|
||||
ctx.stroke(path);
|
||||
}
|
||||
@ -537,24 +652,29 @@ class InkEditor extends AnnotationEditor {
|
||||
// Since it's the last child, there's no need to give it a higher z-index.
|
||||
this.setInForeground();
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (event.type !== "mouse") {
|
||||
this.div.focus();
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
|
||||
this.canvas.addEventListener("pointerleave", this.#boundCanvasPointerleave);
|
||||
this.canvas.addEventListener("pointermove", this.#boundCanvasPointermove);
|
||||
|
||||
this.#startDrawing(event.offsetX, event.offsetY);
|
||||
}
|
||||
|
||||
/**
|
||||
* oncontextmenu callback for the canvas we're drawing on.
|
||||
* @param {PointerEvent} event
|
||||
*/
|
||||
canvasContextMenu(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* onpointermove callback for the canvas we're drawing on.
|
||||
* @param {PointerEvent} event
|
||||
*/
|
||||
canvasPointermove(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.#draw(event.offsetX, event.offsetY);
|
||||
}
|
||||
|
||||
@ -563,17 +683,8 @@ class InkEditor extends AnnotationEditor {
|
||||
* @param {PointerEvent} event
|
||||
*/
|
||||
canvasPointerup(event) {
|
||||
if (event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
if (this.isInEditMode() && this.currentPath.length !== 0) {
|
||||
event.stopPropagation();
|
||||
this.#endDrawing(event);
|
||||
|
||||
// Since the ink editor covers all of the page and we want to be able
|
||||
// to select another editor, we just put this one in the background.
|
||||
this.setInBackground();
|
||||
}
|
||||
event.preventDefault();
|
||||
this.#endDrawing(event);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -582,7 +693,6 @@ class InkEditor extends AnnotationEditor {
|
||||
*/
|
||||
canvasPointerleave(event) {
|
||||
this.#endDrawing(event);
|
||||
this.setInBackground();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -590,8 +700,6 @@ class InkEditor extends AnnotationEditor {
|
||||
* @param {PointerEvent} event
|
||||
*/
|
||||
#endDrawing(event) {
|
||||
this.#stopDrawing(event.offsetX, event.offsetY);
|
||||
|
||||
this.canvas.removeEventListener(
|
||||
"pointerleave",
|
||||
this.#boundCanvasPointerleave
|
||||
@ -600,8 +708,25 @@ class InkEditor extends AnnotationEditor {
|
||||
"pointermove",
|
||||
this.#boundCanvasPointermove
|
||||
);
|
||||
this.canvas.removeEventListener("pointerup", this.#boundCanvasPointerup);
|
||||
this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown);
|
||||
|
||||
// Slight delay to avoid the context menu to appear (it can happen on a long
|
||||
// tap with a pen).
|
||||
setTimeout(() => {
|
||||
this.canvas.removeEventListener(
|
||||
"contextmenu",
|
||||
this.#boundCanvasContextMenu
|
||||
);
|
||||
}, 10);
|
||||
|
||||
this.#stopDrawing(event.offsetX, event.offsetY);
|
||||
|
||||
this.addToAnnotationStorage();
|
||||
|
||||
// Since the ink editor covers all of the page and we want to be able
|
||||
// to select another editor, we just put this one in the background.
|
||||
this.setInBackground();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -762,7 +887,7 @@ class InkEditor extends AnnotationEditor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the output of fitCurve in some Path2D.
|
||||
* Convert into a Path2D.
|
||||
* @param {Arra<Array<number>} bezier
|
||||
* @returns {Path2D}
|
||||
*/
|
||||
@ -1099,4 +1224,4 @@ class InkEditor extends AnnotationEditor {
|
||||
}
|
||||
}
|
||||
|
||||
export { fitCurve, InkEditor };
|
||||
export { InkEditor };
|
||||
|
@ -14,7 +14,6 @@
|
||||
*/
|
||||
|
||||
import { CommandManager } from "../../src/display/editor/tools.js";
|
||||
import { fitCurve } from "../../src/display/editor/ink.js";
|
||||
|
||||
describe("editor", function () {
|
||||
describe("Command Manager", function () {
|
||||
@ -91,29 +90,4 @@ describe("editor", function () {
|
||||
manager.add({ ...makeDoUndo(5), mustExec: true });
|
||||
expect(x).toEqual(11);
|
||||
});
|
||||
|
||||
describe("fitCurve", function () {
|
||||
it("should return a function", function () {
|
||||
expect(typeof fitCurve).toEqual("function");
|
||||
});
|
||||
|
||||
it("should compute an Array of bezier curves", function () {
|
||||
const bezier = fitCurve(
|
||||
[
|
||||
[1, 2],
|
||||
[4, 5],
|
||||
],
|
||||
30,
|
||||
null
|
||||
);
|
||||
expect(bezier).toEqual([
|
||||
[
|
||||
[1, 2],
|
||||
[2, 3],
|
||||
[3, 4],
|
||||
[4, 5],
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -15,7 +15,6 @@
|
||||
"pdfjs/": "../../src/",
|
||||
"pdfjs-lib": "../../src/pdf.js",
|
||||
"pdfjs-web/": "../../web/",
|
||||
"pdfjs-fitCurve": "../../build/dev-fitCurve/fit_curve.js",
|
||||
"pdfjs-test/": "../",
|
||||
|
||||
"web-annotation_editor_params": "../../web/annotation_editor_params.js",
|
||||
|
@ -48,7 +48,6 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
"pdfjs/": "../src/",
|
||||
"pdfjs-lib": "../src/pdf.js",
|
||||
"pdfjs-web/": "./",
|
||||
"pdfjs-fitCurve": "../build/dev-fitCurve/fit_curve.js",
|
||||
|
||||
"web-annotation_editor_params": "./stubs-geckoview.js",
|
||||
"web-com": "./genericcom.js",
|
||||
|
@ -59,7 +59,6 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
"pdfjs/": "../src/",
|
||||
"pdfjs-lib": "../src/pdf.js",
|
||||
"pdfjs-web/": "./",
|
||||
"pdfjs-fitCurve": "../build/dev-fitCurve/fit_curve.js",
|
||||
|
||||
"web-annotation_editor_params": "./annotation_editor_params.js",
|
||||
"web-com": "./genericcom.js",
|
||||
|
Loading…
Reference in New Issue
Block a user