fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
233 lines (232 loc) • 6.6 kB
JavaScript
import { _defineProperty } from "../../_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.mjs";
import { Shadow } from "../Shadow.mjs";
import { getSmoothPathFromPoints, joinPath } from "../util/path/index.mjs";
import { BaseBrush } from "./BaseBrush.mjs";
import { Path } from "../shapes/Path.mjs";
//#region src/brushes/PencilBrush.ts
/**
* @private
* @param {TSimplePathData} pathData SVG path commands
* @returns {boolean}
*/
function isEmptySVGPath(pathData) {
return joinPath(pathData) === "M 0 0 Q 0 0 0 0 L 0 0";
}
var PencilBrush = class PencilBrush extends BaseBrush {
constructor(canvas) {
super(canvas);
_defineProperty(
this,
/**
* Discard points that are less than `decimate` pixel distant from each other
* @type Number
* @default 0.4
*/
"decimate",
.4
);
_defineProperty(
this,
/**
* Draws a straight line between last recorded point to current pointer
* Used for `shift` functionality
*
* @type boolean
* @default false
*/
"drawStraightLine",
false
);
_defineProperty(
this,
/**
* The event modifier key that makes the brush draw a straight line.
* If `null` or 'none' or any other string that is not a modifier key the feature is disabled.
* @type {ModifierKey | undefined | null}
*/
"straightLineKey",
"shiftKey"
);
this._points = [];
this._hasStraightLine = false;
}
needsFullRender() {
return super.needsFullRender() || this._hasStraightLine;
}
static drawSegment(ctx, p1, p2) {
const midPoint = p1.midPointFrom(p2);
ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
return midPoint;
}
/**
* Invoked on mouse down
* @param {Point} pointer
*/
onMouseDown(pointer, { e }) {
if (!this.canvas._isMainEvent(e)) return;
this.drawStraightLine = !!this.straightLineKey && e[this.straightLineKey];
this._prepareForDrawing(pointer);
this._addPoint(pointer);
this._render();
}
/**
* Invoked on mouse move
* @param {Point} pointer
*/
onMouseMove(pointer, { e }) {
if (!this.canvas._isMainEvent(e)) return;
this.drawStraightLine = !!this.straightLineKey && e[this.straightLineKey];
if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) return;
if (this._addPoint(pointer) && this._points.length > 1) if (this.needsFullRender()) {
this.canvas.clearContext(this.canvas.contextTop);
this._render();
} else {
const points = this._points, length = points.length, ctx = this.canvas.contextTop;
this._saveAndTransform(ctx);
if (this.oldEnd) {
ctx.beginPath();
ctx.moveTo(this.oldEnd.x, this.oldEnd.y);
}
this.oldEnd = PencilBrush.drawSegment(ctx, points[length - 2], points[length - 1]);
ctx.stroke();
ctx.restore();
}
}
/**
* Invoked on mouse up
*/
onMouseUp({ e }) {
if (!this.canvas._isMainEvent(e)) return true;
this.drawStraightLine = false;
this.oldEnd = void 0;
this._finalizeAndAddPath();
return false;
}
/**
* @private
* @param {Point} pointer Actual mouse position related to the canvas.
*/
_prepareForDrawing(pointer) {
this._reset();
this._addPoint(pointer);
this.canvas.contextTop.moveTo(pointer.x, pointer.y);
}
/**
* @private
* @param {Point} point Point to be added to points array
*/
_addPoint(point) {
if (this._points.length > 1 && point.eq(this._points[this._points.length - 1])) return false;
if (this.drawStraightLine && this._points.length > 1) {
this._hasStraightLine = true;
this._points.pop();
}
this._points.push(point);
return true;
}
/**
* Clear points array and set contextTop canvas style.
* @private
*/
_reset() {
this._points = [];
this._setBrushStyles(this.canvas.contextTop);
this._setShadow();
this._hasStraightLine = false;
}
/**
* Draw a smooth path on the topCanvas using quadraticCurveTo
* @private
* @param {CanvasRenderingContext2D} [ctx]
*/
_render(ctx = this.canvas.contextTop) {
let p1 = this._points[0], p2 = this._points[1];
this._saveAndTransform(ctx);
ctx.beginPath();
if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) {
const width = this.width / 1e3;
p1.x -= width;
p2.x += width;
}
ctx.moveTo(p1.x, p1.y);
for (let i = 1; i < this._points.length; i++) {
PencilBrush.drawSegment(ctx, p1, p2);
p1 = this._points[i];
p2 = this._points[i + 1];
}
ctx.lineTo(p1.x, p1.y);
ctx.stroke();
ctx.restore();
}
/**
* Converts points to SVG path
* @param {Point[]} points Array of points
* @return {TSimplePathData} SVG path commands
*/
convertPointsToSVGPath(points) {
return getSmoothPathFromPoints(points, this.width / 1e3);
}
/**
* Creates a Path object to add on canvas
* @param {TSimplePathData} pathData Path data
* @return {Path} Path to add on canvas
*/
createPath(pathData) {
const path = new Path(pathData, {
fill: null,
stroke: this.color,
strokeWidth: this.width,
strokeLineCap: this.strokeLineCap,
strokeMiterLimit: this.strokeMiterLimit,
strokeLineJoin: this.strokeLineJoin,
strokeDashArray: this.strokeDashArray
});
if (this.shadow) {
this.shadow.affectStroke = true;
path.shadow = new Shadow(this.shadow);
}
return path;
}
/**
* Decimate points array with the decimate value
*/
decimatePoints(points, distance) {
if (points.length <= 2) return points;
let lastPoint = points[0], cDistance;
const zoom = this.canvas.getZoom(), adjustedDistance = Math.pow(distance / zoom, 2), l = points.length - 1, newPoints = [lastPoint];
for (let i = 1; i < l - 1; i++) {
cDistance = Math.pow(lastPoint.x - points[i].x, 2) + Math.pow(lastPoint.y - points[i].y, 2);
if (cDistance >= adjustedDistance) {
lastPoint = points[i];
newPoints.push(lastPoint);
}
}
newPoints.push(points[l]);
return newPoints;
}
/**
* On mouseup after drawing the path on contextTop canvas
* we use the points captured to create an new Path object
* and add it to the canvas.
*/
_finalizeAndAddPath() {
this.canvas.contextTop.closePath();
if (this.decimate) this._points = this.decimatePoints(this._points, this.decimate);
const pathData = this.convertPointsToSVGPath(this._points);
if (isEmptySVGPath(pathData)) {
this.canvas.requestRenderAll();
return;
}
const path = this.createPath(pathData);
this.canvas.clearContext(this.canvas.contextTop);
this.canvas.fire("before:path:created", { path });
this.canvas.add(path);
this.canvas.requestRenderAll();
path.setCoords();
this._resetShadow();
this.canvas.fire("path:created", { path });
}
};
//#endregion
export { PencilBrush };
//# sourceMappingURL=PencilBrush.mjs.map