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
|
// Plugins
|
||||||
"import/extensions": ["error", "always", { "ignorePackages": true, }],
|
"import/extensions": ["error", "always", { "ignorePackages": true, }],
|
||||||
"import/no-unresolved": ["error", {
|
"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/avoid-removeChild": "error",
|
||||||
"mozilla/use-includes-instead-of-indexOf": "error",
|
"mozilla/use-includes-instead-of-indexOf": "error",
|
||||||
|
135
gulpfile.js
135
gulpfile.js
@ -232,7 +232,6 @@ function createWebpackConfig(
|
|||||||
pdfjs: "src",
|
pdfjs: "src",
|
||||||
"pdfjs-web": "web",
|
"pdfjs-web": "web",
|
||||||
"pdfjs-lib": "web/pdfjs",
|
"pdfjs-lib": "web/pdfjs",
|
||||||
"pdfjs-fitCurve": "src/display/editor/fit_curve",
|
|
||||||
};
|
};
|
||||||
const viewerAlias = {
|
const viewerAlias = {
|
||||||
"web-annotation_editor_params": "web/annotation_editor_params.js",
|
"web-annotation_editor_params": "web/annotation_editor_params.js",
|
||||||
@ -571,26 +570,6 @@ function createImageDecodersBundle(defines) {
|
|||||||
.pipe(replaceJSRootName(imageDecodersAMDName, "pdfjsImageDecoders"));
|
.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() {
|
function createCMapBundle() {
|
||||||
return gulp.src(["external/bcmaps/*.bcmap", "external/bcmaps/LICENSE"], {
|
return gulp.src(["external/bcmaps/*.bcmap", "external/bcmaps/LICENSE"], {
|
||||||
base: "external/bcmaps",
|
base: "external/bcmaps",
|
||||||
@ -1551,7 +1530,6 @@ function buildLibHelper(bundleDefines, inputStream, outputDir) {
|
|||||||
defines: bundleDefines,
|
defines: bundleDefines,
|
||||||
map: {
|
map: {
|
||||||
"pdfjs-lib": "../pdf",
|
"pdfjs-lib": "../pdf",
|
||||||
"pdfjs-fitCurve": "./fit_curve",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const licenseHeaderLibre = fs
|
const licenseHeaderLibre = fs
|
||||||
@ -1690,90 +1668,54 @@ function setTestEnv(done) {
|
|||||||
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(
|
gulp.task(
|
||||||
"test",
|
"test",
|
||||||
gulp.series(
|
gulp.series(setTestEnv, "generic", "components", function runTest() {
|
||||||
setTestEnv,
|
return streamqueue(
|
||||||
"generic",
|
{ objectMode: true },
|
||||||
"components",
|
createTestSource("unit"),
|
||||||
"dev-fitCurve",
|
createTestSource("browser"),
|
||||||
function runTest() {
|
createTestSource("integration")
|
||||||
return streamqueue(
|
);
|
||||||
{ objectMode: true },
|
})
|
||||||
createTestSource("unit"),
|
|
||||||
createTestSource("browser"),
|
|
||||||
createTestSource("integration")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"bottest",
|
"bottest",
|
||||||
gulp.series(
|
gulp.series(setTestEnv, "generic", "components", function runBotTest() {
|
||||||
setTestEnv,
|
return streamqueue(
|
||||||
"generic",
|
{ objectMode: true },
|
||||||
"components",
|
createTestSource("unit", { bot: true }),
|
||||||
"dev-fitCurve",
|
createTestSource("font", { bot: true }),
|
||||||
function runBotTest() {
|
createTestSource("browser", { bot: true }),
|
||||||
return streamqueue(
|
createTestSource("integration")
|
||||||
{ objectMode: true },
|
);
|
||||||
createTestSource("unit", { bot: true }),
|
})
|
||||||
createTestSource("font", { bot: true }),
|
|
||||||
createTestSource("browser", { bot: true }),
|
|
||||||
createTestSource("integration")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"xfatest",
|
"xfatest",
|
||||||
gulp.series(
|
gulp.series(setTestEnv, "generic", "components", function runXfaTest() {
|
||||||
setTestEnv,
|
return streamqueue(
|
||||||
"generic",
|
{ objectMode: true },
|
||||||
"components",
|
createTestSource("unit"),
|
||||||
"dev-fitCurve",
|
createTestSource("browser", { xfaOnly: true }),
|
||||||
function runXfaTest() {
|
createTestSource("integration")
|
||||||
return streamqueue(
|
);
|
||||||
{ objectMode: true },
|
})
|
||||||
createTestSource("unit"),
|
|
||||||
createTestSource("browser", { xfaOnly: true }),
|
|
||||||
createTestSource("integration")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"botxfatest",
|
"botxfatest",
|
||||||
gulp.series(
|
gulp.series(setTestEnv, "generic", "components", function runBotXfaTest() {
|
||||||
setTestEnv,
|
return streamqueue(
|
||||||
"generic",
|
{ objectMode: true },
|
||||||
"components",
|
createTestSource("unit", { bot: true }),
|
||||||
"dev-fitCurve",
|
createTestSource("font", { bot: true }),
|
||||||
function runBotXfaTest() {
|
createTestSource("browser", { bot: true, xfaOnly: true }),
|
||||||
return streamqueue(
|
createTestSource("integration")
|
||||||
{ objectMode: true },
|
);
|
||||||
createTestSource("unit", { bot: true }),
|
})
|
||||||
createTestSource("font", { bot: true }),
|
|
||||||
createTestSource("browser", { bot: true, xfaOnly: true }),
|
|
||||||
createTestSource("integration")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
@ -1800,7 +1742,7 @@ gulp.task(
|
|||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"unittest",
|
"unittest",
|
||||||
gulp.series(setTestEnv, "generic", "dev-fitCurve", function runUnitTest() {
|
gulp.series(setTestEnv, "generic", function runUnitTest() {
|
||||||
return createTestSource("unit");
|
return createTestSource("unit");
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -2028,13 +1970,6 @@ gulp.task(
|
|||||||
gulp.task(
|
gulp.task(
|
||||||
"server",
|
"server",
|
||||||
gulp.parallel(
|
gulp.parallel(
|
||||||
function watchDevFitCurve() {
|
|
||||||
gulp.watch(
|
|
||||||
["src/display/editor/*"],
|
|
||||||
{ ignoreInitial: false },
|
|
||||||
gulp.series("dev-fitCurve")
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function watchDevSandbox() {
|
function watchDevSandbox() {
|
||||||
gulp.watch(
|
gulp.watch(
|
||||||
[
|
[
|
||||||
|
13
package-lock.json
generated
13
package-lock.json
generated
@ -32,7 +32,6 @@
|
|||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-sort-exports": "^0.8.0",
|
"eslint-plugin-sort-exports": "^0.8.0",
|
||||||
"eslint-plugin-unicorn": "^47.0.0",
|
"eslint-plugin-unicorn": "^47.0.0",
|
||||||
"fit-curve": "^0.2.0",
|
|
||||||
"globals": "^13.20.0",
|
"globals": "^13.20.0",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-postcss": "^9.0.1",
|
"gulp-postcss": "^9.0.1",
|
||||||
@ -7065,12 +7064,6 @@
|
|||||||
"node": ">= 0.10"
|
"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": {
|
"node_modules/flagged-respawn": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz",
|
||||||
@ -12732,6 +12725,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/npm/node_modules/lodash._baseindexof": {
|
"node_modules/npm/node_modules/lodash._baseindexof": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@ -12747,16 +12741,19 @@
|
|||||||
},
|
},
|
||||||
"node_modules/npm/node_modules/lodash._bindcallback": {
|
"node_modules/npm/node_modules/lodash._bindcallback": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/npm/node_modules/lodash._cacheindexof": {
|
"node_modules/npm/node_modules/lodash._cacheindexof": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/npm/node_modules/lodash._createcache": {
|
"node_modules/npm/node_modules/lodash._createcache": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -12771,6 +12768,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/npm/node_modules/lodash._getnative": {
|
"node_modules/npm/node_modules/lodash._getnative": {
|
||||||
"version": "3.9.1",
|
"version": "3.9.1",
|
||||||
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@ -12788,6 +12786,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/npm/node_modules/lodash.restparam": {
|
"node_modules/npm/node_modules/lodash.restparam": {
|
||||||
"version": "3.6.1",
|
"version": "3.6.1",
|
||||||
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-sort-exports": "^0.8.0",
|
"eslint-plugin-sort-exports": "^0.8.0",
|
||||||
"eslint-plugin-unicorn": "^47.0.0",
|
"eslint-plugin-unicorn": "^47.0.0",
|
||||||
"fit-curve": "^0.2.0",
|
|
||||||
"globals": "^13.20.0",
|
"globals": "^13.20.0",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-postcss": "^9.0.1",
|
"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,
|
Util,
|
||||||
} from "../../shared/util.js";
|
} from "../../shared/util.js";
|
||||||
import { AnnotationEditor } from "./editor.js";
|
import { AnnotationEditor } from "./editor.js";
|
||||||
import { fitCurve } from "pdfjs-fitCurve";
|
|
||||||
import { opacityToHex } from "./tools.js";
|
import { opacityToHex } from "./tools.js";
|
||||||
|
|
||||||
// The dimensions of the resizer is 15x15:
|
// The dimensions of the resizer is 15x15:
|
||||||
@ -37,6 +36,8 @@ class InkEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
#baseWidth = 0;
|
#baseWidth = 0;
|
||||||
|
|
||||||
|
#boundCanvasContextMenu = this.canvasContextMenu.bind(this);
|
||||||
|
|
||||||
#boundCanvasPointermove = this.canvasPointermove.bind(this);
|
#boundCanvasPointermove = this.canvasPointermove.bind(this);
|
||||||
|
|
||||||
#boundCanvasPointerleave = this.canvasPointerleave.bind(this);
|
#boundCanvasPointerleave = this.canvasPointerleave.bind(this);
|
||||||
@ -45,11 +46,13 @@ class InkEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
#boundCanvasPointerdown = this.canvasPointerdown.bind(this);
|
#boundCanvasPointerdown = this.canvasPointerdown.bind(this);
|
||||||
|
|
||||||
|
#currentPath2D = new Path2D();
|
||||||
|
|
||||||
#disableEditing = false;
|
#disableEditing = false;
|
||||||
|
|
||||||
#isCanvasInitialized = false;
|
#hasSomethingToDraw = false;
|
||||||
|
|
||||||
#lastPoint = null;
|
#isCanvasInitialized = false;
|
||||||
|
|
||||||
#observer = null;
|
#observer = null;
|
||||||
|
|
||||||
@ -76,6 +79,7 @@ class InkEditor extends AnnotationEditor {
|
|||||||
this.opacity = params.opacity || null;
|
this.opacity = params.opacity || null;
|
||||||
this.paths = [];
|
this.paths = [];
|
||||||
this.bezierPath2D = [];
|
this.bezierPath2D = [];
|
||||||
|
this.allRawPaths = [];
|
||||||
this.currentPath = [];
|
this.currentPath = [];
|
||||||
this.scaleFactor = 1;
|
this.scaleFactor = 1;
|
||||||
this.translationX = this.translationY = 0;
|
this.translationX = this.translationY = 0;
|
||||||
@ -294,7 +298,6 @@ class InkEditor extends AnnotationEditor {
|
|||||||
super.enableEditMode();
|
super.enableEditMode();
|
||||||
this.div.draggable = false;
|
this.div.draggable = false;
|
||||||
this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown);
|
this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown);
|
||||||
this.canvas.addEventListener("pointerup", this.#boundCanvasPointerup);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
@ -311,7 +314,6 @@ class InkEditor extends AnnotationEditor {
|
|||||||
"pointerdown",
|
"pointerdown",
|
||||||
this.#boundCanvasPointerdown
|
this.#boundCanvasPointerdown
|
||||||
);
|
);
|
||||||
this.canvas.removeEventListener("pointerup", this.#boundCanvasPointerup);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
@ -362,6 +364,15 @@ class InkEditor extends AnnotationEditor {
|
|||||||
* @param {number} y
|
* @param {number} y
|
||||||
*/
|
*/
|
||||||
#startDrawing(x, 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;
|
this.isEditing = true;
|
||||||
if (!this.#isCanvasInitialized) {
|
if (!this.#isCanvasInitialized) {
|
||||||
this.#isCanvasInitialized = true;
|
this.#isCanvasInitialized = true;
|
||||||
@ -372,30 +383,14 @@ class InkEditor extends AnnotationEditor {
|
|||||||
this.opacity ??= InkEditor._defaultOpacity;
|
this.opacity ??= InkEditor._defaultOpacity;
|
||||||
}
|
}
|
||||||
this.currentPath.push([x, y]);
|
this.currentPath.push([x, y]);
|
||||||
this.#lastPoint = null;
|
this.#hasSomethingToDraw = false;
|
||||||
this.#setStroke();
|
this.#setStroke();
|
||||||
this.ctx.beginPath();
|
|
||||||
this.ctx.moveTo(x, y);
|
|
||||||
|
|
||||||
this.#requestFrameCallback = () => {
|
this.#requestFrameCallback = () => {
|
||||||
if (!this.#requestFrameCallback) {
|
this.#drawPoints();
|
||||||
return;
|
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);
|
window.requestAnimationFrame(this.#requestFrameCallback);
|
||||||
}
|
}
|
||||||
@ -407,11 +402,40 @@ class InkEditor extends AnnotationEditor {
|
|||||||
*/
|
*/
|
||||||
#draw(x, y) {
|
#draw(x, y) {
|
||||||
const [lastX, lastY] = this.currentPath.at(-1);
|
const [lastX, lastY] = this.currentPath.at(-1);
|
||||||
if (x === lastX && y === lastY) {
|
if (this.currentPath.length > 1 && x === lastX && y === lastY) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.currentPath.push([x, y]);
|
const currentPath = this.currentPath;
|
||||||
this.#lastPoint = [x, y];
|
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
|
* @param {number} y
|
||||||
*/
|
*/
|
||||||
#stopDrawing(x, y) {
|
#stopDrawing(x, y) {
|
||||||
this.ctx.closePath();
|
|
||||||
this.#requestFrameCallback = null;
|
this.#requestFrameCallback = null;
|
||||||
|
|
||||||
x = Math.min(Math.max(x, 0), this.canvas.width);
|
x = Math.min(Math.max(x, 0), this.canvas.width);
|
||||||
y = Math.min(Math.max(y, 0), this.canvas.height);
|
y = Math.min(Math.max(y, 0), this.canvas.height);
|
||||||
|
|
||||||
const [lastX, lastY] = this.currentPath.at(-1);
|
this.#draw(x, y);
|
||||||
if (x !== lastX || y !== lastY) {
|
this.#endPath();
|
||||||
this.currentPath.push([x, y]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interpolate the path entered by the user with some
|
// Interpolate the path entered by the user with some
|
||||||
// Bezier's curves in order to have a smoother path and
|
// Bezier's curves in order to have a smoother path and
|
||||||
// to reduce the data size used to draw it in the PDF.
|
// to reduce the data size used to draw it in the PDF.
|
||||||
let bezier;
|
let bezier;
|
||||||
if (this.currentPath.length !== 1) {
|
if (this.currentPath.length !== 1) {
|
||||||
bezier = fitCurve(this.currentPath, 30, null);
|
bezier = this.#generateBezierPoints();
|
||||||
} else {
|
} else {
|
||||||
// We have only one point finally.
|
// We have only one point finally.
|
||||||
const xy = [x, y];
|
const xy = [x, y];
|
||||||
bezier = [[xy, xy.slice(), xy.slice(), xy]];
|
bezier = [[xy, xy.slice(), xy.slice(), xy]];
|
||||||
}
|
}
|
||||||
const path2D = InkEditor.#buildPath2D(bezier);
|
const path2D = this.#currentPath2D;
|
||||||
this.currentPath.length = 0;
|
const currentPath = this.currentPath;
|
||||||
|
this.currentPath = [];
|
||||||
|
this.#currentPath2D = new Path2D();
|
||||||
|
|
||||||
const cmd = () => {
|
const cmd = () => {
|
||||||
|
this.allRawPaths.push(currentPath);
|
||||||
this.paths.push(bezier);
|
this.paths.push(bezier);
|
||||||
this.bezierPath2D.push(path2D);
|
this.bezierPath2D.push(path2D);
|
||||||
this.rebuild();
|
this.rebuild();
|
||||||
};
|
};
|
||||||
|
|
||||||
const undo = () => {
|
const undo = () => {
|
||||||
|
this.allRawPaths.pop();
|
||||||
this.paths.pop();
|
this.paths.pop();
|
||||||
this.bezierPath2D.pop();
|
this.bezierPath2D.pop();
|
||||||
if (this.paths.length === 0) {
|
if (this.paths.length === 0) {
|
||||||
@ -468,6 +493,95 @@ class InkEditor extends AnnotationEditor {
|
|||||||
this.addCommands({ cmd, undo, mustExec: true });
|
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.
|
* Redraw all the paths.
|
||||||
*/
|
*/
|
||||||
@ -482,6 +596,7 @@ class InkEditor extends AnnotationEditor {
|
|||||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
this.#updateTransform();
|
this.#updateTransform();
|
||||||
|
|
||||||
for (const path of this.bezierPath2D) {
|
for (const path of this.bezierPath2D) {
|
||||||
ctx.stroke(path);
|
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.
|
// Since it's the last child, there's no need to give it a higher z-index.
|
||||||
this.setInForeground();
|
this.setInForeground();
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (event.type !== "mouse") {
|
if (event.type !== "mouse") {
|
||||||
this.div.focus();
|
this.div.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
this.canvas.addEventListener("pointerleave", this.#boundCanvasPointerleave);
|
|
||||||
this.canvas.addEventListener("pointermove", this.#boundCanvasPointermove);
|
|
||||||
|
|
||||||
this.#startDrawing(event.offsetX, event.offsetY);
|
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.
|
* onpointermove callback for the canvas we're drawing on.
|
||||||
* @param {PointerEvent} event
|
* @param {PointerEvent} event
|
||||||
*/
|
*/
|
||||||
canvasPointermove(event) {
|
canvasPointermove(event) {
|
||||||
event.stopPropagation();
|
event.preventDefault();
|
||||||
this.#draw(event.offsetX, event.offsetY);
|
this.#draw(event.offsetX, event.offsetY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,17 +683,8 @@ class InkEditor extends AnnotationEditor {
|
|||||||
* @param {PointerEvent} event
|
* @param {PointerEvent} event
|
||||||
*/
|
*/
|
||||||
canvasPointerup(event) {
|
canvasPointerup(event) {
|
||||||
if (event.button !== 0) {
|
event.preventDefault();
|
||||||
return;
|
this.#endDrawing(event);
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -582,7 +693,6 @@ class InkEditor extends AnnotationEditor {
|
|||||||
*/
|
*/
|
||||||
canvasPointerleave(event) {
|
canvasPointerleave(event) {
|
||||||
this.#endDrawing(event);
|
this.#endDrawing(event);
|
||||||
this.setInBackground();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -590,8 +700,6 @@ class InkEditor extends AnnotationEditor {
|
|||||||
* @param {PointerEvent} event
|
* @param {PointerEvent} event
|
||||||
*/
|
*/
|
||||||
#endDrawing(event) {
|
#endDrawing(event) {
|
||||||
this.#stopDrawing(event.offsetX, event.offsetY);
|
|
||||||
|
|
||||||
this.canvas.removeEventListener(
|
this.canvas.removeEventListener(
|
||||||
"pointerleave",
|
"pointerleave",
|
||||||
this.#boundCanvasPointerleave
|
this.#boundCanvasPointerleave
|
||||||
@ -600,8 +708,25 @@ class InkEditor extends AnnotationEditor {
|
|||||||
"pointermove",
|
"pointermove",
|
||||||
this.#boundCanvasPointermove
|
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();
|
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
|
* @param {Arra<Array<number>} bezier
|
||||||
* @returns {Path2D}
|
* @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 { CommandManager } from "../../src/display/editor/tools.js";
|
||||||
import { fitCurve } from "../../src/display/editor/ink.js";
|
|
||||||
|
|
||||||
describe("editor", function () {
|
describe("editor", function () {
|
||||||
describe("Command Manager", function () {
|
describe("Command Manager", function () {
|
||||||
@ -91,29 +90,4 @@ describe("editor", function () {
|
|||||||
manager.add({ ...makeDoUndo(5), mustExec: true });
|
manager.add({ ...makeDoUndo(5), mustExec: true });
|
||||||
expect(x).toEqual(11);
|
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/": "../../src/",
|
||||||
"pdfjs-lib": "../../src/pdf.js",
|
"pdfjs-lib": "../../src/pdf.js",
|
||||||
"pdfjs-web/": "../../web/",
|
"pdfjs-web/": "../../web/",
|
||||||
"pdfjs-fitCurve": "../../build/dev-fitCurve/fit_curve.js",
|
|
||||||
"pdfjs-test/": "../",
|
"pdfjs-test/": "../",
|
||||||
|
|
||||||
"web-annotation_editor_params": "../../web/annotation_editor_params.js",
|
"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/": "../src/",
|
||||||
"pdfjs-lib": "../src/pdf.js",
|
"pdfjs-lib": "../src/pdf.js",
|
||||||
"pdfjs-web/": "./",
|
"pdfjs-web/": "./",
|
||||||
"pdfjs-fitCurve": "../build/dev-fitCurve/fit_curve.js",
|
|
||||||
|
|
||||||
"web-annotation_editor_params": "./stubs-geckoview.js",
|
"web-annotation_editor_params": "./stubs-geckoview.js",
|
||||||
"web-com": "./genericcom.js",
|
"web-com": "./genericcom.js",
|
||||||
|
@ -59,7 +59,6 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||||||
"pdfjs/": "../src/",
|
"pdfjs/": "../src/",
|
||||||
"pdfjs-lib": "../src/pdf.js",
|
"pdfjs-lib": "../src/pdf.js",
|
||||||
"pdfjs-web/": "./",
|
"pdfjs-web/": "./",
|
||||||
"pdfjs-fitCurve": "../build/dev-fitCurve/fit_curve.js",
|
|
||||||
|
|
||||||
"web-annotation_editor_params": "./annotation_editor_params.js",
|
"web-annotation_editor_params": "./annotation_editor_params.js",
|
||||||
"web-com": "./genericcom.js",
|
"web-com": "./genericcom.js",
|
||||||
|
Loading…
Reference in New Issue
Block a user