Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: highlight #771

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
<button type="button" class="button" data-action="change-color">Change color</button>
<button type="button" class="button" data-action="change-width">Change width</button>
<button type="button" class="button" data-action="change-background-color">Change background color</button>
<button type="button" class="button" data-action="change-highlight-color">Change highlight color</button>
<button type="button" class="button" data-action="change-highlight-size">Change highlight size</button>

</div>
<div class="column">
Expand Down
12 changes: 12 additions & 0 deletions docs/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const wrapper = document.getElementById("signature-pad");
const canvasWrapper = document.getElementById("canvas-wrapper");
const clearButton = wrapper.querySelector("[data-action=clear]");
const changeBackgroundColorButton = wrapper.querySelector("[data-action=change-background-color]");
const changeHighlightColorButton = wrapper.querySelector("[data-action=change-highlight-color]");
const changeHighlightSizeButton = wrapper.querySelector("[data-action=change-highlight-size]");
const changeColorButton = wrapper.querySelector("[data-action=change-color]");
const changeWidthButton = wrapper.querySelector("[data-action=change-width]");
const undoButton = wrapper.querySelector("[data-action=undo]");
Expand Down Expand Up @@ -134,6 +136,16 @@ changeBackgroundColorButton.addEventListener("click", () => {
signaturePad.fromData(data);
});

changeHighlightColorButton.addEventListener("click", () => {
signaturePad.highlightColor = randomColor();
});

changeHighlightSizeButton.addEventListener("click", () => {
const size = Math.round(Math.random() * 100) / 10;

signaturePad.highlightSize = size;
});

changeColorButton.addEventListener("click", () => {
signaturePad.penColor = randomColor();
});
Expand Down
2 changes: 1 addition & 1 deletion docs/js/signature_pad.umd.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/js/signature_pad.umd.min.js.map

Large diffs are not rendered by default.

196 changes: 143 additions & 53 deletions src/signature_pad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export interface PointGroupOptions {
minWidth: number;
maxWidth: number;
penColor: string;
highlightColor: string;
highlightSize: number;
velocityFilterWeight: number;
/**
* This is the globalCompositeOperation for the line.
Expand All @@ -67,6 +69,8 @@ export default class SignaturePad extends SignatureEventTarget {
public minWidth: number;
public maxWidth: number;
public penColor: string;
public highlightColor: string;
public highlightSize: number;
public minDistance: number;
public velocityFilterWeight: number;
public compositeOperation: GlobalCompositeOperation;
Expand All @@ -91,20 +95,18 @@ export default class SignaturePad extends SignatureEventTarget {
options: Options = {},
) {
super();
this.velocityFilterWeight = options.velocityFilterWeight || 0.7;
this.minWidth = options.minWidth || 0.5;
this.maxWidth = options.maxWidth || 2.5;
this.throttle = ('throttle' in options ? options.throttle : 16) as number; // in milliseconds
this.minDistance = (
'minDistance' in options ? options.minDistance : 5
) as number; // in pixels
this.dotSize = options.dotSize || 0;
this.penColor = options.penColor || 'black';
this.backgroundColor = options.backgroundColor || 'rgba(0,0,0,0)';
this.compositeOperation = options.compositeOperation || 'source-over';
this.canvasContextOptions = (
'canvasContextOptions' in options ? options.canvasContextOptions : {}
) as CanvasRenderingContext2DSettings;
this.velocityFilterWeight = options.velocityFilterWeight ?? 0.7;
this.minWidth = options.minWidth ?? 0.5;
this.maxWidth = options.maxWidth ?? 2.5;
this.throttle = options.throttle ?? 16; // in milliseconds
this.minDistance = options.minDistance ?? 5; // in pixels
this.dotSize = options.dotSize ?? 0;
this.penColor = options.penColor ?? 'black';
this.highlightColor = options.highlightColor ?? '';
this.highlightSize = options.highlightSize ?? 1;
this.backgroundColor = options.backgroundColor ?? 'rgba(0,0,0,0)';
this.compositeOperation = options.compositeOperation ?? 'source-over';
this.canvasContextOptions = options.canvasContextOptions ?? {};

this._strokeMoveUpdate = this.throttle
? throttle(SignaturePad.prototype._strokeUpdate, this.throttle)
Expand Down Expand Up @@ -416,6 +418,14 @@ export default class SignaturePad extends SignatureEventTarget {
private _getPointGroupOptions(group?: PointGroup): PointGroupOptions {
return {
penColor: group && 'penColor' in group ? group.penColor : this.penColor,
highlightColor:
group && 'highlightColor' in group
? group.highlightColor
: this.highlightColor,
highlightSize:
group && 'highlightSize' in group
? group.highlightSize
: this.highlightSize,
dotSize: group && 'dotSize' in group ? group.dotSize : this.dotSize,
minWidth: group && 'minWidth' in group ? group.minWidth : this.minWidth,
maxWidth: group && 'maxWidth' in group ? group.maxWidth : this.maxWidth,
Expand Down Expand Up @@ -501,18 +511,42 @@ export default class SignaturePad extends SignatureEventTarget {
if (!lastPoint || !(lastPoint && isLastPointTooClose)) {
const curve = this._addPoint(point, pointGroupOptions);

if (!lastPoint) {
this._drawDot(point, pointGroupOptions);
} else if (curve) {
this._drawCurve(curve, pointGroupOptions);
}

lastPoints.push({
time: point.time,
x: point.x,
y: point.y,
pressure: point.pressure,
});

if (!lastPoint) {
if (
pointGroupOptions.highlightColor &&
pointGroupOptions.highlightSize
) {
this._drawDot(point, pointGroupOptions, true);
}
this._drawDot(point, pointGroupOptions, false);
} else if (curve) {
if (
pointGroupOptions.highlightColor &&
pointGroupOptions.highlightSize
) {
this._drawAll(
lastPoints,
pointGroupOptions,
this._drawCurve.bind(this),
true,
);
this._drawAll(
lastPoints,
pointGroupOptions,
this._drawCurve.bind(this),
false,
);
} else {
this._drawCurve(curve, pointGroupOptions, false);
}
}
}

this.dispatchEvent(new CustomEvent('afterUpdateStroke', { detail: event }));
Expand Down Expand Up @@ -633,15 +667,19 @@ export default class SignaturePad extends SignatureEventTarget {
this._isEmpty = false;
}

private _drawCurve(curve: Bezier, options: PointGroupOptions): void {
private _drawCurve(
curve: Bezier,
options: PointGroupOptions,
isHighlight: boolean,
): void {
const ctx = this._ctx;
const widthDelta = curve.endWidth - curve.startWidth;
// '2' is just an arbitrary number here. If only length is used, then
// there are gaps between curve segments :/
const drawSteps = Math.ceil(curve.length()) * 2;

ctx.beginPath();
ctx.fillStyle = options.penColor;
ctx.fillStyle = isHighlight ? options.highlightColor : options.penColor;

for (let i = 0; i < drawSteps; i += 1) {
// Calculate the Bezier (x, y) coordinate for this step.
Expand All @@ -666,60 +704,92 @@ export default class SignaturePad extends SignatureEventTarget {
curve.startWidth + ttt * widthDelta,
options.maxWidth,
);
this._drawCurveSegment(x, y, width);
this._drawCurveSegment(
x,
y,
width + (isHighlight ? options.highlightSize * 2 : 0),
);
}

ctx.closePath();
ctx.fill();
}

private _drawDot(point: BasicPoint, options: PointGroupOptions): void {
private _drawDot(
point: BasicPoint,
options: PointGroupOptions,
isHighlight: boolean,
): void {
const ctx = this._ctx;
const width =
options.dotSize > 0
? options.dotSize
: (options.minWidth + options.maxWidth) / 2;

ctx.beginPath();
this._drawCurveSegment(point.x, point.y, width);
this._drawCurveSegment(
point.x,
point.y,
width + (isHighlight ? options.highlightSize * 2 : 0),
);
ctx.closePath();
ctx.fillStyle = options.penColor;
ctx.fillStyle = isHighlight ? options.highlightColor : options.penColor;
ctx.fill();
}

private _fromData(
pointGroups: PointGroup[],
drawCurve: SignaturePad['_drawCurve'],
drawDot: SignaturePad['_drawDot'],
drawCurve: typeof this._drawCurve,
drawDot: typeof this._drawDot,
): void {
for (const group of pointGroups) {
const { points } = group;
const pointGroupOptions = this._getPointGroupOptions(group);

if (points.length > 1) {
for (let j = 0; j < points.length; j += 1) {
const basicPoint = points[j];
const point = new Point(
basicPoint.x,
basicPoint.y,
basicPoint.pressure,
basicPoint.time,
);

if (j === 0) {
this._reset(pointGroupOptions);
}

const curve = this._addPoint(point, pointGroupOptions);

if (curve) {
drawCurve(curve, pointGroupOptions);
}
if (
pointGroupOptions.highlightColor &&
pointGroupOptions.highlightSize
) {
this._drawAll(points, pointGroupOptions, drawCurve, true);
}
this._drawAll(points, pointGroupOptions, drawCurve, false);
} else {
this._reset(pointGroupOptions);
if (
pointGroupOptions.highlightColor &&
pointGroupOptions.highlightSize
) {
drawDot(points[0], pointGroupOptions, true);
}
drawDot(points[0], pointGroupOptions, false);
}
}
}

private _drawAll(
points: BasicPoint[],
pointGroupOptions: PointGroupOptions,
drawCurve: typeof this._drawCurve,
isHighlight: boolean,
): void {
for (let j = 0; j < points.length; j += 1) {
const basicPoint = points[j];
const point = new Point(
basicPoint.x,
basicPoint.y,
basicPoint.pressure,
basicPoint.time,
);

if (j === 0) {
this._reset(pointGroupOptions);
}

drawDot(points[0], pointGroupOptions);
const curve = this._addPoint(point, pointGroupOptions);

if (curve) {
drawCurve(curve, pointGroupOptions, isHighlight);
}
}
}
Expand Down Expand Up @@ -751,7 +821,7 @@ export default class SignaturePad extends SignatureEventTarget {
this._fromData(
pointGroups,

(curve, { penColor }) => {
(curve, { penColor, highlightColor, highlightSize }, isHighlight) => {
const path = document.createElement('path');

// Need to check curve for NaN values, these pop up when drawing
Expand All @@ -772,8 +842,14 @@ export default class SignaturePad extends SignatureEventTarget {
`${curve.control2.x.toFixed(3)},${curve.control2.y.toFixed(3)} ` +
`${curve.endPoint.x.toFixed(3)},${curve.endPoint.y.toFixed(3)}`;
path.setAttribute('d', attr);
path.setAttribute('stroke-width', (curve.endWidth * 2.25).toFixed(3));
path.setAttribute('stroke', penColor);
path.setAttribute(
'stroke-width',
(
(curve.endWidth + (isHighlight ? highlightSize * 2 : 0)) *
2.25
).toFixed(3),
);
path.setAttribute('stroke', isHighlight ? highlightColor : penColor);
path.setAttribute('fill', 'none');
path.setAttribute('stroke-linecap', 'round');

Expand All @@ -782,13 +858,27 @@ export default class SignaturePad extends SignatureEventTarget {
/* eslint-enable no-restricted-globals */
},

(point, { penColor, dotSize, minWidth, maxWidth }) => {
(
point,
{
penColor,
highlightColor,
highlightSize,
dotSize,
minWidth,
maxWidth,
},
isHighlight,
) => {
const circle = document.createElement('circle');
const size = dotSize > 0 ? dotSize : (minWidth + maxWidth) / 2;
circle.setAttribute('r', size.toString());
circle.setAttribute(
'r',
(size + (isHighlight ? highlightSize * 2 : 0)).toString(),
);
circle.setAttribute('cx', point.x.toString());
circle.setAttribute('cy', point.y.toString());
circle.setAttribute('fill', penColor);
circle.setAttribute('fill', isHighlight ? highlightColor : penColor);

svg.appendChild(circle);
},
Expand Down
6 changes: 6 additions & 0 deletions tests/fixtures/face.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { PointGroup } from '../../src/signature_pad';
export const face: PointGroup[] = [
{
penColor: 'black',
highlightColor: '',
highlightSize: 0,
dotSize: 0,
minWidth: 0.5,
maxWidth: 2.5,
Expand All @@ -19,6 +21,8 @@ export const face: PointGroup[] = [
},
{
penColor: 'black',
highlightColor: '',
highlightSize: 0,
dotSize: 0,
minWidth: 0.5,
maxWidth: 2.5,
Expand All @@ -35,6 +39,8 @@ export const face: PointGroup[] = [
},
{
penColor: 'black',
highlightColor: '',
highlightSize: 0,
dotSize: 0,
minWidth: 0.5,
maxWidth: 2.5,
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures/square.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { PointGroup } from '../../src/signature_pad';
export const square: PointGroup[] = [
{
penColor: 'black',
highlightColor: '',
highlightSize: 0,
dotSize: 0,
minWidth: 0.5,
maxWidth: 2.5,
Expand Down
Loading