aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/CurrentUserUtils.ts10
-rw-r--r--src/client/views/InkingStroke.tsx27
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx281
-rw-r--r--src/fields/InkField.ts3
4 files changed, 282 insertions, 39 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 081115879..031b79b25 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -751,7 +751,15 @@ pie title Minerals in my tap water
return [
{ title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }},
{ title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
- { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
+ // { title: "Eraser", toolTip: "Eraser (Ctrl+E)", width: 50, btnType: ButtonType.DropdownList, icon: "eraser", toolType: "eraser", ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" },
+ // btnList: new List<string>(["Stroke Erase", "Segment Erase", "Radius Erase"]) },
+ { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, toolType:"eraser", scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'},
+ subMenu: [
+ { title: "Stroke", toolTip: "Stroke Erase", btnType: ButtonType.ToggleButton, icon: "eraser", toolType:"strokeeraser", ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} },
+ { title: "Segment", toolTip: "Segment Erase", btnType: ButtonType.ToggleButton, icon: "minus", toolType:"segmenteraser",ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} },
+ { title: "Radius", toolTip: "Radius Erase", btnType: ButtonType.ToggleButton, icon: "circle", toolType:"radiuseraser", ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} },
+ // { title: "Width", toolTip: "Eraser Width", btnType: ButtonType.NumberSliderButton, toolType:"strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1},
+ ]},
{ title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType:GestureUtils.Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
{ title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType:GestureUtils.Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
{ title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType:GestureUtils.Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 51baaa23e..1c24a4903 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -330,6 +330,33 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
);
};
+ splitByEraser = (inkCoords: {X: number; Y: number}, radius: number) => {
+ // const ptsXscale = (NumCast(radius) - NumCast(radius)) / (oldXrange.max - oldXrange.min || 1) || 1;
+ // const ptsYscale = (NumCast(doc._height) - NumCast(doc.stroke_width)) / (oldYrange.max - oldYrange.min || 1) || 1;
+ // const newPoints = func(this.DocumentView?.(), ink, ptsXscale, ptsYscale, NumCast(radius));
+ const controlPointDistance = 0.552284749831 * radius; // (4/3) * tan(pi / 8) * radius
+ const points: { X: number; Y: number }[] = [
+ { X: inkCoords.X + radius, Y: inkCoords.Y }, // right point
+ { X: inkCoords.X + radius, Y: inkCoords.Y - controlPointDistance }, // right's top ctrl point
+ { X: inkCoords.X + controlPointDistance, Y: inkCoords.Y - radius }, // top's right ctrl point
+ { X: inkCoords.X, Y: inkCoords.Y - radius }, // top
+ { X: inkCoords.X, Y: inkCoords.Y - radius }, // top again
+ { X: inkCoords.X - controlPointDistance, Y: inkCoords.Y - radius }, // top's left ctrl point
+ { X: inkCoords.X - radius, Y: inkCoords.Y - controlPointDistance }, // left's top ctrl point
+ { X: inkCoords.X - radius, Y: inkCoords.Y }, // left
+ { X: inkCoords.X - radius, Y: inkCoords.Y }, // left again
+ { X: inkCoords.X - radius, Y: inkCoords.Y + controlPointDistance }, // left's bottom ctrl point
+ { X: inkCoords.X - controlPointDistance, Y: inkCoords.Y + radius }, // bottom's left ctrl point
+ { X: inkCoords.X, Y: inkCoords.Y + radius }, // bottom
+ { X: inkCoords.X, Y: inkCoords.Y + radius }, // bottom again
+ { X: inkCoords.X + controlPointDistance, Y: inkCoords.Y + radius }, // bottom's right ctrl point
+ { X: inkCoords.X + radius, Y: inkCoords.Y + controlPointDistance }, // right's bottom ctrl point
+ { X: inkCoords.X + radius, Y: inkCoords.Y }, // right again
+ ];
+ const eraserCircle = new InkField(points);
+ return points;
+ }
+
_subContentView: ViewBoxInterface | undefined;
setSubContentView = (doc: ViewBoxInterface) => (this._subContentView = doc);
@computed get fillColor() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 791124f50..9a0f34074 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -610,10 +610,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
// prettier-ignore
switch (Doc.ActiveTool) {
- case InkTool.Highlighter: break;
- case InkTool.Write: break;
- case InkTool.Pen: break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
- case InkTool.Eraser:
+ case InkTool.Highlighter:
+ break;
+ case InkTool.Write:
+ break;
+ case InkTool.Pen:
+ break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
+ case InkTool.StrokeEraser:
+ this._batch = UndoManager.StartBatch('collectionErase');
+ setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
+ break;
+ case InkTool.SegmentEraser:
+ this._batch = UndoManager.StartBatch('collectionErase');
+ setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
+ break;
+ case InkTool.RadiusEraser:
this._batch = UndoManager.StartBatch('collectionErase');
setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
break;
@@ -739,10 +750,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
SetActiveInkWidth(StrCast(intersect.inkView.Document.stroke_width?.toString()) || '1');
SetActiveInkColor(StrCast(intersect.inkView.Document.color?.toString()) || 'black');
// create a new curve by appending all curves of the current segment together in order to render a single new stroke.
- if (!e.shiftKey) {
+ if (Doc.ActiveTool !== InkTool.StrokeEraser) {
this._eraserLock++;
- const segments = this.segmentInkStroke(intersect.inkView, intersect.t);
- segments.forEach(segment =>
+ const segments =
+ Doc.ActiveTool === InkTool.SegmentEraser
+ ? this.segmentErase(intersect.inkView, intersect.t) // intersect.t is where the eraser intersected the ink stroke - want to remove the segment that starts at the intersection just before this t value and goes to the one just after it
+ : this.radiusErase(intersect.inkView, intersect.t, currPoint);
+ // } else if (Doc.ActiveTool === InkTool.RadiusEraser) {
+ // segments = undefined;
+ // }
+ segments?.forEach(segment =>
this.forceStrokeGesture(
e,
GestureUtils.Gestures.Stroke,
@@ -752,7 +769,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
setTimeout(() => this._eraserLock--);
}
// Lower ink opacity to give the user a visual indicator of deletion.
- intersect.inkView.layoutDoc.opacity = 0.5;
+ intersect.inkView.layoutDoc.opacity = 0.0;
intersect.inkView.layoutDoc.dontIntersect = true;
}
});
@@ -797,12 +814,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
)
.reduce(
(intersections, { inkStroke, inkView }) => {
- const { inkData } = inkStroke.inkScaledData();
+ const { inkData } = inkStroke.inkScaledData(); // get bezier curve as set of control points
// Convert from screen space to ink space for the intersection.
const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint);
const currPointInkSpace = inkStroke.ptFromScreen(currPoint);
for (var i = 0; i < inkData.length - 3; i += 4) {
+ // iterate over each segment of bezier curve
const rawIntersects = InkField.Segment(inkData, i).intersects({
+ // segment's are indexed by 0, 4, 8,
// compute all unique intersections
p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y },
@@ -817,51 +836,237 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
);
};
+ @action
+ radiusErase = (ink: DocumentView, eraseT: number, screenEraserPt: { X: number; Y: number }): Segment[] => {
+ const segments: Segment[] = [];
+ const inkCoords = ink.ComponentView?.ptFromScreen?.(screenEraserPt); // coordinates in ink space
+ if (!inkCoords) return [];
+
+ var segment1: Segment = [];
+ var segment2: Segment = [];
+ const eraseRadius = ActiveInkWidth() / 2;
+ const inkStroke = ink?.ComponentView as InkingStroke;
+ const { inkData } = inkStroke.inkScaledData();
+
+ const eraserInkData = inkStroke.splitByEraser(inkCoords, eraseRadius);
+
+ const tVals: number[] = []; // should be the tvals of the intersections
+
+ for (var i = 0; i < eraserInkData.length - 3; i += 4) {
+ const eraserBezier: Bezier = InkField.Segment(eraserInkData, i);
+ segment1.push(eraserBezier);
+ for (var j = 0; j < inkData.length; j += 4) {
+ const inkSegment: Bezier = InkField.Segment(inkData, i);
+ // this.bintersects(inkSegment, eraserBezier).forEach((val: string | number, i: number) => {
+ // // Converting the Bezier.js Split type to a t-value number.
+ // const t = +val.toString().split('/')[0];
+ // if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical).
+ // });
+ }
+ }
+ // segment1.push(eraserBezier);
+
+ // for (var i = 0; i < inkData.length - 3; i += 4) {
+ // const currCurveT = Math.floor(i/4);
+ // const inkSegment: Bezier = InkField.Segment(inkData, i);
+ // if (tVals[0] >= currCurveT && tVals[0] < currCurveT+1) {
+ // tVals.shift()
+ // i -= 4;
+ // if (eraseT - eraseWidth > currCurveT && eraseT + eraseWidth < currCurveT + 1) {
+ // segment1.push(inkSegment.split(0, eraseT - currCurveT - eraseWidth));
+ // segment2.push(inkSegment.split(eraseT - currCurveT + eraseWidth, 1));
+ // } else if (eraseT - eraseWidth < currCurveT) {
+ // segment2.push(inkSegment.split(eraseT - currCurveT + eraseWidth, 1));
+ // } else if (eraseT + eraseWidth > currCurveT + 1) {
+ // segment1.push(inkSegment.split(0, eraseT - currCurveT - eraseWidth));
+ // }
+ // } else if (eraseT > currCurveT + 1) {
+ // segment1.push(inkSegment);
+ // } else {
+ // segment2.push(inkSegment);
+ // }
+ // }
+
+ // push 1 or both segments if they are not empty
+ if (segment1.length && (Math.abs(segment1[0].points[0].x - segment1[0].points.lastElement().x) > 0.5 || Math.abs(segment1[0].points[0].y - segment1[0].points.lastElement().y) > 0.5)) {
+ segments.push(segment1);
+ }
+ if (segment2.length && (Math.abs(segment2[0].points[0].x - segment2[0].points.lastElement().x) > 0.5 || Math.abs(segment2[0].points[0].y - segment2[0].points.lastElement().y) > 0.5)) {
+ segments.push(segment2);
+ }
+ return segments;
+ };
+
/**
- * Performs segmentation of the ink stroke - creates "segments" or subsections of the current ink stroke at points in which the
- * ink stroke intersects any other ink stroke (including itself).
+ * Erases ink strokes by segments. Locates intersections of the current ink stroke with all other ink strokes (including itself),
+ * then erases the segment that was intersected by the eraser. This is done by creating either 1 or two resulting segments
+ * (this depends on whether the eraser his the middle or end of a stroke), and returning the segments to "redraw."
* @param ink The ink DocumentView intersected by the eraser.
* @param excludeT The index of the curve in the ink document that the eraser intersection occurred.
* @returns The ink stroke represented as a list of segments, excluding the segment in which the eraser intersection occurred.
*/
@action
- segmentInkStroke = (ink: DocumentView, excludeT: number): Segment[] => {
+ segmentErase = (ink: DocumentView, excludeT: number): Segment[] => {
const segments: Segment[] = [];
- var segment: Segment = [];
- var startSegmentT = 0;
+ var segment1: Segment = [];
+ var segment2: Segment = [];
const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData();
- // This iterates through all segments of the curve and splits them where they intersect another curve.
- // if 'excludeT' is specified, then any segment containing excludeT will be skipped (ie, deleted)
+ var intersections: number[] = []; // list of the ink stroke's intersections
+ var segmentIndexes: number[] = []; // list of indexes of the curve's segment where each intersection occured
+
+ // loops through each segment and adds intersections to the list
for (var i = 0; i < inkData.length - 3; i += 4) {
- const inkSegment = InkField.Segment(inkData, i);
- // Getting all t-value intersections of the current curve with all other curves.
- const tVals = this.getInkIntersections(i, ink, inkSegment).sort();
- if (tVals.length) {
- tVals.forEach((t, index) => {
- const docCurveTVal = t + Math.floor(i / 4);
- if (excludeT < startSegmentT || excludeT > docCurveTVal) {
- const localStartTVal = startSegmentT - Math.floor(i / 4);
- t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t));
- if (segment.length && (Math.abs(segment[0].points[0].x - segment[0].points.lastElement().x) > 0.5 || Math.abs(segment[0].points[0].y - segment[0].points.lastElement().y) > 0.5)) segments.push(segment);
- }
- // start a new segment from the intersection t value
- if (tVals.length - 1 === index) {
- const split = inkSegment.split(t).right;
- if (split && (Math.abs(split.points[0].x - split.points.lastElement().x) > 0.5 || Math.abs(split.points[0].y - split.points.lastElement().y) > 0.5)) segment = [split];
- else segment = [];
- } else segment = [];
- startSegmentT = docCurveTVal;
- });
+ const inkSegment: Bezier = InkField.Segment(inkData, i);
+ var currIntersects = this.getInkIntersections(i, ink, inkSegment).sort();
+ // get current segments intersections (if any) and add the curve index
+ currIntersects = currIntersects.map(tVal => tVal + Math.floor(i / 4));
+ if (currIntersects.length) {
+ intersections = [...intersections, ...currIntersects];
+ for (var j = 0; j < currIntersects.length; j++) {
+ segmentIndexes.push(Math.floor(i / 4));
+ }
+ }
+ }
+
+ // for closed curve bookkeeping
+ var isClosedCurve = false;
+ if (intersections[0] === 0) {
+ isClosedCurve = true;
+
+ // now reduce intersections list to the actual intersections because closed curves have additional ones with itself
+ const indices = intersections.map((value, index) => (value > 0.0001 && value < Math.floor(inkData.length / 4) ? index : -1)).filter(index => index !== -1);
+
+ // Filter intersections and segmentIndexes based on validIndices
+ intersections = indices.map(index => intersections[index]);
+ segmentIndexes = indices.map(index => segmentIndexes[index]);
+ // intersections = intersections.slice(1, intersections.length ); // take the 0 intersection out
+ // segmentIndexes = segmentIndexes.slice(1, segmentIndexes.length); // same for indexes
+ }
+
+ if (intersections.length) {
+ // this is the indexes of the closest intersection(s)
+ var closestTs = this.getClosestTs(intersections, excludeT, 0, intersections.length - 1);
+
+ // find the segments that need to be split
+ var splitSegment1 = -1; // stays -1 if left end is deleted
+ var splitSegment2 = -1; // stays -1 if right end is deleted
+ if (closestTs[0] !== -1 && closestTs[1] !== -1) {
+ // if not on the ends
+ splitSegment1 = segmentIndexes[closestTs[0]];
+ splitSegment2 = segmentIndexes[closestTs[1]];
+ } else if (closestTs[0] === -1) {
+ // for a curve before an intersection
+ splitSegment2 = segmentIndexes[closestTs[1]];
} else {
- segment.push(inkSegment);
+ // for a curve after an intersection
+ splitSegment1 = segmentIndexes[closestTs[0]];
+ }
+
+ // so here splitSegment1 and splitSegment2 will be the index(es) of the segment(s) to split
+
+ var hasSplit = false;
+ var continueErasing = false;
+ // loop through segments again and split them if they match the split segments
+ for (var i = 0; i < inkData.length - 3; i += 4) {
+ const currCurveT = Math.floor(i / 4);
+ const inkSegment: Bezier = InkField.Segment(inkData, i);
+
+ // case where the current curve is the first to split
+ if (splitSegment1 !== -1 && splitSegment2 !== -1) {
+ if (splitSegment1 === splitSegment2 && splitSegment1 === currCurveT) {
+ // if it's the same segment
+ segment1.push(inkSegment.split(0, intersections[closestTs[0]] - currCurveT));
+ segment2.push(inkSegment.split(intersections[closestTs[1]] - currCurveT, 1));
+ hasSplit = true;
+ } else if (splitSegment1 === currCurveT) {
+ segment1.push(inkSegment.split(0, intersections[closestTs[0]] - currCurveT));
+ continueErasing = true;
+ } else if (splitSegment2 === currCurveT) {
+ segment2.push(inkSegment.split(intersections[closestTs[1]] - currCurveT, 1));
+ continueErasing = false;
+ hasSplit = true;
+ } else {
+ if (!continueErasing && !hasSplit) {
+ // segment doesn't get pushed if continueErasing is true
+ segment1.push(inkSegment);
+ } else if (!continueErasing && hasSplit) {
+ segment2.push(inkSegment);
+ }
+ }
+ } else if (splitSegment1 === -1) {
+ // case where left end is erased
+ if (currCurveT === splitSegment2) {
+ segment2.push(inkSegment.split(intersections[closestTs[1]] - currCurveT, 1));
+ hasSplit = true;
+ } else {
+ if (isClosedCurve && currCurveT === segmentIndexes.lastElement()) {
+ segment2.push(inkSegment.split(0, intersections.lastElement() - currCurveT));
+ continueErasing = true;
+ } else if (hasSplit && !continueErasing) {
+ segment2.push(inkSegment);
+ }
+ }
+ } else {
+ // case where right end is erased
+ if (currCurveT === segmentIndexes[0] && isClosedCurve) {
+ segment1.push(inkSegment.split(intersections[0] - currCurveT, 1));
+ hasSplit = true;
+ } else if (currCurveT === splitSegment1) {
+ segment1.push(inkSegment.split(0, intersections[closestTs[0]] - currCurveT));
+ hasSplit = false; // although this is not technically true, we set to false so the segments after don't get added
+ // } else if (isClosedCurve && currCurveT === segmentIndexes[1]) {
+ // segment1.push(inkSegment.split(intersections[0] - currCurveT, 1));
+ } else {
+ if ((isClosedCurve && hasSplit) || (!isClosedCurve && !hasSplit)) {
+ segment1.push(inkSegment);
+ }
+ }
+ }
}
}
- if (excludeT < startSegmentT || excludeT > inkData.length / 4) {
- segment.length && segments.push(segment);
+
+ // push 1 or both segments if they are not empty
+ if (segment1.length && (Math.abs(segment1[0].points[0].x - segment1[0].points.lastElement().x) > 0.5 || Math.abs(segment1[0].points[0].y - segment1[0].points.lastElement().y) > 0.5)) {
+ segments.push(segment1);
}
+ if (segment2.length && (Math.abs(segment2[0].points[0].x - segment2[0].points.lastElement().x) > 0.5 || Math.abs(segment2[0].points[0].y - segment2[0].points.lastElement().y) > 0.5)) {
+ segments.push(segment2);
+ }
+
return segments;
};
+ /**
+ * Standard logarithmic search function to search a sorted list of tVals for the ones closest to excludeT.
+ * @param tVals list of tvalues (usage is for intersection t values) to search within
+ * @param excludeT the t value of where the eraser intersected the curve
+ * @param startIndex the start index to search from
+ * @param endIndex the end index to search to
+ * @returns 2-item array of the closest tVals indexes
+ */
+ getClosestTs = (tVals: number[], excludeT: number, startIndex: number, endIndex: number): number[] => {
+ if (tVals[startIndex] >= excludeT) {
+ return [-1, startIndex];
+ } else if (tVals[endIndex] < excludeT) {
+ return [endIndex, -1];
+ } else {
+ const mid = Math.floor((startIndex + endIndex) / 2);
+ if (excludeT >= tVals[mid]) {
+ if (mid + 1 <= endIndex && tVals[mid + 1] > excludeT) {
+ return [mid, mid + 1];
+ } else {
+ return this.getClosestTs(tVals, excludeT, mid + 1, endIndex);
+ }
+ } else {
+ if (mid - 1 >= startIndex && tVals[mid - 1] < excludeT) {
+ return [mid - 1, mid];
+ } else {
+ return this.getClosestTs(tVals, excludeT, startIndex, mid - 1);
+ }
+ }
+ }
+ };
+
// for some reason bezier.js doesn't handle the case of intersecting a linear curve, so we wrap the intersection
// call in a test for linearity
bintersects = (curve: Bezier, otherCurve: Bezier) => {
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index b3e01229a..9c8c5df2c 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -11,6 +11,9 @@ export enum InkTool {
Pen = 'pen',
Highlighter = 'highlighter',
Eraser = 'eraser',
+ StrokeEraser = 'strokeeraser',
+ SegmentEraser = 'segmenteraser',
+ RadiusEraser = 'radiuseraser',
Stamp = 'stamp',
Write = 'write',
PresentationPin = 'presentationpin',