From 441a3dab4ada425d28a55435be51339e3d28c892 Mon Sep 17 00:00:00 2001
From: vkalev <50213748+vkalev@users.noreply.github.com>
Date: Mon, 21 Jun 2021 17:44:19 -0500
Subject: adding comments

---
 src/client/views/InkingStroke.tsx | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 449019ca8..859e53b97 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -41,6 +41,11 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
     });
 
+    /**
+     * Handles the movement of a selected control point when the user clicks and drags.
+     * @param e React Pointer Event.
+     * @param controlNum The number of the currently selected control point.
+     */
     @action
     onControlDown = (e: React.PointerEvent, controlNum: number): void => {
         if (InkStrokeProperties.Instance) {
@@ -56,6 +61,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         }
     }
 
+    /**
+     * Changes the current selected control point.
+     * @param i The number of the point to be selected.
+     */
     @action
     changeCurrPoint = (i: number) => {
         if (InkStrokeProperties.Instance) {
@@ -64,6 +73,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         }
     }
 
+    /**
+     * Deletes the currently selected points.
+     * @param e Keyboard Event.
+     */
     @action
     delPts = (e: KeyboardEvent) => {
         if (["-", "Backspace", "Delete"].includes(e.key)) {
@@ -71,6 +84,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         }
     }
 
+    /**
+     * Handles the movement of the entire ink object when the user clicks and drags.
+     * @param e React Pointer Event.
+     */
     onPointerDown = (e: React.PointerEvent) => {
         if (this.props.isSelected(true)) {
             setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e: PointerEvent, doubleTap: boolean | undefined) =>
@@ -102,17 +119,18 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const scaleY = height === strokeWidth ? 1 : (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
         const strokeColor = StrCast(this.layoutDoc.color, "");
 
+        // Visually renders the polygonal line made by the user.
         const points = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
             StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
             StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
 
+        // Invisible polygonal line that enables the ink to be selected by the user.
         const hpoints = InteractionUtils.CreatePolyline(data, left, top,
             this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, (strokeWidth + 15),
             StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
             "none", "none", undefined, scaleX, scaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
 
-        //points for adding
         const apoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth,
             StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
@@ -153,24 +171,27 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         // }
         const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
 
+        // Additional points (controls) added by the user via click when editing the ink's format.
         const addpoints = apoints.map((pts, i) =>
             <svg height="10" width="10" key={`add${i}`}>
                 <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth / 2} stroke="invisible" strokeWidth={dotsize / 2} fill="invisible"
                     onPointerDown={(e) => { formatInstance.addPoints(pts.X, pts.Y, apoints, i, controlPoints); }} pointerEvents="all" cursor="all-scroll"
                 />
             </svg>);
+        // Green circles that allow the user to edit the curvature of the line using the selected point as the anchor.
         const handles = handlePoints.map((pts, i) =>
             <svg height="10" width="10" key={`hdl${i}`}>
                 <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth} strokeWidth={0} fill="green"
                     onPointerDown={(e) => this.onControlDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
             </svg>);
-
+        // Points (red circles) of the ink that are made visible to user when editing its format.
         const controls = controlPoints.map((pts, i) =>
             <svg height="10" width="10" key={`ctrl${i}`}>
                 <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth / 2} strokeWidth={0} fill="red"
                     onPointerDown={(e) => { this.changeCurrPoint(pts.I); this.onControlDown(e, pts.I); }} pointerEvents="all" cursor="default"
                 />
             </svg>);
+        // Set of two green lines (each with a handle at the end) that are rendered perpendicular to the current selected point while editing.
         const handleLines = handleLine.map((pts, i) =>
             <svg height="100" width="100" key={`line${i}`}  >
                 <line x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
@@ -181,7 +202,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
             </svg>);
 
-
         return (
             <svg className="inkingStroke"
                 style={{
-- 
cgit v1.2.3-70-g09d2


From b0efa4a390415072eaeb06c8719ea57d73e10466 Mon Sep 17 00:00:00 2001
From: vkalev <50213748+vkalev@users.noreply.github.com>
Date: Wed, 30 Jun 2021 12:52:52 -0500
Subject: ink Bézier handle movement fixed + small visual changes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/client/util/InteractionUtils.tsx    |   2 +-
 src/client/views/InkStrokeProperties.ts | 119 ++++++++++++++++++++++++--------
 src/client/views/InkingStroke.scss      |  18 ++---
 src/client/views/InkingStroke.tsx       |  93 ++++++++++++++-----------
 src/fields/InkField.ts                  |  24 +++++++
 5 files changed, 177 insertions(+), 79 deletions(-)

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 01d00db30..ba935e3bf 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -208,7 +208,7 @@ export namespace InteractionUtils {
             <polyline
                 points={strpts}
                 style={{
-                    filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
+                    // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
                     fill: fill ? fill : "none",
                     opacity: strokeWidth !== width ? 0.5 : undefined,
                     pointerEvents: pevents as any,
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 533fdf006..5c5395984 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -2,12 +2,11 @@ import { action, computed, observable } from "mobx";
 import { ColorState } from 'react-color';
 import { Doc, Field, Opt } from "../../fields/Doc";
 import { Document } from "../../fields/documentSchemas";
-import { InkField, InkData } from "../../fields/InkField";
+import { InkField, InkData, PointData } from "../../fields/InkField";
 import { Cast, NumCast } from "../../fields/Types";
 import { DocumentType } from "../documents/DocumentTypes";
 import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch } from "../util/UndoManager";
-import { bool } from "sharp";
 
 export class InkStrokeProperties {
     static Instance: InkStrokeProperties | undefined;
@@ -15,15 +14,15 @@ export class InkStrokeProperties {
     private _lastFill = "#D0021B";
     private _lastLine = "#D0021B";
     private _lastDash = "2";
-    private _inkDocs: { x: number, y: number, width: number, height: number }[] = [];
 
-    // Indicates whether the ink is locked.
     @observable _lock = false;
-    // Indicates whether the ink's format is being currently edited (displaying of control points).
     @observable _controlBtn = false;
-    // Stores the index of the current selected control point of the ink instance.
     @observable _currPoint = -1;
 
+    constructor() {
+        InkStrokeProperties.Instance = this;
+    }
+
     getField(key: string) {
         return this.selectedInk?.reduce((p, i) =>
             (p === undefined || (p && p === i.rootDoc[key])) && i.rootDoc[key] !== "0" ? Field.toString(i.rootDoc[key] as Field) : "", undefined as Opt<string>);
@@ -79,10 +78,6 @@ export class InkStrokeProperties {
         });
     }
 
-    constructor() {
-        InkStrokeProperties.Instance = this;
-    }
-
     /**
      * Adds a new control point to the ink instance when editing its format.
      * @param x The x-coordinate of the current new point.
@@ -213,35 +208,99 @@ export class InkStrokeProperties {
     }
 
     /**
-     * Handles the movement / scaling of control points of an ink instance.
-     * @param xDiff The movement of the control point's x-coordinate.
-     * @param yDiff The movement of the control point's y-coordinate.
-     * @param controlNum The index of the current control point selected.
-     * @returns The changed x- and y-coordinates of the control points.
+     * Handles the movement/scaling of a control point.
      */
     @undoBatch
     @action
-    control = (xDiff: number, yDiff: number, controlNum: number) =>
-        this.applyFunction((doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => {
+    moveControl = (deltaX: number, deltaY: number, controlIndex: number) =>
+        this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
             const newPoints: { X: number, Y: number }[] = [];
-            const order = controlNum % 4;
+            const order = controlIndex % 4;
             for (var i = 0; i < ink.length; i++) {
-                newPoints.push(
-                    (controlNum === i ||
-                        (order === 0 && i === controlNum + 1) ||
-                        (order === 0 && controlNum !== 0 && i === controlNum - 2) ||
-                        (order === 0 && controlNum !== 0 && i === controlNum - 1) ||
-                        (order === 3 && i === controlNum - 1) ||
-                        (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 1) ||
-                        (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 2) ||
-                        ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlNum === 0 || controlNum === ink.length - 1))
-                    ) ?
-                        { X: ink[i].X - xDiff / ptsXscale, Y: ink[i].Y - yDiff / ptsYscale } :
-                        { X: ink[i].X, Y: ink[i].Y });
+                const leftHandlePoint = order === 0 && i === controlIndex + 1;
+                const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2;
+                if (controlIndex === i ||
+                    leftHandlePoint ||
+                    rightHandlePoint ||
+                    (order === 0 && controlIndex !== 0 && i === controlIndex - 1) ||
+                    (order === 3 && i === controlIndex - 1) ||
+                    (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) ||
+                    (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) ||
+                    ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))) {
+                    newPoints.push({ X: ink[i].X - deltaX / xScale, Y: ink[i].Y - deltaY / yScale });
+                } else {
+                    newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+                }
             }
             return newPoints;
         });
 
+    /**
+     * Rotates the target point about the origin point for a given angle (radians).
+     */
+    @action
+    rotatePoint = (target: PointData, origin: PointData, angle: number) => {
+        target.X -= origin.X;
+        target.Y -= origin.Y;
+        const newX = Math.cos(angle) * target.X - Math.sin(angle) * target.Y;
+        const newY = Math.sin(angle) * target.X + Math.cos(angle) * target.Y;
+        target.X = newX + origin.X;
+        target.Y = newY + origin.Y;
+        return target
+    }
+
+    /**
+     * Finds the angle difference (in radians) between two vectors relative to an arbitrary origin.
+     */
+    angleChange = (a: PointData, b: PointData, origin: PointData) => {
+        // Finding vector representation of inputted points relative to new origin.
+        let vectorA = { X: a.X - origin.X, Y: a.Y - origin.Y };
+        let vectorB = { X: b.X - origin.X, Y: b.Y - origin.Y };
+        const crossProduct = vectorB.X * vectorA.Y - vectorB.Y * vectorA.X;
+        // Determining whether rotation is clockwise or counterclockwise.
+        const sign = crossProduct < 0 ? 1 : -1;
+        const magnitudeA = Math.sqrt(vectorA.X * vectorA.X + vectorA.Y * vectorA.Y);
+        const magnitudeB = Math.sqrt(vectorB.X * vectorB.X + vectorB.Y * vectorB.Y);
+        // Normalizing the vectors.
+        vectorA = { X: vectorA.X / magnitudeA, Y: vectorA.Y / magnitudeA };
+        vectorB = { X: vectorB.X / magnitudeB, Y: vectorB.Y / magnitudeB };
+        const dotProduct = vectorB.X * vectorA.X + vectorB.Y * vectorA.Y;
+        const theta = Math.acos(dotProduct);
+        return sign * theta;
+    }
+
+    /**
+     * Handles the movement/scaling of a handle point.
+     */
+    @undoBatch
+    @action
+    moveHandle = (deltaX: number, deltaY: number, handleIndex: number) =>
+        this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
+            const newPoints: { X: number, Y: number }[] = [];
+            const order = handleIndex % 4;
+            let newHandlePoint = { X: 0, Y: 0 };
+
+            for (var i = 0; i < ink.length; i++) {
+                if (handleIndex === i) {
+                    newHandlePoint = { X: ink[i].X - deltaX / xScale, Y: ink[i].Y - deltaY / yScale };
+                    newPoints.push({ X: newHandlePoint.X, Y: newHandlePoint.Y });
+                } else {
+                    newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+                }
+            }
+
+            if (handleIndex !== 1 && handleIndex !== ink.length - 2) {
+                const oldHandlePoint = ink[handleIndex];
+                let oppositeHandlePoint = order === 1 ? ink[handleIndex - 3] : ink[handleIndex + 3];
+                const controlPoint = order === 1 ? ink[handleIndex - 1] : ink[handleIndex + 1];
+                const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint);
+                oppositeHandlePoint = this.rotatePoint(oppositeHandlePoint, controlPoint, angle);
+                order === 1 ? newPoints[handleIndex - 3] = oppositeHandlePoint : newPoints[handleIndex + 3] = oppositeHandlePoint;
+            }
+
+            return newPoints;
+        });
+
     /**
      * Changes the color of the border of the ink instance.
      * @param color The new hex value to change the border to.
diff --git a/src/client/views/InkingStroke.scss b/src/client/views/InkingStroke.scss
index 30ab1967e..f67b1779d 100644
--- a/src/client/views/InkingStroke.scss
+++ b/src/client/views/InkingStroke.scss
@@ -1,11 +1,11 @@
 .inkingStroke {
-    mix-blend-mode: multiply;
-    stroke-linejoin: round;
-    stroke-linecap: round;
-    overflow: visible !important;
-    transform-origin: top left;
+  mix-blend-mode: multiply;
+  stroke-linejoin: round;
+  stroke-linecap: round;
+  overflow: visible !important;
+  transform-origin: top left;
 
-    svg:not(:root) {
-        overflow: visible !important;
-    }
-}
\ No newline at end of file
+  svg:not(:root) {
+    overflow: visible !important;
+  }
+}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 859e53b97..163eb05b0 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -2,7 +2,7 @@ import { action } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../fields/Doc";
 import { documentSchema } from "../../fields/documentSchemas";
-import { InkData, InkField, InkTool } from "../../fields/InkField";
+import { InkData, InkField, InkTool, ControlPoint, HandlePoint, HandleLine } from "../../fields/InkField";
 import { makeInterface } from "../../fields/Schema";
 import { Cast, StrCast } from "../../fields/Types";
 import { TraceMobx } from "../../fields/util";
@@ -28,33 +28,51 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
 
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
 
-    private analyzeStrokes = () => {
+    analyzeStrokes = () => {
         const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
         CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
     }
 
-    public static toggleMask = action((inkDoc: Doc) => {
+    @action
+    public static toggleMask = (inkDoc: Doc) => {
         inkDoc.isInkMask = !inkDoc.isInkMask;
         inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
         inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
         inkDoc.color = "#9b9b9bff";
         inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
-    });
+    };
 
     /**
      * Handles the movement of a selected control point when the user clicks and drags.
-     * @param e React Pointer Event.
-     * @param controlNum The number of the currently selected control point.
+     * @param controlNum The index of the currently selected control point.
      */
     @action
     onControlDown = (e: React.PointerEvent, controlNum: number): void => {
         if (InkStrokeProperties.Instance) {
-            InkStrokeProperties.Instance.control(0, 0, 1);
+            InkStrokeProperties.Instance.moveControl(0, 0, 1);
+            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
+            const screenScale = this.props.ScreenToLocalTransform().Scale;
+            setupMoveUpEvents(this, e,
+                (e: PointerEvent, down: number[], delta: number[]) => {
+                    InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlNum);
+                    return false;
+                },
+                () => controlUndo?.end(), emptyFunction);
+        }
+    }
+
+    /**
+     * Handles the movement of a selected handle point when the user clicks and drags.
+     * @param controlNum The index of the currently selected handle point.
+     */
+    onHandleDown = (e: React.PointerEvent, handleNum: number): void => {
+        if (InkStrokeProperties.Instance) {
+            InkStrokeProperties.Instance.moveControl(0, 0, 1);
             const controlUndo = UndoManager.StartBatch("DocDecs set radius");
             const screenScale = this.props.ScreenToLocalTransform().Scale;
             setupMoveUpEvents(this, e,
                 (e: PointerEvent, down: number[], delta: number[]) => {
-                    InkStrokeProperties.Instance?.control(-delta[0] * screenScale, -delta[1] * screenScale, controlNum);
+                    InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleNum);
                     return false;
                 },
                 () => controlUndo?.end(), emptyFunction);
@@ -69,7 +87,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
     changeCurrPoint = (i: number) => {
         if (InkStrokeProperties.Instance) {
             InkStrokeProperties.Instance._currPoint = i;
-            document.addEventListener("keydown", this.delPts, true);
+            document.addEventListener("keydown", this.onDelete, true);
         }
     }
 
@@ -78,7 +96,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
      * @param e Keyboard Event.
      */
     @action
-    delPts = (e: KeyboardEvent) => {
+    onDelete = (e: KeyboardEvent) => {
         if (["-", "Backspace", "Delete"].includes(e.key)) {
             if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation();
         }
@@ -101,7 +119,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const formatInstance = InkStrokeProperties.Instance;
         if (!formatInstance) return (null);
         const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
-        // const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()));
         const strokeWidth = Number(this.layoutDoc.strokeWidth);
         const xs = data.map(p => p.X);
         const ys = data.map(p => p.Y);
@@ -118,12 +135,18 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const scaleX = width === strokeWidth ? 1 : (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
         const scaleY = height === strokeWidth ? 1 : (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
         const strokeColor = StrCast(this.layoutDoc.color, "");
+        const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
 
         // Visually renders the polygonal line made by the user.
         const points = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
             StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
             StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
+        
+        const selectedLine = InteractionUtils.CreatePolyline(data, lineLft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6, strokeWidth / 6,
+            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
+            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
+            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
 
         // Invisible polygonal line that enables the ink to be selected by the user.
         const hpoints = InteractionUtils.CreatePolyline(data, left, top,
@@ -136,10 +159,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
             StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
 
-        const controlPoints: { X: number, Y: number, I: number }[] = [];
-        const handlePoints: { X: number, Y: number, I: number, dot1: number, dot2: number }[] = [];
-        const handleLine: { X1: number, Y1: number, X2: number, Y2: number, X3: number, Y3: number, dot1: number, dot2: number }[] = [];
+        const controlPoints: ControlPoint[] = [];
+        const handlePoints: HandlePoint[] = [];
+        const handleLine: HandleLine[] = [];
+        // check the purpose of this conditional
         if (data.length >= 4) {
+
+            // create separate functions for these            
             for (var i = 0; i <= data.length - 4; i += 4) {
                 controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
                 controlPoints.push({ X: data[i + 3].X, Y: data[i + 3].Y, I: i + 3 });
@@ -147,29 +173,21 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                 handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 });
             }
 
-            handleLine.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 });
+            // first (single) handle line 
+            handleLine.push({ X1: data[0].X, Y1: data[0].Y, X2: data[1].X, Y2: data[1].Y, dot1: 0, dot2: 0 });
             for (var i = 2; i < data.length - 4; i += 4) {
 
-                handleLine.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
+                handleLine.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 3].X, Y2: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
 
             }
-            handleLine.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
+            // last (single) handle line
+            handleLine.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
 
             for (var i = 0; i <= data.length - 4; i += 4) {
                 handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? i : i - 1 });
                 handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 });
             }
         }
-        // if (data.length <= 4) {
-        //     handlePoints = [];
-        //     handleLine = [];
-        //     controlPoints = [];
-        //     for (var i = 0; i < data.length; i++) {
-        //         controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
-        //     }
-
-        // }
-        const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
 
         // Additional points (controls) added by the user via click when editing the ink's format.
         const addpoints = apoints.map((pts, i) =>
@@ -178,27 +196,24 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     onPointerDown={(e) => { formatInstance.addPoints(pts.X, pts.Y, apoints, i, controlPoints); }} pointerEvents="all" cursor="all-scroll"
                 />
             </svg>);
-        // Green circles that allow the user to edit the curvature of the line using the selected point as the anchor.
+        // Blue circles that allow the user to edit the curvature of the line using the selected control point as the anchor.
         const handles = handlePoints.map((pts, i) =>
             <svg height="10" width="10" key={`hdl${i}`}>
-                <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth} strokeWidth={0} fill="green"
-                    onPointerDown={(e) => this.onControlDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
+                <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth / 2} strokeWidth={0} fill="#1F85DE"
+                    onPointerDown={(e) => this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
             </svg>);
-        // Points (red circles) of the ink that are made visible to user when editing its format.
+        // Control points of the ink (blue outlined squares) that are made visible to user when editing its format.
         const controls = controlPoints.map((pts, i) =>
             <svg height="10" width="10" key={`ctrl${i}`}>
-                <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth / 2} strokeWidth={0} fill="red"
+                <rect x={(pts.X - left) * scaleX} y={(pts.Y - top) * scaleY} height={strokeWidth} width={strokeWidth} strokeWidth={strokeWidth / 6} stroke="#1F85DE" fill={formatInstance && formatInstance._currPoint === pts.I ? "#1F85DE" : "white"}
                     onPointerDown={(e) => { this.changeCurrPoint(pts.I); this.onControlDown(e, pts.I); }} pointerEvents="all" cursor="default"
                 />
             </svg>);
-        // Set of two green lines (each with a handle at the end) that are rendered perpendicular to the current selected point while editing.
+        // Set of two blue lines (each with a handle at the end) that are rendered perpendicular to the current selected point while editing.
         const handleLines = handleLine.map((pts, i) =>
-            <svg height="100" width="100" key={`line${i}`}  >
+            <svg height="100" width="100" key={`line${i}`}>
                 <line x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
-                    x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" strokeWidth={dotsize / 6}
-                    display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
-                <line x1={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
-                    x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" strokeWidth={dotsize / 6}
+                    x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="#1F85DE" strokeWidth={dotsize / 10}
                     display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
             </svg>);
 
@@ -227,8 +242,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                 {formatInstance._controlBtn && this.props.isSelected() ? addpoints : ""}
                 {formatInstance._controlBtn && this.props.isSelected() ? handleLines : ""}
                 {formatInstance._controlBtn && this.props.isSelected() ? handles : ""}
+                {this.props.isSelected() ? selectedLine : ""}
                 {formatInstance._controlBtn && this.props.isSelected() ? controls : ""}
-
             </svg>
         );
     }
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index b79a03146..c158dac42 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -13,6 +13,7 @@ export enum InkTool {
     Stamp = "stamp"
 }
 
+
 // Defines a point in an ink as a pair of x- and y-coordinates.
 export interface PointData {
     X: number;
@@ -22,6 +23,29 @@ export interface PointData {
 // Defines an ink as an array of points.
 export type InkData = Array<PointData>;
 
+export interface ControlPoint {
+    X: number;
+    Y: number;
+    I: number;
+}
+
+export interface HandlePoint {
+    X: number;
+    Y: number;
+    I: number;
+    dot1: number;
+    dot2: number;
+}
+
+export interface HandleLine {
+    X1: number;
+    Y1: number;
+    X2: number;
+    Y2: number;
+    dot1: number;
+    dot2: number;
+}
+
 const pointSchema = createSimpleSchema({
     X: true, Y: true
 });
-- 
cgit v1.2.3-70-g09d2


From d9b5d949c22658531504c95cc94e30c824519cd1 Mon Sep 17 00:00:00 2001
From: vkalev <50213748+vkalev@users.noreply.github.com>
Date: Thu, 1 Jul 2021 12:44:52 -0500
Subject: ink controls resize on hover

---
 src/client/views/DocumentDecorations.tsx |   2 +-
 src/client/views/InkControl.tsx          | 103 ++++++++++++++++++++++
 src/client/views/InkStrokeProperties.ts  | 141 ++++++++++++++++---------------
 src/client/views/InkingStroke.tsx        |  35 +-------
 4 files changed, 179 insertions(+), 102 deletions(-)
 create mode 100644 src/client/views/InkControl.tsx

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index bf939d57c..d1682c09d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -201,7 +201,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
             (e: PointerEvent, down: number[], delta: number[]) => {
                 const movement = { X: delta[0], Y: e.clientY - down[1] };
                 const angle = Math.max(1, Math.abs(movement.Y / 10));
-                InkStrokeProperties.Instance?.rotate(2 * movement.X / angle * (Math.PI / 180));
+                InkStrokeProperties.Instance?.rotateInk(2 * movement.X / angle * (Math.PI / 180));
                 return false;
             },
             () => this._rotateUndo?.end(),
diff --git a/src/client/views/InkControl.tsx b/src/client/views/InkControl.tsx
new file mode 100644
index 000000000..ae0fc0b22
--- /dev/null
+++ b/src/client/views/InkControl.tsx
@@ -0,0 +1,103 @@
+import React = require("react");
+import { observable, action } from "mobx";
+import { observer } from "mobx-react";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { setupMoveUpEvents, emptyFunction } from "../../Utils";
+import { UndoManager } from "../util/UndoManager";
+import { ControlPoint } from "../../fields/InkField";
+import { Transform } from "../util/Transform";
+
+export interface InkControlProps {
+    control: ControlPoint;
+    left: number;
+    top: number;
+    scaleX: number;
+    scaleY: number;
+    strokeWidth: number;
+    ScreenToLocalTransform: () => Transform;
+}
+
+@observer
+export class InkControl extends React.Component<InkControlProps> {
+    @observable private _overControl = false;
+
+    @action
+    onPointerEnter = () => {
+        this._overControl = true;
+    }
+
+    @action
+    onPointerLeave = () => {
+        this._overControl = false;
+    }
+
+    /**
+     * Handles the movement of a selected control point when the user clicks and drags.
+     * @param controlNum The index of the currently selected control point.
+     */
+    @action
+    onControlDown = (e: React.PointerEvent, controlNum: number): void => {
+        if (InkStrokeProperties.Instance) {
+            InkStrokeProperties.Instance.moveControl(0, 0, 1);
+            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
+            const screenScale = this.props.ScreenToLocalTransform().Scale;
+            setupMoveUpEvents(this, e,
+                (e: PointerEvent, down: number[], delta: number[]) => {
+                    InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlNum);
+                    return false;
+                },
+                () => controlUndo?.end(), emptyFunction);
+        }
+    }
+
+    /**
+     * Deletes the currently selected points.
+     * @param e Keyboard Event.
+     */
+    @action
+    onDelete = (e: KeyboardEvent) => {
+        if (["-", "Backspace", "Delete"].includes(e.key)) {
+            if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation();
+        }
+    }
+
+    /**
+     * Changes the current selected control point.
+     * @param i The number of the point to be selected.
+     */
+    @action
+    changeCurrPoint = (i: number) => {
+        if (InkStrokeProperties.Instance) {
+            InkStrokeProperties.Instance._currPoint = i;
+            document.addEventListener("keydown", this.onDelete, true);
+        }
+    }
+ 
+
+    render() {
+        const control = this.props.control;
+        const left = this.props.left;
+        const top = this.props.top;
+        const scaleX = this.props.scaleX;
+        const scaleY = this.props.scaleY;
+        const strokeWidth = this.props.strokeWidth;
+
+        return (
+            <svg height="10" width="10" key={`ctrl${control.I}`}>
+                <rect 
+                    x={(control.X - left) * scaleX} 
+                    y={(control.Y - top) * scaleY} 
+                    height={this._overControl ? strokeWidth * 1.5 : strokeWidth} 
+                    width={this._overControl ? strokeWidth * 1.5 : strokeWidth} 
+                    strokeWidth={strokeWidth / 6} stroke="#1F85DE" 
+                    fill={InkStrokeProperties.Instance?._currPoint === control.I ? "#1F85DE" : "white"}
+                    onPointerDown={(e) => { this.changeCurrPoint(control.I); this.onControlDown(e, control.I); }} 
+                    onMouseEnter={this.onPointerEnter} 
+                    onMouseLeave={this.onPointerLeave} 
+                    pointerEvents="all" 
+                    cursor="default"
+                />
+            </svg>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 5c5395984..720a89334 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -32,51 +32,52 @@ export class InkStrokeProperties {
         const inks = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK);
         return inks.length ? inks : undefined;
     }
-    @computed get unFilled() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; }
-    @computed get unStrokd() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; }
-    @computed get solidFil() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; }
-    @computed get solidStk() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; }
-    @computed get dashdStk() { return !this.unStrokd && this.getField("strokeDash") || ""; }
-    @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
-    @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
-    @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
-    @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
-    @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
-    @computed get shapeHgt() { return this.getField("_height"); }
-    @computed get shapeWid() { return this.getField("_width"); }
-    @computed get shapeXps() { return this.getField("x"); }
-    @computed get shapeYps() { return this.getField("y"); }
-    @computed get shapeRot() { return this.getField("rotation"); }
-    set unFilled(value) { this.colorFil = value ? "" : this._lastFill; }
-    set solidFil(value) { this.unFilled = !value; }
-    set colorFil(value) { value && (this._lastFill = value); this.selectedInk?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); }
-    set colorStk(value) { value && (this._lastLine = value); this.selectedInk?.forEach(i => i.rootDoc.color = value ? value : undefined); }
-    set markHead(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeStartMarker = value); }
-    set markTail(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeEndMarker = value); }
-    set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
-    set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
-    set dashdStk(value) {
-        value && (this._lastDash = value) && (this.unStrokd = false);
-        this.selectedInk?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined);
-    }
-    set shapeXps(value) { this.selectedInk?.forEach(i => i.rootDoc.x = Number(value)); }
-    set shapeYps(value) { this.selectedInk?.forEach(i => i.rootDoc.y = Number(value)); }
-    set shapeRot(value) { this.selectedInk?.forEach(i => i.rootDoc.rotation = Number(value)); }
-    set widthStk(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = Number(value)); }
-    set shapeWid(value) {
-        this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
-            const oldWidth = NumCast(i.rootDoc._width);
-            i.rootDoc._width = Number(value);
-            this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth);
-        });
-    }
-    set shapeHgt(value) {
-        this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
-            const oldHeight = NumCast(i.rootDoc._height);
-            i.rootDoc._height = Number(value);
-            this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight);
-        });
-    }
+
+    // @computed get unFilled() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; }
+    // @computed get unStrokd() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; }
+    // @computed get solidFil() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; }
+    // @computed get solidStk() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; }
+    // @computed get dashdStk() { return !this.unStrokd && this.getField("strokeDash") || ""; }
+    // @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
+    // @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
+    // @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
+    // @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
+    // @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
+    // @computed get shapeHgt() { return this.getField("_height"); }
+    // @computed get shapeWid() { return this.getField("_width"); }
+    // @computed get shapeXps() { return this.getField("x"); }
+    // @computed get shapeYps() { return this.getField("y"); }
+    // @computed get shapeRot() { return this.getField("rotation"); }
+    // set unFilled(value) { this.colorFil = value ? "" : this._lastFill; }
+    // set solidFil(value) { this.unFilled = !value; }
+    // set colorFil(value) { value && (this._lastFill = value); this.selectedInk?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); }
+    // set colorStk(value) { value && (this._lastLine = value); this.selectedInk?.forEach(i => i.rootDoc.color = value ? value : undefined); }
+    // set markHead(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeStartMarker = value); }
+    // set markTail(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeEndMarker = value); }
+    // set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
+    // set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
+    // set dashdStk(value) {
+    //     value && (this._lastDash = value) && (this.unStrokd = false);
+    //     this.selectedInk?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined);
+    // }
+    // set shapeXps(value) { this.selectedInk?.forEach(i => i.rootDoc.x = Number(value)); }
+    // set shapeYps(value) { this.selectedInk?.forEach(i => i.rootDoc.y = Number(value)); }
+    // set shapeRot(value) { this.selectedInk?.forEach(i => i.rootDoc.rotation = Number(value)); }
+    // set widthStk(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = Number(value)); }
+    // set shapeWid(value) {
+    //     this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+    //         const oldWidth = NumCast(i.rootDoc._width);
+    //         i.rootDoc._width = Number(value);
+    //         this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth);
+    //     });
+    // }
+    // set shapeHgt(value) {
+    //     this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+    //         const oldHeight = NumCast(i.rootDoc._height);
+    //         i.rootDoc._height = Number(value);
+    //         this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight);
+    //     });
+    // }
 
     /**
      * Adds a new control point to the ink instance when editing its format.
@@ -191,7 +192,7 @@ export class InkStrokeProperties {
      */
     @undoBatch
     @action
-    rotate = (angle: number) => {
+    rotateInk = (angle: number) => {
         this.applyFunction((doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => {
             const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X));
             const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
@@ -301,29 +302,29 @@ export class InkStrokeProperties {
             return newPoints;
         });
 
-    /**
-     * Changes the color of the border of the ink instance.
-     * @param color The new hex value to change the border to.
-     * @returns true.
-     */
-    @undoBatch
-    @action
-    switchStk = (color: ColorState) => {
-        const val = String(color.hex);
-        this.colorStk = val;
-        return true;
-    }
+    // /**
+    //  * Changes the color of the border of the ink instance.
+    //  * @param color The new hex value to change the border to.
+    //  * @returns true.
+    //  */
+    // @undoBatch
+    // @action
+    // switchStk = (color: ColorState) => {
+    //     const val = String(color.hex);
+    //     this.colorStk = val;
+    //     return true;
+    // }
 
-    /**
-     * Changes the color of the fill of the ink instance.
-     * @param color The new hex value to change the fill to.
-     * @returns true.
-     */
-    @undoBatch
-    @action
-    switchFil = (color: ColorState) => {
-        const val = String(color.hex);
-        this.colorFil = val;
-        return true;
-    }
+    // /**
+    //  * Changes the color of the fill of the ink instance.
+    //  * @param color The new hex value to change the fill to.
+    //  * @returns true.
+    //  */
+    // @undoBatch
+    // @action
+    // switchFil = (color: ColorState) => {
+    //     const val = String(color.hex);
+    //     this.colorFil = val;
+    //     return true;
+    // }
 }
\ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 163eb05b0..9555557ec 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -18,6 +18,7 @@ import { FieldView, FieldViewProps } from "./nodes/FieldView";
 import React = require("react");
 import { InkStrokeProperties } from "./InkStrokeProperties";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
+import { InkControl } from "./InkControl";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
@@ -79,29 +80,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         }
     }
 
-    /**
-     * Changes the current selected control point.
-     * @param i The number of the point to be selected.
-     */
-    @action
-    changeCurrPoint = (i: number) => {
-        if (InkStrokeProperties.Instance) {
-            InkStrokeProperties.Instance._currPoint = i;
-            document.addEventListener("keydown", this.onDelete, true);
-        }
-    }
-
-    /**
-     * Deletes the currently selected points.
-     * @param e Keyboard Event.
-     */
-    @action
-    onDelete = (e: KeyboardEvent) => {
-        if (["-", "Backspace", "Delete"].includes(e.key)) {
-            if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation();
-        }
-    }
-
     /**
      * Handles the movement of the entire ink object when the user clicks and drags.
      * @param e React Pointer Event.
@@ -113,7 +91,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         }
     }
 
-    public static MaskDim = 50000;
+    static readonly MaskDim = 50000;
     render() {
         TraceMobx();
         const formatInstance = InkStrokeProperties.Instance;
@@ -143,7 +121,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
             StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
         
-        const selectedLine = InteractionUtils.CreatePolyline(data, lineLft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6, strokeWidth / 6,
+        const selectedLine = InteractionUtils.CreatePolyline(data, lineLft - strokeWidth * 4 * scaleX, lineTop - strokeWidth * 4 * scaleX, "#1F85DE", strokeWidth / 6, strokeWidth / 6,
             StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
             StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
@@ -203,12 +181,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     onPointerDown={(e) => this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
             </svg>);
         // Control points of the ink (blue outlined squares) that are made visible to user when editing its format.
-        const controls = controlPoints.map((pts, i) =>
-            <svg height="10" width="10" key={`ctrl${i}`}>
-                <rect x={(pts.X - left) * scaleX} y={(pts.Y - top) * scaleY} height={strokeWidth} width={strokeWidth} strokeWidth={strokeWidth / 6} stroke="#1F85DE" fill={formatInstance && formatInstance._currPoint === pts.I ? "#1F85DE" : "white"}
-                    onPointerDown={(e) => { this.changeCurrPoint(pts.I); this.onControlDown(e, pts.I); }} pointerEvents="all" cursor="default"
-                />
-            </svg>);
+        const controls = controlPoints.map((pts, i) => <InkControl control={pts} left={left} top={top} scaleX={scaleX} scaleY={scaleY} strokeWidth={strokeWidth} ScreenToLocalTransform={this.props.ScreenToLocalTransform} /> );
         // Set of two blue lines (each with a handle at the end) that are rendered perpendicular to the current selected point while editing.
         const handleLines = handleLine.map((pts, i) =>
             <svg height="100" width="100" key={`line${i}`}>
-- 
cgit v1.2.3-70-g09d2


From 89c8891e9def96a871d36ab7772e453b8d8c21c1 Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Fri, 2 Jul 2021 11:58:06 -0500
Subject: indicator appears on hover for ink control points to be added

---
 src/client/views/InkControl.tsx   |  8 +++-----
 src/client/views/InkingStroke.tsx | 23 ++++++++++++++++++-----
 2 files changed, 21 insertions(+), 10 deletions(-)

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/views/InkControl.tsx b/src/client/views/InkControl.tsx
index ae0fc0b22..accb16f61 100644
--- a/src/client/views/InkControl.tsx
+++ b/src/client/views/InkControl.tsx
@@ -51,8 +51,7 @@ export class InkControl extends React.Component<InkControlProps> {
     }
 
     /**
-     * Deletes the currently selected points.
-     * @param e Keyboard Event.
+     * Deletes the currently selected point.
      */
     @action
     onDelete = (e: KeyboardEvent) => {
@@ -63,7 +62,6 @@ export class InkControl extends React.Component<InkControlProps> {
 
     /**
      * Changes the current selected control point.
-     * @param i The number of the point to be selected.
      */
     @action
     changeCurrPoint = (i: number) => {
@@ -85,8 +83,8 @@ export class InkControl extends React.Component<InkControlProps> {
         return (
             <svg height="10" width="10" key={`ctrl${control.I}`}>
                 <rect 
-                    x={(control.X - left) * scaleX} 
-                    y={(control.Y - top) * scaleY} 
+                    x={(control.X - left - strokeWidth / 2) * scaleX} 
+                    y={(control.Y - top - strokeWidth / 2) * scaleY} 
                     height={this._overControl ? strokeWidth * 1.5 : strokeWidth} 
                     width={this._overControl ? strokeWidth * 1.5 : strokeWidth} 
                     strokeWidth={strokeWidth / 6} stroke="#1F85DE" 
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 9555557ec..b2ef76882 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,4 +1,4 @@
-import { action } from "mobx";
+import { action, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../fields/Doc";
 import { documentSchema } from "../../fields/documentSchemas";
@@ -26,6 +26,9 @@ const InkDocument = makeInterface(documentSchema);
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
     private _controlUndo?: UndoManager.Batch;
+    @observable private _overAddPoint = -1;
+
+    static readonly MaskDim = 50000;
 
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
 
@@ -34,6 +37,17 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
     }
 
+    @action
+    onPointerEnter = (i: number) => {
+        this._overAddPoint = i;
+    }
+
+    @action
+    onPointerLeave = () => {
+        this._overAddPoint = -1;
+    }
+
+
     @action
     public static toggleMask = (inkDoc: Doc) => {
         inkDoc.isInkMask = !inkDoc.isInkMask;
@@ -91,7 +105,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         }
     }
 
-    static readonly MaskDim = 50000;
     render() {
         TraceMobx();
         const formatInstance = InkStrokeProperties.Instance;
@@ -121,7 +134,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
             StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
         
-        const selectedLine = InteractionUtils.CreatePolyline(data, lineLft - strokeWidth * 4 * scaleX, lineTop - strokeWidth * 4 * scaleX, "#1F85DE", strokeWidth / 6, strokeWidth / 6,
+        const selectedLine = InteractionUtils.CreatePolyline(data, lineLft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6, strokeWidth / 6,
             StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
             StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
@@ -170,8 +183,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         // Additional points (controls) added by the user via click when editing the ink's format.
         const addpoints = apoints.map((pts, i) =>
             <svg height="10" width="10" key={`add${i}`}>
-                <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth / 2} stroke="invisible" strokeWidth={dotsize / 2} fill="invisible"
-                    onPointerDown={(e) => { formatInstance.addPoints(pts.X, pts.Y, apoints, i, controlPoints); }} pointerEvents="all" cursor="all-scroll"
+                <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth / 2} stroke={this._overAddPoint === i ? "#1F85DE" : "invisible"} strokeWidth={dotsize / 4} fill={this._overAddPoint === i ? "#1F85DE" : "invisible"}
+                    onPointerDown={(e) => { formatInstance.addPoints(pts.X, pts.Y, apoints, i, controlPoints); }} onMouseEnter={() => this.onPointerEnter(i)} onMouseLeave={this.onPointerLeave} pointerEvents="all" cursor="all-scroll"
                 />
             </svg>);
         // Blue circles that allow the user to edit the curvature of the line using the selected control point as the anchor.
-- 
cgit v1.2.3-70-g09d2


From a0207bf861908da9235a1752a723e69ecdbba734 Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Tue, 6 Jul 2021 12:45:37 -0500
Subject: refactoring

---
 src/client/documents/Documents.ts                  |   8 +-
 src/client/views/GestureOverlay.tsx                |   2 +-
 src/client/views/GlobalKeyHandler.ts               |   2 +-
 src/client/views/InkControl.tsx                    | 101 --------
 src/client/views/InkControls.tsx                   | 118 +++++++++
 src/client/views/InkHandles.tsx                    |  84 +++++++
 src/client/views/InkStroke.scss                    |  11 +
 src/client/views/InkStroke.tsx                     | 187 ++++++++++++++
 src/client/views/InkStrokeProperties.ts            | 205 +++++-----------
 src/client/views/InkingStroke.scss                 |  11 -
 src/client/views/InkingStroke.tsx                  | 271 ---------------------
 src/client/views/PropertiesButtons.tsx             |   4 +-
 src/client/views/PropertiesView.tsx                |   4 +-
 src/client/views/collections/CollectionMenu.tsx    |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   4 +-
 src/client/views/nodes/ColorBox.tsx                |   2 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   4 +-
 src/client/views/nodes/DocumentView.tsx            |   6 +-
 19 files changed, 478 insertions(+), 550 deletions(-)
 delete mode 100644 src/client/views/InkControl.tsx
 create mode 100644 src/client/views/InkControls.tsx
 create mode 100644 src/client/views/InkHandles.tsx
 create mode 100644 src/client/views/InkStroke.scss
 create mode 100644 src/client/views/InkStroke.tsx
 delete mode 100644 src/client/views/InkingStroke.scss
 delete mode 100644 src/client/views/InkingStroke.tsx

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 24682cbd0..188fe31c0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -31,7 +31,7 @@ import { CollectionView, CollectionViewType } from "../views/collections/Collect
 import { ContextMenu } from "../views/ContextMenu";
 import { ContextMenuProps } from "../views/ContextMenuItem";
 import { DFLT_IMAGE_NATIVE_DIM } from "../views/globalCssVariables.scss";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from "../views/InkingStroke";
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkStroke } from "../views/InkStroke";
 import { AudioBox } from "../views/nodes/AudioBox";
 import { ColorBox } from "../views/nodes/ColorBox";
 import { ComparisonBox } from "../views/nodes/ComparisonBox";
@@ -432,7 +432,7 @@ export namespace Docs {
                 options: { links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true }
             }],
             [DocumentType.INK, {
-                layout: { view: InkingStroke, dataField: defaultDataKey },
+                layout: { view: InkStroke, dataField: defaultDataKey },
                 options: { _fontFamily: "cursive", backgroundColor: "transparent", links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.SCREENSHOT, {
@@ -760,7 +760,7 @@ export namespace Docs {
             const I = new Doc();
             I[Initializing] = true;
             I.type = DocumentType.INK;
-            I.layout = InkingStroke.LayoutString("data");
+            I.layout = InkStroke.LayoutString("data");
             I.color = color;
             I.fillColor = fillColor;
             I.strokeWidth = Number(strokeWidth);
@@ -1144,7 +1144,7 @@ export namespace DocUtils {
             layout = AudioBox.LayoutString;
         } else if (field instanceof InkField) {
             created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved);
-            layout = InkingStroke.LayoutString;
+            layout = InkStroke.LayoutString;
         } else if (field instanceof List && field[0] instanceof Doc) {
             created = Docs.Create.StackingDocument(DocListCast(field), resolved);
             layout = CollectionView.LayoutString;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 491bf18b2..455b3a935 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -17,7 +17,7 @@ import { Scripting } from "../util/Scripting";
 import { Transform } from "../util/Transform";
 import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
 import "./GestureOverlay.scss";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke";
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "./InkStroke";
 import { DocumentView } from "./nodes/DocumentView";
 import { RadialMenu } from "./nodes/RadialMenu";
 import HorizontalPalette from "./Palette";
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index cbaa706e0..c1130ce61 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -116,7 +116,7 @@ export class KeyManager {
             case "escape":
                 DocumentLinksButton.StartLink = undefined;
                 DocumentLinksButton.StartLinkView = undefined;
-                InkStrokeProperties.Instance && (InkStrokeProperties.Instance._controlBtn = false);
+                InkStrokeProperties.Instance && (InkStrokeProperties.Instance._controlButton = false);
                 CurrentUserUtils.SelectedTool = InkTool.None;
                 var doDeselect = true;
                 if (SnappingManager.GetIsDragging()) {
diff --git a/src/client/views/InkControl.tsx b/src/client/views/InkControl.tsx
deleted file mode 100644
index accb16f61..000000000
--- a/src/client/views/InkControl.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import React = require("react");
-import { observable, action } from "mobx";
-import { observer } from "mobx-react";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { setupMoveUpEvents, emptyFunction } from "../../Utils";
-import { UndoManager } from "../util/UndoManager";
-import { ControlPoint } from "../../fields/InkField";
-import { Transform } from "../util/Transform";
-
-export interface InkControlProps {
-    control: ControlPoint;
-    left: number;
-    top: number;
-    scaleX: number;
-    scaleY: number;
-    strokeWidth: number;
-    ScreenToLocalTransform: () => Transform;
-}
-
-@observer
-export class InkControl extends React.Component<InkControlProps> {
-    @observable private _overControl = false;
-
-    @action
-    onPointerEnter = () => {
-        this._overControl = true;
-    }
-
-    @action
-    onPointerLeave = () => {
-        this._overControl = false;
-    }
-
-    /**
-     * Handles the movement of a selected control point when the user clicks and drags.
-     * @param controlNum The index of the currently selected control point.
-     */
-    @action
-    onControlDown = (e: React.PointerEvent, controlNum: number): void => {
-        if (InkStrokeProperties.Instance) {
-            InkStrokeProperties.Instance.moveControl(0, 0, 1);
-            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
-            const screenScale = this.props.ScreenToLocalTransform().Scale;
-            setupMoveUpEvents(this, e,
-                (e: PointerEvent, down: number[], delta: number[]) => {
-                    InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlNum);
-                    return false;
-                },
-                () => controlUndo?.end(), emptyFunction);
-        }
-    }
-
-    /**
-     * Deletes the currently selected point.
-     */
-    @action
-    onDelete = (e: KeyboardEvent) => {
-        if (["-", "Backspace", "Delete"].includes(e.key)) {
-            if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation();
-        }
-    }
-
-    /**
-     * Changes the current selected control point.
-     */
-    @action
-    changeCurrPoint = (i: number) => {
-        if (InkStrokeProperties.Instance) {
-            InkStrokeProperties.Instance._currPoint = i;
-            document.addEventListener("keydown", this.onDelete, true);
-        }
-    }
- 
-
-    render() {
-        const control = this.props.control;
-        const left = this.props.left;
-        const top = this.props.top;
-        const scaleX = this.props.scaleX;
-        const scaleY = this.props.scaleY;
-        const strokeWidth = this.props.strokeWidth;
-
-        return (
-            <svg height="10" width="10" key={`ctrl${control.I}`}>
-                <rect 
-                    x={(control.X - left - strokeWidth / 2) * scaleX} 
-                    y={(control.Y - top - strokeWidth / 2) * scaleY} 
-                    height={this._overControl ? strokeWidth * 1.5 : strokeWidth} 
-                    width={this._overControl ? strokeWidth * 1.5 : strokeWidth} 
-                    strokeWidth={strokeWidth / 6} stroke="#1F85DE" 
-                    fill={InkStrokeProperties.Instance?._currPoint === control.I ? "#1F85DE" : "white"}
-                    onPointerDown={(e) => { this.changeCurrPoint(control.I); this.onControlDown(e, control.I); }} 
-                    onMouseEnter={this.onPointerEnter} 
-                    onMouseLeave={this.onPointerLeave} 
-                    pointerEvents="all" 
-                    cursor="default"
-                />
-            </svg>
-        )
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx
new file mode 100644
index 000000000..eeddfce4c
--- /dev/null
+++ b/src/client/views/InkControls.tsx
@@ -0,0 +1,118 @@
+import React = require("react");
+import { observable, action } from "mobx";
+import { observer } from "mobx-react";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { setupMoveUpEvents, emptyFunction } from "../../Utils";
+import { UndoManager } from "../util/UndoManager";
+import { ControlPoint, InkData, PointData } from "../../fields/InkField";
+import { Transform } from "../util/Transform";
+
+export interface InkControlProps {
+    data: InkData;
+    addedPoints: PointData[];
+    format: number[];
+    ScreenToLocalTransform: () => Transform;
+}
+
+@observer
+export class InkControls extends React.Component<InkControlProps> {
+    // @observable private _controlPoints: ControlPoint[] = [];
+    @observable private _overControl = -1;
+    @observable private _overAddPoint = -1;
+
+    /**
+     * Handles the movement of a selected control point when the user clicks and drags.
+     * @param controlNum The index of the currently selected control point.
+     */
+    @action
+    onControlDown = (e: React.PointerEvent, controlNum: number): void => {
+        if (InkStrokeProperties.Instance) {
+            InkStrokeProperties.Instance.moveControl(0, 0, 1);
+            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
+            const screenScale = this.props.ScreenToLocalTransform().Scale;
+            setupMoveUpEvents(this, e,
+                (e: PointerEvent, down: number[], delta: number[]) => {
+                    InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlNum);
+                    return false;
+                },
+                () => controlUndo?.end(), emptyFunction);
+        }
+    }
+
+    /**
+     * Deletes the currently selected point.
+     */
+    @action
+    onDelete = (e: KeyboardEvent) => {
+        if (["-", "Backspace", "Delete"].includes(e.key)) {
+            if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation();
+        }
+    }
+
+    /**
+     * Changes the current selected control point.
+     */
+    @action
+    changeCurrPoint = (i: number) => {
+        if (InkStrokeProperties.Instance) {
+            InkStrokeProperties.Instance._currentPoint = i;
+            document.addEventListener("keydown", this.onDelete, true);
+        }
+    }
+
+    @action onEnterControl = (i: number) => { this._overControl = i; };
+    @action onLeaveControl = () => { this._overControl = -1; };
+    @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; };
+    @action onLeaveAddPoint = () => { this._overAddPoint = -1; };
+ 
+    render() {
+        const data = this.props.data;
+        const controlPoints: ControlPoint[] = [];
+        if (data.length >= 4) {
+            for (let i = 0; i <= data.length - 4; i += 4) {
+                controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
+                controlPoints.push({ X: data[i + 3].X, Y: data[i + 3].Y, I: i + 3 });
+            }
+        }
+        const addedPoints = this.props.addedPoints;
+        const [left, top, scaleX, scaleY, strokeWidth, dotsize] = this.props.format;
+
+        return (
+            <>
+                {addedPoints.map((pts, i) =>
+                    <svg height="10" width="10" key={`add${i}`}>
+                        <circle 
+                            cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} 
+                            cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} 
+                            r={strokeWidth / 2} 
+                            stroke={this._overAddPoint === i ? "#1F85DE" : "transparent"} 
+                            strokeWidth={dotsize / 4} fill={this._overAddPoint === i ? "#1F85DE" : "transparent"}
+                            onPointerDown={() => { InkStrokeProperties.Instance?.addPoints(pts.X, pts.Y, addedPoints, i, controlPoints); }} 
+                            onMouseEnter={() => this.onEnterAddPoint(i)} 
+                            onMouseLeave={this.onLeaveAddPoint} 
+                            pointerEvents="all" 
+                            cursor="all-scroll"
+                        />
+                    </svg>
+                )}
+                {controlPoints.map((control, i) =>
+                    <svg height="10" width="10" key={`ctrl${i}`}>
+                        <rect 
+                            x={(control.X - left - strokeWidth / 2) * scaleX} 
+                            y={(control.Y - top - strokeWidth / 2) * scaleY} 
+                            height={this._overControl === i ? strokeWidth * 1.5 : strokeWidth} 
+                            width={this._overControl === i ? strokeWidth * 1.5 : strokeWidth} 
+                            strokeWidth={strokeWidth / 6} stroke="#1F85DE" 
+                            fill={InkStrokeProperties.Instance?._currentPoint === control.I ? "#1F85DE" : "white"}
+                            onPointerDown={(e) => { this.changeCurrPoint(control.I); this.onControlDown(e, control.I); }} 
+                            onMouseEnter={() => this.onEnterControl(i)} 
+                            onMouseLeave={this.onLeaveControl} 
+                            pointerEvents="all" 
+                            cursor="default"
+                        />
+                    </svg>
+                )}
+            </>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx
new file mode 100644
index 000000000..c2163c124
--- /dev/null
+++ b/src/client/views/InkHandles.tsx
@@ -0,0 +1,84 @@
+import React = require("react");
+import { observable, action } from "mobx";
+import { observer } from "mobx-react";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { setupMoveUpEvents, emptyFunction } from "../../Utils";
+import { UndoManager } from "../util/UndoManager";
+import { InkData, HandlePoint, HandleLine } from "../../fields/InkField";
+import { Transform } from "../util/Transform";
+
+export interface InkControlProps {
+    data: InkData;
+    format: number[];
+    ScreenToLocalTransform: () => Transform;
+}
+
+@observer
+export class InkHandles extends React.Component<InkControlProps> {
+    /**
+     * Handles the movement of a selected handle point when the user clicks and drags.
+     * @param handleNum The index of the currently selected handle point.
+     */
+     onHandleDown = (e: React.PointerEvent, handleNum: number): void => {
+        if (InkStrokeProperties.Instance) {
+            InkStrokeProperties.Instance.moveControl(0, 0, 1);
+            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
+            const screenScale = this.props.ScreenToLocalTransform().Scale;
+            setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
+                    InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleNum);
+                    return false;
+                }, () => controlUndo?.end(), emptyFunction
+            );
+        }
+    }
+ 
+    render() {
+        const formatInstance = InkStrokeProperties.Instance;
+        if (!formatInstance) return (null);
+        const data = this.props.data;
+        const handlePoints: HandlePoint[] = [];
+        const handleLines: HandleLine[] = [];
+        if (data.length >= 4) {
+            // adding first and last (single) handle lines
+            handleLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[1].X, Y2: data[1].Y, dot1: 0, dot2: 0 });
+            handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
+            for (let i = 0; i <= data.length - 4; i += 4) {
+                handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? i : i - 1 });
+                handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 });
+            }
+            for (let i = 2; i < data.length - 4; i += 4) {
+                handleLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 3].X, Y2: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
+            }
+        }
+        const [left, top, scaleX, scaleY, strokeWidth, dotsize] = this.props.format;
+
+        return (
+            <>
+                {handlePoints.map((pts, i) =>
+                    <svg height="10" width="10" key={`hdl${i}`}>
+                        <circle 
+                            cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} 
+                            cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} 
+                            r={strokeWidth / 2} 
+                            strokeWidth={0} 
+                            fill="#1F85DE"
+                            onPointerDown={(e) => this.onHandleDown(e, pts.I)} 
+                            pointerEvents="all" 
+                            cursor="default" 
+                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
+                    </svg>)}
+                {handleLines.map((pts, i) =>
+                    <svg height="100" width="100" key={`line${i}`}>
+                        <line 
+                            x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} 
+                            y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
+                            x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} 
+                            y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} 
+                            stroke="#1F85DE" 
+                            strokeWidth={dotsize / 8}
+                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
+                    </svg>)}
+            </>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss
new file mode 100644
index 000000000..812a79bd5
--- /dev/null
+++ b/src/client/views/InkStroke.scss
@@ -0,0 +1,11 @@
+.inkStroke {
+  mix-blend-mode: multiply;
+  stroke-linejoin: round;
+  stroke-linecap: round;
+  overflow: visible !important;
+  transform-origin: top left;
+
+  svg:not(:root) {
+    overflow: visible !important;
+  }
+}
diff --git a/src/client/views/InkStroke.tsx b/src/client/views/InkStroke.tsx
new file mode 100644
index 000000000..9ca8bdbea
--- /dev/null
+++ b/src/client/views/InkStroke.tsx
@@ -0,0 +1,187 @@
+import React = require("react");
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Doc } from "../../fields/Doc";
+import { documentSchema } from "../../fields/documentSchemas";
+import { InkData, InkField, InkTool, ControlPoint, HandlePoint, HandleLine } from "../../fields/InkField";
+import { makeInterface } from "../../fields/Schema";
+import { Cast, StrCast } from "../../fields/Types";
+import { TraceMobx } from "../../fields/util";
+import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
+import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { InteractionUtils } from "../util/InteractionUtils";
+import { Scripting } from "../util/Scripting";
+import { UndoManager } from "../util/UndoManager";
+import { ContextMenu } from "./ContextMenu";
+import { ViewBoxBaseComponent } from "./DocComponent";
+import "./InkStroke.scss";
+import { FieldView, FieldViewProps } from "./nodes/FieldView";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
+import { InkControls } from "./InkControls";
+import { InkHandles } from "./InkHandles";
+
+type InkDocument = makeInterface<[typeof documentSchema]>;
+const InkDocument = makeInterface(documentSchema);
+
+@observer
+export class InkStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
+    static readonly MaskDim = 50000;
+    @observable private _properties?: InkStrokeProperties;
+
+    constructor(props: FieldViewProps & InkDocument) {
+        super(props);
+
+        this._properties = InkStrokeProperties.Instance;
+    }
+
+    public static LayoutString(fieldStr: string) { 
+        return FieldView.LayoutString(InkStroke, fieldStr); 
+    }
+
+    analyzeStrokes() {
+        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+        CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
+    }
+
+    @action
+    public static toggleMask = (inkDoc: Doc) => {
+        inkDoc.isInkMask = !inkDoc.isInkMask;
+        inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
+        inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
+        inkDoc.color = "#9b9b9bff";
+        inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
+    }
+
+    /**
+     * Handles the movement of the entire ink object when the user clicks and drags.
+     * @param e React Pointer Event.
+     */
+    onPointerDown = (e: React.PointerEvent) => {
+        if (this.props.isSelected(true)) {
+            setupMoveUpEvents(this, e, returnFalse, emptyFunction, 
+                action((e: PointerEvent, doubleTap: boolean | undefined) =>
+                doubleTap && this._properties && (this._properties._controlButton = true))
+            );
+        }
+    }
+
+    render() {
+        TraceMobx();
+
+        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+        const strokeWidth = Number(this.layoutDoc.strokeWidth);
+        const lineTop = Math.min(...data.map(p => p.Y));
+        const lineBottom = Math.max(...data.map(p => p.Y));
+        const lineLeft = Math.min(...data.map(p => p.X));
+        const lineRight = Math.max(...data.map(p => p.X));
+        const left = lineLeft - strokeWidth / 2;
+        const top = lineTop - strokeWidth / 2;
+        const right = lineRight + strokeWidth / 2;
+        const bottom = lineBottom + strokeWidth / 2;
+        const width = Math.max(1, right - left);
+        const height = Math.max(1, bottom - top);
+        const scaleX = width === strokeWidth ? 1 : (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
+        const scaleY = height === strokeWidth ? 1 : (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
+        const strokeColor = StrCast(this.layoutDoc.color, "");
+        const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
+
+        // Visually renders the polygonal line made by the user.
+        const inkLine = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth, 
+            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), 
+            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), 
+            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 
+            this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, 
+            false);
+        const selectedLine = InteractionUtils.CreatePolyline(
+            data, lineLeft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6, 
+            strokeWidth / 6,StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
+            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
+            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 
+            this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, 
+            false);
+        // Invisible polygonal line that enables the ink to be selected by the user.
+        const clickableLine = InteractionUtils.CreatePolyline(data, left, top, 
+            this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, 
+            strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier), 
+            StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "", 
+            this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
+        const addedPoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth,
+            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
+            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
+            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 
+            this.props.isSelected() && strokeWidth <= 5, false);
+
+        return (
+            <svg className="inkStroke"
+                style={{
+                    pointerEvents: this.props.Document.isInkMask && this.props.layerProvider?.(this.props.Document) !== false ? "all" : "none",
+                    transform: this.props.Document.isInkMask ? `translate(${InkStroke.MaskDim / 2}px, ${InkStroke.MaskDim / 2}px)` : undefined,
+                    mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
+                    overflow: "visible",
+                }}
+                onPointerDown={this.onPointerDown}
+                onContextMenu={() => {
+                    const cm = ContextMenu.Instance;
+                    if (cm) {
+                        !Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
+                        cm.addItem({ description: "Toggle Mask", event: () => InkStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
+                        cm.addItem({ description: "Edit Points", event: action(() => {if (this._properties) { this._properties._controlButton = !this._properties._controlButton; }} ), icon: "paint-brush" });
+                    }
+                }}
+            >
+                
+                {clickableLine}
+                {inkLine}
+                {this.props.isSelected() ? selectedLine : ""}
+                {this.props.isSelected() && this._properties?._controlButton ? 
+                    <>
+                        <InkControls 
+                            data={data} 
+                            addedPoints={addedPoints} 
+                            format={[left, top, scaleX, scaleY, strokeWidth, dotsize]} 
+                            ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
+                        <InkHandles 
+                            data={data} 
+                            format={[left, top, scaleX, scaleY, strokeWidth, dotsize]} 
+                            ScreenToLocalTransform={this.props.ScreenToLocalTransform} /> 
+                    </> : ""}
+            </svg>
+        );
+    }
+}
+
+
+export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); }
+export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); }
+export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); }
+export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); }
+export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); }
+export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); }
+export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
+export function ActiveInkPen(): Doc { return Doc.UserDoc(); }
+export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
+export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); }
+export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
+export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
+export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
+export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); }
+export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
+Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
+    CurrentUserUtils.SelectedTool = pen ? InkTool.Highlighter : InkTool.None;
+    SetActiveInkWidth(width);
+    SetActiveInkColor(color);
+    SetActiveFillColor(fill);
+    SetActiveArrowStart(arrowStart);
+    SetActiveArrowEnd(arrowEnd);
+    SetActiveDash(dash);
+});
+Scripting.addGlobal(function activateEraser(pen: any) { return CurrentUserUtils.SelectedTool = pen ? InkTool.Eraser : InkTool.None; });
+Scripting.addGlobal(function activateStamp(pen: any) { return CurrentUserUtils.SelectedTool = pen ? InkTool.Stamp : InkTool.None; });
+Scripting.addGlobal(function deactivateInk() { return CurrentUserUtils.SelectedTool = InkTool.None; });
+Scripting.addGlobal(function setInkWidth(width: any) { return SetActiveInkWidth(width); });
+Scripting.addGlobal(function setInkColor(color: any) { return SetActiveInkColor(color); });
+Scripting.addGlobal(function setFillColor(fill: any) { return SetActiveFillColor(fill); });
+Scripting.addGlobal(function setActiveArrowStart(arrowStart: any) { return SetActiveArrowStart(arrowStart); });
+Scripting.addGlobal(function setActiveArrowEnd(arrowEnd: any) { return SetActiveArrowStart(arrowEnd); });
+Scripting.addGlobal(function setActiveDash(dash: any) { return SetActiveDash(dash); });
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 720a89334..a5c028730 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,5 +1,4 @@
 import { action, computed, observable } from "mobx";
-import { ColorState } from 'react-color';
 import { Doc, Field, Opt } from "../../fields/Doc";
 import { Document } from "../../fields/documentSchemas";
 import { InkField, InkData, PointData } from "../../fields/InkField";
@@ -11,80 +10,62 @@ import { undoBatch } from "../util/UndoManager";
 export class InkStrokeProperties {
     static Instance: InkStrokeProperties | undefined;
 
-    private _lastFill = "#D0021B";
-    private _lastLine = "#D0021B";
-    private _lastDash = "2";
-
     @observable _lock = false;
-    @observable _controlBtn = false;
-    @observable _currPoint = -1;
+    @observable _controlButton = false;
+    @observable _currentPoint = -1;
 
     constructor() {
         InkStrokeProperties.Instance = this;
     }
 
+    @computed get selectedInk() {
+        const inks = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK);
+        return inks.length ? inks : undefined;
+    }
+
     getField(key: string) {
         return this.selectedInk?.reduce((p, i) =>
             (p === undefined || (p && p === i.rootDoc[key])) && i.rootDoc[key] !== "0" ? Field.toString(i.rootDoc[key] as Field) : "", undefined as Opt<string>);
     }
 
-    @computed get selectedInk() {
-        const inks = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK);
-        return inks.length ? inks : undefined;
+    /**
+     * Helper function that enables other functions to be applied to a particular ink instance.
+     * @param func The inputted function.
+     * @param requireCurrPoint Indicates whether the current selected point is needed.
+     */
+     applyFunction = (func: (doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => {
+        var appliedFunc = false;
+        this.selectedInk?.forEach(action(inkView => {
+            if (this.selectedInk?.length === 1 && (!requireCurrPoint || this._currentPoint !== -1)) {
+                const doc = Document(inkView.rootDoc);
+                if (doc.type === DocumentType.INK && doc.width && doc.height) {
+                    const ink = Cast(doc.data, InkField)?.inkData;
+                    if (ink) {
+                        const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X));
+                        const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
+                        const ptsXscale = NumCast(doc._width) / (oldXrange.max - oldXrange.min);
+                        const ptsYscale = NumCast(doc._height) / (oldYrange.max - oldYrange.min);
+                        const newPoints = func(doc, ink, ptsXscale, ptsYscale);
+                        if (newPoints) {
+                            const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X));
+                            const newYrange = (ys => ({ min: Math.min(...ys), max: Math.max(...ys) }))(newPoints.map(p => p.Y));
+                            doc._width = (newXrange.max - newXrange.min) * ptsXscale;
+                            doc._height = (newYrange.max - newYrange.min) * ptsYscale;
+                            doc.x = (oldXrange.coord + (newXrange.min - oldXrange.min) * ptsXscale);
+                            doc.y = (oldYrange.coord + (newYrange.min - oldYrange.min) * ptsYscale);
+                            Doc.GetProto(doc).data = new InkField(newPoints);
+                            appliedFunc = true;
+                        }
+                    }
+                }
+            }
+        }));
+        return appliedFunc;
     }
 
-    // @computed get unFilled() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; }
-    // @computed get unStrokd() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; }
-    // @computed get solidFil() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; }
-    // @computed get solidStk() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; }
-    // @computed get dashdStk() { return !this.unStrokd && this.getField("strokeDash") || ""; }
-    // @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
-    // @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
-    // @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
-    // @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
-    // @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
-    // @computed get shapeHgt() { return this.getField("_height"); }
-    // @computed get shapeWid() { return this.getField("_width"); }
-    // @computed get shapeXps() { return this.getField("x"); }
-    // @computed get shapeYps() { return this.getField("y"); }
-    // @computed get shapeRot() { return this.getField("rotation"); }
-    // set unFilled(value) { this.colorFil = value ? "" : this._lastFill; }
-    // set solidFil(value) { this.unFilled = !value; }
-    // set colorFil(value) { value && (this._lastFill = value); this.selectedInk?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); }
-    // set colorStk(value) { value && (this._lastLine = value); this.selectedInk?.forEach(i => i.rootDoc.color = value ? value : undefined); }
-    // set markHead(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeStartMarker = value); }
-    // set markTail(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeEndMarker = value); }
-    // set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
-    // set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
-    // set dashdStk(value) {
-    //     value && (this._lastDash = value) && (this.unStrokd = false);
-    //     this.selectedInk?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined);
-    // }
-    // set shapeXps(value) { this.selectedInk?.forEach(i => i.rootDoc.x = Number(value)); }
-    // set shapeYps(value) { this.selectedInk?.forEach(i => i.rootDoc.y = Number(value)); }
-    // set shapeRot(value) { this.selectedInk?.forEach(i => i.rootDoc.rotation = Number(value)); }
-    // set widthStk(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = Number(value)); }
-    // set shapeWid(value) {
-    //     this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
-    //         const oldWidth = NumCast(i.rootDoc._width);
-    //         i.rootDoc._width = Number(value);
-    //         this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth);
-    //     });
-    // }
-    // set shapeHgt(value) {
-    //     this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
-    //         const oldHeight = NumCast(i.rootDoc._height);
-    //         i.rootDoc._height = Number(value);
-    //         this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight);
-    //     });
-    // }
-
     /**
      * Adds a new control point to the ink instance when editing its format.
-     * @param x The x-coordinate of the current new point.
-     * @param y The y-coordinate of the current new point.
-     * @param pts The list containing all of the points to be added in PointData form.
-     * @param index The index of the current new point.
+     * @param index The index of the new point.
      * @param control The list of all control points of the ink.
      */
     @undoBatch
@@ -114,7 +95,7 @@ export class InkStrokeProperties {
                         for (var i = spNum; i < ink.length; i++) {
                             newPoints.push({ X: ink[i].X, Y: ink[i].Y });
                         }
-                        this._currPoint = -1;
+                        this._currentPoint = -1;
                         Doc.GetProto(doc).data = new InkField(newPoints);
                     }
                 }
@@ -122,42 +103,6 @@ export class InkStrokeProperties {
         }));
     }
 
-    /**
-     * Helper function that enables other functions to be applied to a particular ink instance.
-     * @param func The inputted function.
-     * @param requireCurrPoint Indicates whether the current selected point is needed.
-     * @returns The applied function.
-     */
-    applyFunction = (func: (doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => {
-        var appliedFunc = false;
-        this.selectedInk?.forEach(action(inkView => {
-            if (this.selectedInk?.length === 1 && (!requireCurrPoint || this._currPoint !== -1)) {
-                const doc = Document(inkView.rootDoc);
-                if (doc.type === DocumentType.INK && doc.width && doc.height) {
-                    const ink = Cast(doc.data, InkField)?.inkData;
-                    if (ink) {
-                        const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X));
-                        const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
-                        const ptsXscale = NumCast(doc._width) / (oldXrange.max - oldXrange.min);
-                        const ptsYscale = NumCast(doc._height) / (oldYrange.max - oldYrange.min);
-                        const newPoints = func(doc, ink, ptsXscale, ptsYscale);
-                        if (newPoints) {
-                            const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X));
-                            const newYrange = (ys => ({ min: Math.min(...ys), max: Math.max(...ys) }))(newPoints.map(p => p.Y));
-                            doc._width = (newXrange.max - newXrange.min) * ptsXscale;
-                            doc._height = (newYrange.max - newYrange.min) * ptsYscale;
-                            doc.x = (oldXrange.coord + (newXrange.min - oldXrange.min) * ptsXscale);
-                            doc.y = (oldYrange.coord + (newYrange.min - oldYrange.min) * ptsYscale);
-                            Doc.GetProto(doc).data = new InkField(newPoints);
-                            appliedFunc = true;
-                        }
-                    }
-                }
-            }
-        }));
-        return appliedFunc;
-    }
-
     /**
      * Deletes the points of the current ink instance.
      * @returns The changed x- and y-coordinates of the control points.
@@ -165,14 +110,14 @@ export class InkStrokeProperties {
     @undoBatch
     @action
     deletePoints = () => this.applyFunction((doc: Doc, ink: InkData) => {
-        var newPoints: { X: number, Y: number }[] = [];
-        const toRemove = Math.floor(((this._currPoint + 2) / 4));
-        for (var i = 0; i < ink.length; i++) {
+        const newPoints: { X: number, Y: number }[] = [];
+        const toRemove = Math.floor(((this._currentPoint + 2) / 4));
+        for (let i = 0; i < ink.length; i++) {
             if (Math.floor((i + 2) / 4) !== toRemove && (toRemove !== 0 || i > 3)) {
                 newPoints.push({ X: ink[i].X, Y: ink[i].Y });
             }
         }
-        this._currPoint = -1;
+        this._currentPoint = -1;
         if (newPoints.length < 4) return undefined;
         if (newPoints.length === 4) {
             const newerPoints: { X: number, Y: number }[] = [];
@@ -183,7 +128,7 @@ export class InkStrokeProperties {
             return newerPoints;
         }
         return newPoints;
-    }, true);
+    }, true)
 
     /**
      * Rotates the points of the current ink instance by a certain angle degree.
@@ -193,7 +138,7 @@ export class InkStrokeProperties {
     @undoBatch
     @action
     rotateInk = (angle: number) => {
-        this.applyFunction((doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => {
+        this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
             const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X));
             const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
             const centerPoint = { X: (oldXrange.min + oldXrange.max) / 2, Y: (oldYrange.min + oldYrange.max) / 2 };
@@ -234,7 +179,7 @@ export class InkStrokeProperties {
                 }
             }
             return newPoints;
-        });
+        })
 
     /**
      * Rotates the target point about the origin point for a given angle (radians).
@@ -247,7 +192,7 @@ export class InkStrokeProperties {
         const newY = Math.sin(angle) * target.X + Math.cos(angle) * target.Y;
         target.X = newX + origin.X;
         target.Y = newY + origin.Y;
-        return target
+        return target;
     }
 
     /**
@@ -277,54 +222,20 @@ export class InkStrokeProperties {
     @action
     moveHandle = (deltaX: number, deltaY: number, handleIndex: number) =>
         this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
-            const newPoints: { X: number, Y: number }[] = [];
             const order = handleIndex % 4;
-            let newHandlePoint = { X: 0, Y: 0 };
-
-            for (var i = 0; i < ink.length; i++) {
-                if (handleIndex === i) {
-                    newHandlePoint = { X: ink[i].X - deltaX / xScale, Y: ink[i].Y - deltaY / yScale };
-                    newPoints.push({ X: newHandlePoint.X, Y: newHandlePoint.Y });
-                } else {
-                    newPoints.push({ X: ink[i].X, Y: ink[i].Y });
-                }
-            }
-
+            const oldHandlePoint = ink[handleIndex];
+            const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale };
+            ink[handleIndex] = newHandlePoint;
+            
+            // Rotating opposite handle (first and final control point only have one handle).
             if (handleIndex !== 1 && handleIndex !== ink.length - 2) {
-                const oldHandlePoint = ink[handleIndex];
                 let oppositeHandlePoint = order === 1 ? ink[handleIndex - 3] : ink[handleIndex + 3];
                 const controlPoint = order === 1 ? ink[handleIndex - 1] : ink[handleIndex + 1];
                 const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint);
                 oppositeHandlePoint = this.rotatePoint(oppositeHandlePoint, controlPoint, angle);
-                order === 1 ? newPoints[handleIndex - 3] = oppositeHandlePoint : newPoints[handleIndex + 3] = oppositeHandlePoint;
+                order === 1 ? ink[handleIndex - 3] = oppositeHandlePoint : ink[handleIndex + 3] = oppositeHandlePoint;
             }
 
-            return newPoints;
-        });
-
-    // /**
-    //  * Changes the color of the border of the ink instance.
-    //  * @param color The new hex value to change the border to.
-    //  * @returns true.
-    //  */
-    // @undoBatch
-    // @action
-    // switchStk = (color: ColorState) => {
-    //     const val = String(color.hex);
-    //     this.colorStk = val;
-    //     return true;
-    // }
-
-    // /**
-    //  * Changes the color of the fill of the ink instance.
-    //  * @param color The new hex value to change the fill to.
-    //  * @returns true.
-    //  */
-    // @undoBatch
-    // @action
-    // switchFil = (color: ColorState) => {
-    //     const val = String(color.hex);
-    //     this.colorFil = val;
-    //     return true;
-    // }
+            return ink;
+        })
 }
\ No newline at end of file
diff --git a/src/client/views/InkingStroke.scss b/src/client/views/InkingStroke.scss
deleted file mode 100644
index f67b1779d..000000000
--- a/src/client/views/InkingStroke.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-.inkingStroke {
-  mix-blend-mode: multiply;
-  stroke-linejoin: round;
-  stroke-linecap: round;
-  overflow: visible !important;
-  transform-origin: top left;
-
-  svg:not(:root) {
-    overflow: visible !important;
-  }
-}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
deleted file mode 100644
index b2ef76882..000000000
--- a/src/client/views/InkingStroke.tsx
+++ /dev/null
@@ -1,271 +0,0 @@
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../fields/Doc";
-import { documentSchema } from "../../fields/documentSchemas";
-import { InkData, InkField, InkTool, ControlPoint, HandlePoint, HandleLine } from "../../fields/InkField";
-import { makeInterface } from "../../fields/Schema";
-import { Cast, StrCast } from "../../fields/Types";
-import { TraceMobx } from "../../fields/util";
-import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
-import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { InteractionUtils } from "../util/InteractionUtils";
-import { Scripting } from "../util/Scripting";
-import { UndoManager } from "../util/UndoManager";
-import { ContextMenu } from "./ContextMenu";
-import { ViewBoxBaseComponent } from "./DocComponent";
-import "./InkingStroke.scss";
-import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import React = require("react");
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { InkControl } from "./InkControl";
-
-type InkDocument = makeInterface<[typeof documentSchema]>;
-const InkDocument = makeInterface(documentSchema);
-
-@observer
-export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
-    private _controlUndo?: UndoManager.Batch;
-    @observable private _overAddPoint = -1;
-
-    static readonly MaskDim = 50000;
-
-    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
-
-    analyzeStrokes = () => {
-        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
-        CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
-    }
-
-    @action
-    onPointerEnter = (i: number) => {
-        this._overAddPoint = i;
-    }
-
-    @action
-    onPointerLeave = () => {
-        this._overAddPoint = -1;
-    }
-
-
-    @action
-    public static toggleMask = (inkDoc: Doc) => {
-        inkDoc.isInkMask = !inkDoc.isInkMask;
-        inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
-        inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
-        inkDoc.color = "#9b9b9bff";
-        inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
-    };
-
-    /**
-     * Handles the movement of a selected control point when the user clicks and drags.
-     * @param controlNum The index of the currently selected control point.
-     */
-    @action
-    onControlDown = (e: React.PointerEvent, controlNum: number): void => {
-        if (InkStrokeProperties.Instance) {
-            InkStrokeProperties.Instance.moveControl(0, 0, 1);
-            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
-            const screenScale = this.props.ScreenToLocalTransform().Scale;
-            setupMoveUpEvents(this, e,
-                (e: PointerEvent, down: number[], delta: number[]) => {
-                    InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlNum);
-                    return false;
-                },
-                () => controlUndo?.end(), emptyFunction);
-        }
-    }
-
-    /**
-     * Handles the movement of a selected handle point when the user clicks and drags.
-     * @param controlNum The index of the currently selected handle point.
-     */
-    onHandleDown = (e: React.PointerEvent, handleNum: number): void => {
-        if (InkStrokeProperties.Instance) {
-            InkStrokeProperties.Instance.moveControl(0, 0, 1);
-            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
-            const screenScale = this.props.ScreenToLocalTransform().Scale;
-            setupMoveUpEvents(this, e,
-                (e: PointerEvent, down: number[], delta: number[]) => {
-                    InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleNum);
-                    return false;
-                },
-                () => controlUndo?.end(), emptyFunction);
-        }
-    }
-
-    /**
-     * Handles the movement of the entire ink object when the user clicks and drags.
-     * @param e React Pointer Event.
-     */
-    onPointerDown = (e: React.PointerEvent) => {
-        if (this.props.isSelected(true)) {
-            setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e: PointerEvent, doubleTap: boolean | undefined) =>
-                doubleTap && InkStrokeProperties.Instance && (InkStrokeProperties.Instance._controlBtn = true)));
-        }
-    }
-
-    render() {
-        TraceMobx();
-        const formatInstance = InkStrokeProperties.Instance;
-        if (!formatInstance) return (null);
-        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
-        const strokeWidth = Number(this.layoutDoc.strokeWidth);
-        const xs = data.map(p => p.X);
-        const ys = data.map(p => p.Y);
-        const lineTop = Math.min(...ys);
-        const lineBot = Math.max(...ys);
-        const lineLft = Math.min(...xs);
-        const lineRgt = Math.max(...xs);
-        const left = lineLft - strokeWidth / 2;
-        const top = lineTop - strokeWidth / 2;
-        const right = lineRgt + strokeWidth / 2;
-        const bottom = lineBot + strokeWidth / 2;
-        const width = Math.max(1, right - left);
-        const height = Math.max(1, bottom - top);
-        const scaleX = width === strokeWidth ? 1 : (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
-        const scaleY = height === strokeWidth ? 1 : (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
-        const strokeColor = StrCast(this.layoutDoc.color, "");
-        const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
-
-        // Visually renders the polygonal line made by the user.
-        const points = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
-            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
-            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
-            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
-        
-        const selectedLine = InteractionUtils.CreatePolyline(data, lineLft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6, strokeWidth / 6,
-            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
-            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
-            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBot - lineTop > 1 && lineRgt - lineLft > 1, false);
-
-        // Invisible polygonal line that enables the ink to be selected by the user.
-        const hpoints = InteractionUtils.CreatePolyline(data, left, top,
-            this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, (strokeWidth + 15),
-            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
-            "none", "none", undefined, scaleX, scaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
-
-        const apoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth,
-            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
-            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
-            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
-
-        const controlPoints: ControlPoint[] = [];
-        const handlePoints: HandlePoint[] = [];
-        const handleLine: HandleLine[] = [];
-        // check the purpose of this conditional
-        if (data.length >= 4) {
-
-            // create separate functions for these            
-            for (var i = 0; i <= data.length - 4; i += 4) {
-                controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
-                controlPoints.push({ X: data[i + 3].X, Y: data[i + 3].Y, I: i + 3 });
-                handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? i : i - 1 });
-                handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 });
-            }
-
-            // first (single) handle line 
-            handleLine.push({ X1: data[0].X, Y1: data[0].Y, X2: data[1].X, Y2: data[1].Y, dot1: 0, dot2: 0 });
-            for (var i = 2; i < data.length - 4; i += 4) {
-
-                handleLine.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 3].X, Y2: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
-
-            }
-            // last (single) handle line
-            handleLine.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
-
-            for (var i = 0; i <= data.length - 4; i += 4) {
-                handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? i : i - 1 });
-                handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 });
-            }
-        }
-
-        // Additional points (controls) added by the user via click when editing the ink's format.
-        const addpoints = apoints.map((pts, i) =>
-            <svg height="10" width="10" key={`add${i}`}>
-                <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth / 2} stroke={this._overAddPoint === i ? "#1F85DE" : "invisible"} strokeWidth={dotsize / 4} fill={this._overAddPoint === i ? "#1F85DE" : "invisible"}
-                    onPointerDown={(e) => { formatInstance.addPoints(pts.X, pts.Y, apoints, i, controlPoints); }} onMouseEnter={() => this.onPointerEnter(i)} onMouseLeave={this.onPointerLeave} pointerEvents="all" cursor="all-scroll"
-                />
-            </svg>);
-        // Blue circles that allow the user to edit the curvature of the line using the selected control point as the anchor.
-        const handles = handlePoints.map((pts, i) =>
-            <svg height="10" width="10" key={`hdl${i}`}>
-                <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={strokeWidth / 2} strokeWidth={0} fill="#1F85DE"
-                    onPointerDown={(e) => this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
-            </svg>);
-        // Control points of the ink (blue outlined squares) that are made visible to user when editing its format.
-        const controls = controlPoints.map((pts, i) => <InkControl control={pts} left={left} top={top} scaleX={scaleX} scaleY={scaleY} strokeWidth={strokeWidth} ScreenToLocalTransform={this.props.ScreenToLocalTransform} /> );
-        // Set of two blue lines (each with a handle at the end) that are rendered perpendicular to the current selected point while editing.
-        const handleLines = handleLine.map((pts, i) =>
-            <svg height="100" width="100" key={`line${i}`}>
-                <line x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
-                    x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="#1F85DE" strokeWidth={dotsize / 10}
-                    display={(pts.dot1 === formatInstance._currPoint || pts.dot2 === formatInstance._currPoint) ? "inherit" : "none"} />
-            </svg>);
-
-        return (
-            <svg className="inkingStroke"
-                style={{
-                    pointerEvents: this.props.Document.isInkMask && this.props.layerProvider?.(this.props.Document) !== false ? "all" : "none",
-                    transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
-                    mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
-                    overflow: "visible",
-                }}
-                onPointerDown={this.onPointerDown}
-                onContextMenu={() => {
-                    const cm = ContextMenu.Instance;
-                    if (cm) {
-                        !Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
-                        cm.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
-                        cm.addItem({ description: "Edit Points", event: action(() => formatInstance._controlBtn = !formatInstance._controlBtn), icon: "paint-brush" });
-                        //cm.addItem({ description: "Format Shape...", event: this.formatShape, icon: "paint-brush" });
-                    }
-                }}
-            ><defs>
-                </defs>
-                {hpoints}
-                {points}
-                {formatInstance._controlBtn && this.props.isSelected() ? addpoints : ""}
-                {formatInstance._controlBtn && this.props.isSelected() ? handleLines : ""}
-                {formatInstance._controlBtn && this.props.isSelected() ? handles : ""}
-                {this.props.isSelected() ? selectedLine : ""}
-                {formatInstance._controlBtn && this.props.isSelected() ? controls : ""}
-            </svg>
-        );
-    }
-}
-
-
-export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); }
-export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); }
-export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); }
-export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); }
-export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); }
-export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); }
-export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
-export function ActiveInkPen(): Doc { return Doc.UserDoc(); }
-export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
-export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); }
-export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
-export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
-export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
-export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); }
-export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
-Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
-    CurrentUserUtils.SelectedTool = pen ? InkTool.Highlighter : InkTool.None;
-    SetActiveInkWidth(width);
-    SetActiveInkColor(color);
-    SetActiveFillColor(fill);
-    SetActiveArrowStart(arrowStart);
-    SetActiveArrowEnd(arrowEnd);
-    SetActiveDash(dash);
-});
-Scripting.addGlobal(function activateEraser(pen: any) { return CurrentUserUtils.SelectedTool = pen ? InkTool.Eraser : InkTool.None; });
-Scripting.addGlobal(function activateStamp(pen: any) { return CurrentUserUtils.SelectedTool = pen ? InkTool.Stamp : InkTool.None; });
-Scripting.addGlobal(function deactivateInk() { return CurrentUserUtils.SelectedTool = InkTool.None; });
-Scripting.addGlobal(function setInkWidth(width: any) { return SetActiveInkWidth(width); });
-Scripting.addGlobal(function setInkColor(color: any) { return SetActiveInkColor(color); });
-Scripting.addGlobal(function setFillColor(fill: any) { return SetActiveFillColor(fill); });
-Scripting.addGlobal(function setActiveArrowStart(arrowStart: any) { return SetActiveArrowStart(arrowStart); });
-Scripting.addGlobal(function setActiveArrowEnd(arrowEnd: any) { return SetActiveArrowStart(arrowEnd); });
-Scripting.addGlobal(function setActiveDash(dash: any) { return SetActiveDash(dash); });
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 5c41a96d0..920244463 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -11,7 +11,7 @@ import { DocumentType } from '../documents/DocumentTypes';
 import { SelectionManager } from '../util/SelectionManager';
 import { undoBatch } from '../util/UndoManager';
 import { CollectionViewType } from './collections/CollectionView';
-import { InkingStroke } from './InkingStroke';
+import { InkStroke } from './InkStroke';
 import { DocumentView } from './nodes/DocumentView';
 import './PropertiesButtons.scss';
 import React = require("react");
@@ -56,7 +56,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         return this.propertyToggleBtn("Dictate", "_showAudio", on => `${on ? "Hide" : "Show"} dictation/recording controls`, on => "microphone");
     }
     @computed get maskButton() {
-        return this.propertyToggleBtn("Mask", "isInkMask", on => on ? "Make plain ink" : "Make highlight mask", on => "paint-brush", (dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc));
+        return this.propertyToggleBtn("Mask", "isInkMask", on => on ? "Make plain ink" : "Make highlight mask", on => "paint-brush", (dv, doc) => InkStroke.toggleMask(dv?.layoutDoc || doc));
     }
     @computed get clustersButton() {
         return this.propertyToggleBtn("Clusters", "_useClusters", on => `${on ? "Hide" : "Show"} clusters`, on => "braille");
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index d09d949ff..4df3e4f00 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -86,7 +86,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @observable openSlideOptions: boolean = false;
 
     @observable inOptions: boolean = false;
-    @observable _controlBtn: boolean = false;
+    @observable _controlButton: boolean = false;
     @observable _lock: boolean = false;
 
     componentDidMount() {
@@ -540,7 +540,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         const formatInstance = InkStrokeProperties.Instance;
         return !formatInstance ? (null) : <div className="inking-button">
             <Tooltip title={<div className="dash-tooltip">{"Edit points"}</div>}>
-                <div className="inking-button-points" onPointerDown={action(() => formatInstance._controlBtn = !formatInstance._controlBtn)} style={{ backgroundColor: formatInstance._controlBtn ? "black" : "" }}>
+                <div className="inking-button-points" onPointerDown={action(() => formatInstance._controlButton = !formatInstance._controlButton)} style={{ backgroundColor: formatInstance._controlButton ? "black" : "" }}>
                     <FontAwesomeIcon icon="bezier-curve" color="white" size="lg" />
                 </div>
             </Tooltip>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 6e6fabd0d..65061d260 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -25,7 +25,7 @@ import { undoBatch } from "../../util/UndoManager";
 import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
 import { EditableView } from "../EditableView";
 import { GestureOverlay } from "../GestureOverlay";
-import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, ActiveArrowStart, ActiveArrowEnd } from "../InkingStroke";
+import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, ActiveArrowStart, ActiveArrowEnd } from "../InkStroke";
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
 import { DocumentView } from "../nodes/DocumentView";
 import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index accb80c5a..9e81a6cda 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -32,7 +32,7 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"
 import { Timeline } from "../../animationtimeline/Timeline";
 import { ContextMenu } from "../../ContextMenu";
 import { DocumentDecorations } from "../../DocumentDecorations";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkingStroke";
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkStroke";
 import { LightboxView } from "../../LightboxView";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView";
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 092823603..744a9c5de 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -13,7 +13,7 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { DocComponent } from "../DocComponent";
-import { InkingStroke } from "../InkingStroke";
+import { InkStroke } from "../InkStroke";
 import { StyleProp } from "../StyleProvider";
 import "./CollectionFreeFormDocumentView.scss";
 import { DocumentView, DocumentViewProps } from "./DocumentView";
@@ -38,7 +38,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @observable _animPos: number[] | undefined = undefined;
     @observable _contentView: DocumentView | undefined | null;
     get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive
-    get maskCentering() { return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; }
+    get maskCentering() { return this.props.Document.isInkMask ? InkStroke.MaskDim / 2 : 0; }
     get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${this.props.jitterRotation}deg)`; }
     get X() { return this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
     get Y() { return this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 8da5cd1b1..2d466763b 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -12,7 +12,7 @@ import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 import { SelectionManager } from "../../util/SelectionManager";
 import { undoBatch } from "../../util/UndoManager";
 import { ViewBoxBaseComponent } from "../DocComponent";
-import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke";
+import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth } from "../InkStroke";
 import "./ColorBox.scss";
 import { FieldView, FieldViewProps } from './FieldView';
 import { RichTextMenu } from "./formattedText/RichTextMenu";
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f0a54e4ac..5da3a52d5 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -10,7 +10,7 @@ import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionSchemaView } from "../collections/CollectionSchemaView";
 import { CollectionView } from "../collections/CollectionView";
-import { InkingStroke } from "../InkingStroke";
+import { InkStroke } from "../InkStroke";
 import { PresElementBox } from "../presentationview/PresElementBox";
 import { SearchBox } from "../search/SearchBox";
 import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
@@ -224,7 +224,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
                     FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, EquationBox, SliderBox, FieldView,
                     CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
                     PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, FilterBox, FunctionPlotBox,
-                    ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, LinkBox, ScriptingBox,
+                    ColorBox, DashWebRTCVideo, LinkAnchorBox, InkStroke, LinkBox, ScriptingBox,
                     ScreenshotBox,
                     HTMLtag, ComparisonBox
                 }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b861669f8..924b0e7d3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -34,7 +34,7 @@ import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from "../DocComponent";
 import { EditableView } from '../EditableView';
-import { InkingStroke } from "../InkingStroke";
+import { InkStroke } from "../InkStroke";
 import { LightboxView } from "../LightboxView";
 import { StyleLayers, StyleProp } from "../StyleProvider";
 import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
@@ -1138,8 +1138,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     render() {
         TraceMobx();
-        const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
-        const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+        const xshift = () => (this.props.Document.isInkMask ? InkStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
+        const yshift = () => (this.props.Document.isInkMask ? InkStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
         return (<div className="contentFittingDocumentView">
             {!this.props.Document || !this.props.PanelWidth() ? (null) : (
                 <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
-- 
cgit v1.2.3-70-g09d2


From 8bc17cecdfce184e5a426dc2332d3c9ad0406f58 Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Sat, 10 Jul 2021 11:13:42 -0500
Subject: fixed adding point bug with breaking handle tangency

---
 src/client/documents/Documents.ts                  |   8 +-
 src/client/views/GestureOverlay.tsx                |   2 +-
 src/client/views/InkControls.tsx                   |  18 +-
 src/client/views/InkHandles.tsx                    |  27 +--
 src/client/views/InkStroke.tsx                     | 187 ---------------------
 src/client/views/InkStrokeProperties.ts            |  69 ++++++--
 src/client/views/InkingStroke.tsx                  | 187 +++++++++++++++++++++
 src/client/views/PropertiesButtons.tsx             |   4 +-
 src/client/views/collections/CollectionMenu.tsx    |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   4 +-
 src/client/views/nodes/ColorBox.tsx                |   2 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   4 +-
 src/client/views/nodes/DocumentView.tsx            |   6 +-
 14 files changed, 285 insertions(+), 237 deletions(-)
 delete mode 100644 src/client/views/InkStroke.tsx
 create mode 100644 src/client/views/InkingStroke.tsx

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 188fe31c0..24682cbd0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -31,7 +31,7 @@ import { CollectionView, CollectionViewType } from "../views/collections/Collect
 import { ContextMenu } from "../views/ContextMenu";
 import { ContextMenuProps } from "../views/ContextMenuItem";
 import { DFLT_IMAGE_NATIVE_DIM } from "../views/globalCssVariables.scss";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkStroke } from "../views/InkStroke";
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from "../views/InkingStroke";
 import { AudioBox } from "../views/nodes/AudioBox";
 import { ColorBox } from "../views/nodes/ColorBox";
 import { ComparisonBox } from "../views/nodes/ComparisonBox";
@@ -432,7 +432,7 @@ export namespace Docs {
                 options: { links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true }
             }],
             [DocumentType.INK, {
-                layout: { view: InkStroke, dataField: defaultDataKey },
+                layout: { view: InkingStroke, dataField: defaultDataKey },
                 options: { _fontFamily: "cursive", backgroundColor: "transparent", links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.SCREENSHOT, {
@@ -760,7 +760,7 @@ export namespace Docs {
             const I = new Doc();
             I[Initializing] = true;
             I.type = DocumentType.INK;
-            I.layout = InkStroke.LayoutString("data");
+            I.layout = InkingStroke.LayoutString("data");
             I.color = color;
             I.fillColor = fillColor;
             I.strokeWidth = Number(strokeWidth);
@@ -1144,7 +1144,7 @@ export namespace DocUtils {
             layout = AudioBox.LayoutString;
         } else if (field instanceof InkField) {
             created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved);
-            layout = InkStroke.LayoutString;
+            layout = InkingStroke.LayoutString;
         } else if (field instanceof List && field[0] instanceof Doc) {
             created = Docs.Create.StackingDocument(DocListCast(field), resolved);
             layout = CollectionView.LayoutString;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 455b3a935..491bf18b2 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -17,7 +17,7 @@ import { Scripting } from "../util/Scripting";
 import { Transform } from "../util/Transform";
 import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
 import "./GestureOverlay.scss";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "./InkStroke";
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke";
 import { DocumentView } from "./nodes/DocumentView";
 import { RadialMenu } from "./nodes/RadialMenu";
 import HorizontalPalette from "./Palette";
diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx
index eeddfce4c..4d8b2c6b5 100644
--- a/src/client/views/InkControls.tsx
+++ b/src/client/views/InkControls.tsx
@@ -22,17 +22,17 @@ export class InkControls extends React.Component<InkControlProps> {
 
     /**
      * Handles the movement of a selected control point when the user clicks and drags.
-     * @param controlNum The index of the currently selected control point.
+     * @param controlIndex The index of the currently selected control point.
      */
     @action
-    onControlDown = (e: React.PointerEvent, controlNum: number): void => {
+    onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
         if (InkStrokeProperties.Instance) {
             InkStrokeProperties.Instance.moveControl(0, 0, 1);
             const controlUndo = UndoManager.StartBatch("DocDecs set radius");
             const screenScale = this.props.ScreenToLocalTransform().Scale;
             setupMoveUpEvents(this, e,
                 (e: PointerEvent, down: number[], delta: number[]) => {
-                    InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlNum);
+                    InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex);
                     return false;
                 },
                 () => controlUndo?.end(), emptyFunction);
@@ -60,12 +60,20 @@ export class InkControls extends React.Component<InkControlProps> {
         }
     }
 
+    /**
+     * Updates whether a user has hovered over a particular control point or point that could be added
+     * on click.
+     */
     @action onEnterControl = (i: number) => { this._overControl = i; };
     @action onLeaveControl = () => { this._overControl = -1; };
     @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; };
     @action onLeaveAddPoint = () => { this._overAddPoint = -1; };
  
     render() {
+        const formatInstance = InkStrokeProperties.Instance;
+        if (!formatInstance) return (null);
+
+        // Accessing the current ink's data and extracting all control points.
         const data = this.props.data;
         const controlPoints: ControlPoint[] = [];
         if (data.length >= 4) {
@@ -87,7 +95,7 @@ export class InkControls extends React.Component<InkControlProps> {
                             r={strokeWidth / 2} 
                             stroke={this._overAddPoint === i ? "#1F85DE" : "transparent"} 
                             strokeWidth={dotsize / 4} fill={this._overAddPoint === i ? "#1F85DE" : "transparent"}
-                            onPointerDown={() => { InkStrokeProperties.Instance?.addPoints(pts.X, pts.Y, addedPoints, i, controlPoints); }} 
+                            onPointerDown={() => { formatInstance?.addPoints(pts.X, pts.Y, addedPoints, i, controlPoints); }} 
                             onMouseEnter={() => this.onEnterAddPoint(i)} 
                             onMouseLeave={this.onLeaveAddPoint} 
                             pointerEvents="all" 
@@ -103,7 +111,7 @@ export class InkControls extends React.Component<InkControlProps> {
                             height={this._overControl === i ? strokeWidth * 1.5 : strokeWidth} 
                             width={this._overControl === i ? strokeWidth * 1.5 : strokeWidth} 
                             strokeWidth={strokeWidth / 6} stroke="#1F85DE" 
-                            fill={InkStrokeProperties.Instance?._currentPoint === control.I ? "#1F85DE" : "white"}
+                            fill={formatInstance?._currentPoint === control.I ? "#1F85DE" : "white"}
                             onPointerDown={(e) => { this.changeCurrPoint(control.I); this.onControlDown(e, control.I); }} 
                             onMouseEnter={() => this.onEnterControl(i)} 
                             onMouseLeave={this.onLeaveControl} 
diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx
index 993e427b3..a33380221 100644
--- a/src/client/views/InkHandles.tsx
+++ b/src/client/views/InkHandles.tsx
@@ -15,33 +15,38 @@ export interface InkControlProps {
 
 @observer
 export class InkHandles extends React.Component<InkControlProps> {
-    @observable private _brokenIndices: number[] = [];
-
     /**
      * Handles the movement of a selected handle point when the user clicks and drags.
      * @param handleNum The index of the currently selected handle point.
      */
-     onHandleDown = (e: React.PointerEvent, handleNum: number): void => {
+     onHandleDown = (e: React.PointerEvent, handleIndex: number): void => {
         if (InkStrokeProperties.Instance) {
             InkStrokeProperties.Instance.moveControl(0, 0, 1);
             const controlUndo = UndoManager.StartBatch("DocDecs set radius");
             const screenScale = this.props.ScreenToLocalTransform().Scale;
-            document.addEventListener("keydown", (e: KeyboardEvent) => this.onBreakTangent(e, handleNum), true);
+
+            const order = handleIndex % 4;
+            const oppositeHandleIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
+            const controlIndex = order === 1 ? handleIndex - 1 : handleIndex + 2;
+            document.addEventListener("keydown", (e: KeyboardEvent) => this.onBreakTangent(e, controlIndex), true);
             setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
-                    InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleNum, this._brokenIndices);
+                    InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
                     return false;
                 }, () => controlUndo?.end(), emptyFunction
             );
         }
     }
 
+    /**
+     * Breaks tangent handle movement when ‘Alt’ key is held down. Adds the current handle index and 
+     * its matching (opposite) handle to a list of broken handle indices.
+     * @param handleNum The index of the currently selected handle point.
+     */
     @action
-    onBreakTangent = (e: KeyboardEvent, handleIndex: number) => {
+    onBreakTangent = (e: KeyboardEvent, controlIndex: number) => {
         if (["Alt"].includes(e.key)) {
-            const order = handleIndex % 4;
-            const oppositeHandleIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
-            if (!this._brokenIndices.includes(handleIndex) && !this._brokenIndices.includes(oppositeHandleIndex)) {
-                this._brokenIndices.push(handleIndex, oppositeHandleIndex);
+            if (!InkStrokeProperties.Instance?._brokenIndices.includes(controlIndex)) {
+                InkStrokeProperties.Instance?._brokenIndices.push(controlIndex);
             }
         }
     }
@@ -49,6 +54,8 @@ export class InkHandles extends React.Component<InkControlProps> {
     render() {
         const formatInstance = InkStrokeProperties.Instance;
         if (!formatInstance) return (null);
+
+        // Accessing the current ink's data and extracting all handle points and handle lines.
         const data = this.props.data;
         const handlePoints: HandlePoint[] = [];
         const handleLines: HandleLine[] = [];
diff --git a/src/client/views/InkStroke.tsx b/src/client/views/InkStroke.tsx
deleted file mode 100644
index 9ca8bdbea..000000000
--- a/src/client/views/InkStroke.tsx
+++ /dev/null
@@ -1,187 +0,0 @@
-import React = require("react");
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../fields/Doc";
-import { documentSchema } from "../../fields/documentSchemas";
-import { InkData, InkField, InkTool, ControlPoint, HandlePoint, HandleLine } from "../../fields/InkField";
-import { makeInterface } from "../../fields/Schema";
-import { Cast, StrCast } from "../../fields/Types";
-import { TraceMobx } from "../../fields/util";
-import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
-import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { InteractionUtils } from "../util/InteractionUtils";
-import { Scripting } from "../util/Scripting";
-import { UndoManager } from "../util/UndoManager";
-import { ContextMenu } from "./ContextMenu";
-import { ViewBoxBaseComponent } from "./DocComponent";
-import "./InkStroke.scss";
-import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { InkControls } from "./InkControls";
-import { InkHandles } from "./InkHandles";
-
-type InkDocument = makeInterface<[typeof documentSchema]>;
-const InkDocument = makeInterface(documentSchema);
-
-@observer
-export class InkStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
-    static readonly MaskDim = 50000;
-    @observable private _properties?: InkStrokeProperties;
-
-    constructor(props: FieldViewProps & InkDocument) {
-        super(props);
-
-        this._properties = InkStrokeProperties.Instance;
-    }
-
-    public static LayoutString(fieldStr: string) { 
-        return FieldView.LayoutString(InkStroke, fieldStr); 
-    }
-
-    analyzeStrokes() {
-        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
-        CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
-    }
-
-    @action
-    public static toggleMask = (inkDoc: Doc) => {
-        inkDoc.isInkMask = !inkDoc.isInkMask;
-        inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
-        inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
-        inkDoc.color = "#9b9b9bff";
-        inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
-    }
-
-    /**
-     * Handles the movement of the entire ink object when the user clicks and drags.
-     * @param e React Pointer Event.
-     */
-    onPointerDown = (e: React.PointerEvent) => {
-        if (this.props.isSelected(true)) {
-            setupMoveUpEvents(this, e, returnFalse, emptyFunction, 
-                action((e: PointerEvent, doubleTap: boolean | undefined) =>
-                doubleTap && this._properties && (this._properties._controlButton = true))
-            );
-        }
-    }
-
-    render() {
-        TraceMobx();
-
-        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
-        const strokeWidth = Number(this.layoutDoc.strokeWidth);
-        const lineTop = Math.min(...data.map(p => p.Y));
-        const lineBottom = Math.max(...data.map(p => p.Y));
-        const lineLeft = Math.min(...data.map(p => p.X));
-        const lineRight = Math.max(...data.map(p => p.X));
-        const left = lineLeft - strokeWidth / 2;
-        const top = lineTop - strokeWidth / 2;
-        const right = lineRight + strokeWidth / 2;
-        const bottom = lineBottom + strokeWidth / 2;
-        const width = Math.max(1, right - left);
-        const height = Math.max(1, bottom - top);
-        const scaleX = width === strokeWidth ? 1 : (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
-        const scaleY = height === strokeWidth ? 1 : (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
-        const strokeColor = StrCast(this.layoutDoc.color, "");
-        const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
-
-        // Visually renders the polygonal line made by the user.
-        const inkLine = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth, 
-            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), 
-            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), 
-            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 
-            this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, 
-            false);
-        const selectedLine = InteractionUtils.CreatePolyline(
-            data, lineLeft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6, 
-            strokeWidth / 6,StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
-            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
-            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 
-            this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, 
-            false);
-        // Invisible polygonal line that enables the ink to be selected by the user.
-        const clickableLine = InteractionUtils.CreatePolyline(data, left, top, 
-            this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, 
-            strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier), 
-            StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "", 
-            this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
-        const addedPoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth,
-            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
-            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
-            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 
-            this.props.isSelected() && strokeWidth <= 5, false);
-
-        return (
-            <svg className="inkStroke"
-                style={{
-                    pointerEvents: this.props.Document.isInkMask && this.props.layerProvider?.(this.props.Document) !== false ? "all" : "none",
-                    transform: this.props.Document.isInkMask ? `translate(${InkStroke.MaskDim / 2}px, ${InkStroke.MaskDim / 2}px)` : undefined,
-                    mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
-                    overflow: "visible",
-                }}
-                onPointerDown={this.onPointerDown}
-                onContextMenu={() => {
-                    const cm = ContextMenu.Instance;
-                    if (cm) {
-                        !Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
-                        cm.addItem({ description: "Toggle Mask", event: () => InkStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
-                        cm.addItem({ description: "Edit Points", event: action(() => {if (this._properties) { this._properties._controlButton = !this._properties._controlButton; }} ), icon: "paint-brush" });
-                    }
-                }}
-            >
-                
-                {clickableLine}
-                {inkLine}
-                {this.props.isSelected() ? selectedLine : ""}
-                {this.props.isSelected() && this._properties?._controlButton ? 
-                    <>
-                        <InkControls 
-                            data={data} 
-                            addedPoints={addedPoints} 
-                            format={[left, top, scaleX, scaleY, strokeWidth, dotsize]} 
-                            ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
-                        <InkHandles 
-                            data={data} 
-                            format={[left, top, scaleX, scaleY, strokeWidth, dotsize]} 
-                            ScreenToLocalTransform={this.props.ScreenToLocalTransform} /> 
-                    </> : ""}
-            </svg>
-        );
-    }
-}
-
-
-export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); }
-export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); }
-export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); }
-export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); }
-export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); }
-export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); }
-export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
-export function ActiveInkPen(): Doc { return Doc.UserDoc(); }
-export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
-export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); }
-export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
-export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
-export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
-export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); }
-export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
-Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
-    CurrentUserUtils.SelectedTool = pen ? InkTool.Highlighter : InkTool.None;
-    SetActiveInkWidth(width);
-    SetActiveInkColor(color);
-    SetActiveFillColor(fill);
-    SetActiveArrowStart(arrowStart);
-    SetActiveArrowEnd(arrowEnd);
-    SetActiveDash(dash);
-});
-Scripting.addGlobal(function activateEraser(pen: any) { return CurrentUserUtils.SelectedTool = pen ? InkTool.Eraser : InkTool.None; });
-Scripting.addGlobal(function activateStamp(pen: any) { return CurrentUserUtils.SelectedTool = pen ? InkTool.Stamp : InkTool.None; });
-Scripting.addGlobal(function deactivateInk() { return CurrentUserUtils.SelectedTool = InkTool.None; });
-Scripting.addGlobal(function setInkWidth(width: any) { return SetActiveInkWidth(width); });
-Scripting.addGlobal(function setInkColor(color: any) { return SetActiveInkColor(color); });
-Scripting.addGlobal(function setFillColor(fill: any) { return SetActiveFillColor(fill); });
-Scripting.addGlobal(function setActiveArrowStart(arrowStart: any) { return SetActiveArrowStart(arrowStart); });
-Scripting.addGlobal(function setActiveArrowEnd(arrowEnd: any) { return SetActiveArrowStart(arrowEnd); });
-Scripting.addGlobal(function setActiveDash(dash: any) { return SetActiveDash(dash); });
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 812e8ff6e..4ec03c560 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,7 +1,7 @@
 import { action, computed, observable } from "mobx";
 import { Doc, Field, Opt } from "../../fields/Doc";
 import { Document } from "../../fields/documentSchemas";
-import { InkField, InkData, PointData } from "../../fields/InkField";
+import { InkField, InkData, PointData, ControlPoint } from "../../fields/InkField";
 import { Cast, NumCast } from "../../fields/Types";
 import { DocumentType } from "../documents/DocumentTypes";
 import { SelectionManager } from "../util/SelectionManager";
@@ -13,6 +13,7 @@ export class InkStrokeProperties {
     @observable _lock = false;
     @observable _controlButton = false;
     @observable _currentPoint = -1;
+    @observable _brokenIndices: number[] = [];
 
     constructor() {
         InkStrokeProperties.Instance = this;
@@ -73,6 +74,7 @@ export class InkStrokeProperties {
     addPoints = (x: number, y: number, pts: { X: number, Y: number }[], index: number, control: { X: number, Y: number }[]) => {
         this.selectedInk?.forEach(action(inkView => {
             if (this.selectedInk?.length === 1) {
+                const newPoint = { X: x, Y: y };
                 const doc = Document(inkView.rootDoc);
                 if (doc.type === DocumentType.INK) {
                     const ink = Cast(doc.data, InkField)?.inkData;
@@ -88,12 +90,25 @@ export class InkStrokeProperties {
                         for (var i = 0; i < spNum; i++) {
                             ink[i] && newPoints.push({ X: ink[i].X, Y: ink[i].Y });
                         }
-                        for (var j = 0; j < 4; j++) {
-                            newPoints.push({ X: x, Y: y });
 
-                        }
+                        // Updating the indices of the control points whose handle tangency has been broken.
+                        this._brokenIndices = this._brokenIndices.map((control) => { 
+                            if (control >= spNum) {
+                                return control + 4; 
+                            } else {
+                                return control;
+                            }
+                        });
+
+                        // const [handleA, handleB] = this.getNewHandlePoints(newPoint, pts[index-1], pts[index+1]);
+                        newPoints.push(newPoint);
+                        newPoints.push(newPoint);
+                        newPoints.push(newPoint);
+                        newPoints.push(newPoint);
+
                         for (var i = spNum; i < ink.length; i++) {
                             newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+
                         }
                         this._currentPoint = -1;
                         Doc.GetProto(doc).data = new InkField(newPoints);
@@ -103,6 +118,20 @@ export class InkStrokeProperties {
         }));
     }
 
+    getNewHandlePoints = (newControl: PointData, a: PointData, b: PointData) => {
+        // find midpoint between the left and right control point of new control
+        // rotate midpoint by +-pi/2 to get new handle points
+        // multiplying x-y coordinates of both by 10/L where L is its current magnitude
+        const angle = this.angleChange(a, b, newControl);
+        const midpoint = this.rotatePoint(a, newControl, angle/2);
+        // const handleA = this.rotatePoint(midpoint, newControl, -Math.PI/2);
+        // const handleB = this.rotatePoint(midpoint, newControl, -Math.PI/2);
+        const handleA = { X: midpoint.X + (20 * Math.cos(-Math.PI/2)), Y: midpoint.Y + (20 * Math.sin(-Math.PI/2)) };
+        const handleB = { X: midpoint.X + (20 * Math.cos(Math.PI/2)), Y: midpoint.Y + (20 * Math.sin(Math.PI/2)) };
+
+        return [handleA, handleB];
+    }
+
     /**
      * Deletes the points of the current ink instance.
      * @returns The changed x- and y-coordinates of the control points.
@@ -195,23 +224,27 @@ export class InkStrokeProperties {
         return target;
     }
 
+    angleBetweenTwoVectors = (vectorA: PointData, vectorB: PointData) => {
+        const magnitudeA = Math.sqrt(vectorA.X * vectorA.X + vectorA.Y * vectorA.Y);
+        const magnitudeB = Math.sqrt(vectorB.X * vectorB.X + vectorB.Y * vectorB.Y);
+        // Normalizing the vectors.
+        vectorA = { X: vectorA.X / magnitudeA, Y: vectorA.Y / magnitudeA };
+        vectorB = { X: vectorB.X / magnitudeB, Y: vectorB.Y / magnitudeB };
+        const dotProduct = vectorB.X * vectorA.X + vectorB.Y * vectorA.Y;
+        return Math.acos(dotProduct);
+    }
+
     /**
      * Finds the angle difference (in radians) between two vectors relative to an arbitrary origin.
      */
     angleChange = (a: PointData, b: PointData, origin: PointData) => {
         // Finding vector representation of inputted points relative to new origin.
-        let vectorA = { X: a.X - origin.X, Y: a.Y - origin.Y };
-        let vectorB = { X: b.X - origin.X, Y: b.Y - origin.Y };
+        const vectorA = { X: a.X - origin.X, Y: a.Y - origin.Y };
+        const vectorB = { X: b.X - origin.X, Y: b.Y - origin.Y };
         const crossProduct = vectorB.X * vectorA.Y - vectorB.Y * vectorA.X;
         // Determining whether rotation is clockwise or counterclockwise.
         const sign = crossProduct < 0 ? 1 : -1;
-        const magnitudeA = Math.sqrt(vectorA.X * vectorA.X + vectorA.Y * vectorA.Y);
-        const magnitudeB = Math.sqrt(vectorB.X * vectorB.X + vectorB.Y * vectorB.Y);
-        // Normalizing the vectors.
-        vectorA = { X: vectorA.X / magnitudeA, Y: vectorA.Y / magnitudeA };
-        vectorB = { X: vectorB.X / magnitudeB, Y: vectorB.Y / magnitudeB };
-        const dotProduct = vectorB.X * vectorA.X + vectorB.Y * vectorA.Y;
-        const theta = Math.acos(dotProduct);
+        const theta = this.angleBetweenTwoVectors(vectorA, vectorB);
         return sign * theta;
     }
 
@@ -220,20 +253,20 @@ export class InkStrokeProperties {
      */
     @undoBatch
     @action
-    moveHandle = (deltaX: number, deltaY: number, handleIndex: number, brokenIndices: number[]) =>
+    moveHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) =>
         this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
             const order = handleIndex % 4;
             const oldHandlePoint = ink[handleIndex];
+            let oppositeHandlePoint = ink[oppositeHandleIndex];
+            const controlPoint = ink[controlIndex];
             const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale };
             ink[handleIndex] = newHandlePoint;
             
             // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle).
-            if (!brokenIndices.includes(handleIndex) && handleIndex !== 1 && handleIndex !== ink.length - 2) {
-                let oppositeHandlePoint = order === 1 ? ink[handleIndex - 3] : ink[handleIndex + 3];
-                const controlPoint = order === 1 ? ink[handleIndex - 1] : ink[handleIndex + 1];
+            if (!this._brokenIndices.includes(controlIndex) && handleIndex !== 1 && handleIndex !== ink.length - 2) {
                 const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint);
                 oppositeHandlePoint = this.rotatePoint(oppositeHandlePoint, controlPoint, angle);
-                order === 1 ? ink[handleIndex - 3] = oppositeHandlePoint : ink[handleIndex + 3] = oppositeHandlePoint;
+                ink[oppositeHandleIndex] = oppositeHandlePoint;
             }
 
             return ink;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
new file mode 100644
index 000000000..b630c20cc
--- /dev/null
+++ b/src/client/views/InkingStroke.tsx
@@ -0,0 +1,187 @@
+import React = require("react");
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Doc } from "../../fields/Doc";
+import { documentSchema } from "../../fields/documentSchemas";
+import { InkData, InkField, InkTool } from "../../fields/InkField";
+import { makeInterface } from "../../fields/Schema";
+import { Cast, StrCast } from "../../fields/Types";
+import { TraceMobx } from "../../fields/util";
+import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
+import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { InteractionUtils } from "../util/InteractionUtils";
+import { Scripting } from "../util/Scripting";
+import { ContextMenu } from "./ContextMenu";
+import { ViewBoxBaseComponent } from "./DocComponent";
+import "./InkStroke.scss";
+import { FieldView, FieldViewProps } from "./nodes/FieldView";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
+import { InkControls } from "./InkControls";
+import { InkHandles } from "./InkHandles";
+
+type InkDocument = makeInterface<[typeof documentSchema]>;
+const InkDocument = makeInterface(documentSchema);
+
+@observer
+export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
+    static readonly MaskDim = 50000;
+    @observable private _properties?: InkStrokeProperties;
+
+    constructor(props: FieldViewProps & InkDocument) {
+        super(props);
+
+        this._properties = InkStrokeProperties.Instance;
+    }
+
+    public static LayoutString(fieldStr: string) { 
+        return FieldView.LayoutString(InkingStroke, fieldStr); 
+    }
+
+    analyzeStrokes() {
+        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+        CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
+    }
+
+    @action
+    public static toggleMask = (inkDoc: Doc) => {
+        inkDoc.isInkMask = !inkDoc.isInkMask;
+        inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
+        inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
+        inkDoc.color = "#9b9b9bff";
+        inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
+    }
+
+    /**
+     * Handles the movement of the entire ink object when the user clicks and drags.
+     */
+    onPointerDown = (e: React.PointerEvent) => {
+        if (this.props.isSelected(true)) {
+            setupMoveUpEvents(this, e, returnFalse, emptyFunction, 
+                action((e: PointerEvent, doubleTap: boolean | undefined) =>
+                doubleTap && this._properties && (this._properties._controlButton = true))
+            );
+        }
+    }
+
+    render() {
+        TraceMobx();
+        // Extracting the ink data and formatting information of the current ink stroke.
+        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+        const strokeWidth = Number(this.layoutDoc.strokeWidth);
+        const lineTop = Math.min(...data.map(p => p.Y));
+        const lineBottom = Math.max(...data.map(p => p.Y));
+        const lineLeft = Math.min(...data.map(p => p.X));
+        const lineRight = Math.max(...data.map(p => p.X));
+        const left = lineLeft - strokeWidth / 2;
+        const top = lineTop - strokeWidth / 2;
+        const right = lineRight + strokeWidth / 2;
+        const bottom = lineBottom + strokeWidth / 2;
+        const width = Math.max(1, right - left);
+        const height = Math.max(1, bottom - top);
+        const scaleX = width === strokeWidth ? 1 : (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
+        const scaleY = height === strokeWidth ? 1 : (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
+        const strokeColor = StrCast(this.layoutDoc.color, "");
+        const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
+
+        // Visually renders the polygonal line made by the user.
+        const inkLine = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth, 
+            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), 
+            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), 
+            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 
+            this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, 
+            false);
+        // Thin blue line indicating that the current ink stroke is selected.
+        const selectedLine = InteractionUtils.CreatePolyline(
+            data, lineLeft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6, 
+            strokeWidth / 6,StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
+            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
+            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 
+            this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, 
+            false);
+        // Invisible polygonal line that enables the ink to be selected by the user.
+        const clickableLine = InteractionUtils.CreatePolyline(data, left, top, 
+            this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, 
+            strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier), 
+            StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "", 
+            this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
+        // Set of points rendered upon the ink that can be added if a user clicks on one.
+        const addedPoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth,
+            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
+            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
+            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 
+            this.props.isSelected() && strokeWidth <= 5, false);
+
+        return (
+            <svg className="inkStroke"
+                style={{
+                    pointerEvents: this.props.Document.isInkMask && this.props.layerProvider?.(this.props.Document) !== false ? "all" : "none",
+                    transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+                    mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
+                    overflow: "visible",
+                }}
+                onPointerDown={this.onPointerDown}
+                onContextMenu={() => {
+                    const cm = ContextMenu.Instance;
+                    if (cm) {
+                        !Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
+                        cm.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
+                        cm.addItem({ description: "Edit Points", event: action(() => {if (this._properties) { this._properties._controlButton = !this._properties._controlButton; }} ), icon: "paint-brush" });
+                    }
+                }}
+            >
+                
+                {clickableLine}
+                {inkLine}
+                {this.props.isSelected() ? selectedLine : ""}
+                {this.props.isSelected() && this._properties?._controlButton ? 
+                    <>
+                        <InkControls 
+                            data={data} 
+                            addedPoints={addedPoints} 
+                            format={[left, top, scaleX, scaleY, strokeWidth, dotsize]} 
+                            ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
+                        <InkHandles 
+                            data={data} 
+                            format={[left, top, scaleX, scaleY, strokeWidth, dotsize]} 
+                            ScreenToLocalTransform={this.props.ScreenToLocalTransform} /> 
+                    </> : ""}
+            </svg>
+        );
+    }
+}
+
+
+export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); }
+export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); }
+export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); }
+export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); }
+export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); }
+export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); }
+export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
+export function ActiveInkPen(): Doc { return Doc.UserDoc(); }
+export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
+export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); }
+export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
+export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
+export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
+export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); }
+export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
+Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
+    CurrentUserUtils.SelectedTool = pen ? InkTool.Highlighter : InkTool.None;
+    SetActiveInkWidth(width);
+    SetActiveInkColor(color);
+    SetActiveFillColor(fill);
+    SetActiveArrowStart(arrowStart);
+    SetActiveArrowEnd(arrowEnd);
+    SetActiveDash(dash);
+});
+Scripting.addGlobal(function activateEraser(pen: any) { return CurrentUserUtils.SelectedTool = pen ? InkTool.Eraser : InkTool.None; });
+Scripting.addGlobal(function activateStamp(pen: any) { return CurrentUserUtils.SelectedTool = pen ? InkTool.Stamp : InkTool.None; });
+Scripting.addGlobal(function deactivateInk() { return CurrentUserUtils.SelectedTool = InkTool.None; });
+Scripting.addGlobal(function setInkWidth(width: any) { return SetActiveInkWidth(width); });
+Scripting.addGlobal(function setInkColor(color: any) { return SetActiveInkColor(color); });
+Scripting.addGlobal(function setFillColor(fill: any) { return SetActiveFillColor(fill); });
+Scripting.addGlobal(function setActiveArrowStart(arrowStart: any) { return SetActiveArrowStart(arrowStart); });
+Scripting.addGlobal(function setActiveArrowEnd(arrowEnd: any) { return SetActiveArrowStart(arrowEnd); });
+Scripting.addGlobal(function setActiveDash(dash: any) { return SetActiveDash(dash); });
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 920244463..5c41a96d0 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -11,7 +11,7 @@ import { DocumentType } from '../documents/DocumentTypes';
 import { SelectionManager } from '../util/SelectionManager';
 import { undoBatch } from '../util/UndoManager';
 import { CollectionViewType } from './collections/CollectionView';
-import { InkStroke } from './InkStroke';
+import { InkingStroke } from './InkingStroke';
 import { DocumentView } from './nodes/DocumentView';
 import './PropertiesButtons.scss';
 import React = require("react");
@@ -56,7 +56,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         return this.propertyToggleBtn("Dictate", "_showAudio", on => `${on ? "Hide" : "Show"} dictation/recording controls`, on => "microphone");
     }
     @computed get maskButton() {
-        return this.propertyToggleBtn("Mask", "isInkMask", on => on ? "Make plain ink" : "Make highlight mask", on => "paint-brush", (dv, doc) => InkStroke.toggleMask(dv?.layoutDoc || doc));
+        return this.propertyToggleBtn("Mask", "isInkMask", on => on ? "Make plain ink" : "Make highlight mask", on => "paint-brush", (dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc));
     }
     @computed get clustersButton() {
         return this.propertyToggleBtn("Clusters", "_useClusters", on => `${on ? "Hide" : "Show"} clusters`, on => "braille");
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 65061d260..6e6fabd0d 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -25,7 +25,7 @@ import { undoBatch } from "../../util/UndoManager";
 import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
 import { EditableView } from "../EditableView";
 import { GestureOverlay } from "../GestureOverlay";
-import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, ActiveArrowStart, ActiveArrowEnd } from "../InkStroke";
+import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, ActiveArrowStart, ActiveArrowEnd } from "../InkingStroke";
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
 import { DocumentView } from "../nodes/DocumentView";
 import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9e81a6cda..accb80c5a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -32,7 +32,7 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"
 import { Timeline } from "../../animationtimeline/Timeline";
 import { ContextMenu } from "../../ContextMenu";
 import { DocumentDecorations } from "../../DocumentDecorations";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkStroke";
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkingStroke";
 import { LightboxView } from "../../LightboxView";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView";
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 744a9c5de..092823603 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -13,7 +13,7 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { DocComponent } from "../DocComponent";
-import { InkStroke } from "../InkStroke";
+import { InkingStroke } from "../InkingStroke";
 import { StyleProp } from "../StyleProvider";
 import "./CollectionFreeFormDocumentView.scss";
 import { DocumentView, DocumentViewProps } from "./DocumentView";
@@ -38,7 +38,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @observable _animPos: number[] | undefined = undefined;
     @observable _contentView: DocumentView | undefined | null;
     get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive
-    get maskCentering() { return this.props.Document.isInkMask ? InkStroke.MaskDim / 2 : 0; }
+    get maskCentering() { return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; }
     get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${this.props.jitterRotation}deg)`; }
     get X() { return this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
     get Y() { return this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 2d466763b..8da5cd1b1 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -12,7 +12,7 @@ import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 import { SelectionManager } from "../../util/SelectionManager";
 import { undoBatch } from "../../util/UndoManager";
 import { ViewBoxBaseComponent } from "../DocComponent";
-import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth } from "../InkStroke";
+import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke";
 import "./ColorBox.scss";
 import { FieldView, FieldViewProps } from './FieldView';
 import { RichTextMenu } from "./formattedText/RichTextMenu";
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 5da3a52d5..f0a54e4ac 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -10,7 +10,7 @@ import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionSchemaView } from "../collections/CollectionSchemaView";
 import { CollectionView } from "../collections/CollectionView";
-import { InkStroke } from "../InkStroke";
+import { InkingStroke } from "../InkingStroke";
 import { PresElementBox } from "../presentationview/PresElementBox";
 import { SearchBox } from "../search/SearchBox";
 import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
@@ -224,7 +224,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
                     FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, EquationBox, SliderBox, FieldView,
                     CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
                     PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, FilterBox, FunctionPlotBox,
-                    ColorBox, DashWebRTCVideo, LinkAnchorBox, InkStroke, LinkBox, ScriptingBox,
+                    ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, LinkBox, ScriptingBox,
                     ScreenshotBox,
                     HTMLtag, ComparisonBox
                 }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 924b0e7d3..b861669f8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -34,7 +34,7 @@ import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from "../DocComponent";
 import { EditableView } from '../EditableView';
-import { InkStroke } from "../InkStroke";
+import { InkingStroke } from "../InkingStroke";
 import { LightboxView } from "../LightboxView";
 import { StyleLayers, StyleProp } from "../StyleProvider";
 import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
@@ -1138,8 +1138,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     render() {
         TraceMobx();
-        const xshift = () => (this.props.Document.isInkMask ? InkStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
-        const yshift = () => (this.props.Document.isInkMask ? InkStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+        const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
+        const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
         return (<div className="contentFittingDocumentView">
             {!this.props.Document || !this.props.PanelWidth() ? (null) : (
                 <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
-- 
cgit v1.2.3-70-g09d2


From e3b1401b854e5f968385dcc469c02fae86308bea Mon Sep 17 00:00:00 2001
From: geireann <60007097+geireann@users.noreply.github.com>
Date: Sat, 17 Jul 2021 13:51:25 -0400
Subject: added broken indices to layoutDoc

:broken_heart:
---
 src/client/views/InkHandles.tsx         | 60 +++++++++++++++++++--------------
 src/client/views/InkStrokeProperties.ts | 31 +++++++++++------
 src/client/views/InkingStroke.tsx       | 60 +++++++++++++++++----------------
 3 files changed, 86 insertions(+), 65 deletions(-)

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx
index ba3fdf9db..ffb6986f1 100644
--- a/src/client/views/InkHandles.tsx
+++ b/src/client/views/InkHandles.tsx
@@ -6,8 +6,12 @@ import { setupMoveUpEvents, emptyFunction } from "../../Utils";
 import { UndoManager } from "../util/UndoManager";
 import { InkData, HandlePoint, HandleLine } from "../../fields/InkField";
 import { Transform } from "../util/Transform";
+import { Doc } from "../../fields/Doc";
+import { listSpec } from "../../fields/Schema";
+import { Cast } from "../../fields/Types";
 
 export interface InkControlProps {
+    inkDoc: Doc;
     data: InkData;
     format: number[];
     ScreenToLocalTransform: () => Transform;
@@ -19,7 +23,7 @@ export class InkHandles extends React.Component<InkControlProps> {
      * Handles the movement of a selected handle point when the user clicks and drags.
      * @param handleNum The index of the currently selected handle point.
      */
-     onHandleDown = (e: React.PointerEvent, handleIndex: number): void => {
+    onHandleDown = (e: React.PointerEvent, handleIndex: number): void => {
         if (InkStrokeProperties.Instance) {
             InkStrokeProperties.Instance.moveControl(0, 0, 1);
             const controlUndo = UndoManager.StartBatch("DocDecs set radius");
@@ -29,9 +33,9 @@ export class InkHandles extends React.Component<InkControlProps> {
             const controlIndex = order === 1 ? handleIndex - 1 : handleIndex + 2;
             document.addEventListener("keydown", (e: KeyboardEvent) => this.onBreakTangent(e, controlIndex), true);
             setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
-                    InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
-                    return false;
-                }, () => controlUndo?.end(), emptyFunction
+                InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
+                return false;
+            }, () => controlUndo?.end(), emptyFunction
             );
         }
     }
@@ -43,13 +47,19 @@ export class InkHandles extends React.Component<InkControlProps> {
      */
     @action
     onBreakTangent = (e: KeyboardEvent, controlIndex: number) => {
+        const doc: Doc = this.props.inkDoc;
         if (["Alt"].includes(e.key)) {
-            if (!InkStrokeProperties.Instance?._brokenIndices.includes(controlIndex)) {
-                InkStrokeProperties.Instance?._brokenIndices.push(controlIndex);
+            if (doc) {
+                let brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
+                if (brokenIndices && brokenIndices.includes(controlIndex)) {
+                    brokenIndices.push(controlIndex);
+                }
+
+                doc.brokenInkIndices = brokenIndices;
             }
         }
     }
- 
+
     render() {
         const formatInstance = InkStrokeProperties.Instance;
         if (!formatInstance) return (null);
@@ -76,33 +86,33 @@ export class InkHandles extends React.Component<InkControlProps> {
             <>
                 {handlePoints.map((pts, i) =>
                     <svg height="10" width="10" key={`hdl${i}`}>
-                        <circle 
-                            cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} 
-                            cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} 
-                            r={strokeWidth / 2} 
-                            strokeWidth={0} 
+                        <circle
+                            cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
+                            cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
+                            r={strokeWidth / 2}
+                            strokeWidth={0}
                             fill="#1F85DE"
-                            onPointerDown={(e) => this.onHandleDown(e, pts.I)} 
-                            pointerEvents="all" 
-                            cursor="default" 
+                            onPointerDown={(e) => this.onHandleDown(e, pts.I)}
+                            pointerEvents="all"
+                            cursor="default"
                             display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
                     </svg>)}
                 {handleLines.map((pts, i) =>
                     <svg height="100" width="100" key={`line${i}`}>
-                        <line 
-                            x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} 
+                        <line
+                            x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
                             y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
-                            x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} 
-                            y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} 
-                            stroke="#1F85DE" 
+                            x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
+                            y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
+                            stroke="#1F85DE"
                             strokeWidth={dotsize / 8}
                             display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
-                        <line 
-                            x1={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} 
+                        <line
+                            x1={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
                             y1={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
-                            x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} 
-                            y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} 
-                            stroke="#1F85DE" 
+                            x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
+                            y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
+                            stroke="#1F85DE"
                             strokeWidth={dotsize / 8}
                             display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
                     </svg>)}
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index a3f7562e0..4c225210e 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,7 +1,9 @@
 import { action, computed, observable } from "mobx";
-import { Doc, Field, Opt } from "../../fields/Doc";
+import { Doc, DocListCast, Field, Opt } from "../../fields/Doc";
 import { Document } from "../../fields/documentSchemas";
 import { InkField, InkData, PointData, ControlPoint } from "../../fields/InkField";
+import { List } from "../../fields/List";
+import { listSpec } from "../../fields/Schema";
 import { Cast, NumCast } from "../../fields/Types";
 import { DocumentType } from "../documents/DocumentTypes";
 import { SelectionManager } from "../util/SelectionManager";
@@ -13,7 +15,7 @@ export class InkStrokeProperties {
     @observable _lock = false;
     @observable _controlButton = false;
     @observable _currentPoint = -1;
-    @observable _brokenIndices: number[] = [];
+    // @observable _brokenIndices: List<number> = new List<number>();
 
     constructor() {
         InkStrokeProperties.Instance = this;
@@ -110,14 +112,20 @@ export class InkStrokeProperties {
                 }
                 
             }
+
+            let brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
+
             // Updating the indices of the control points whose handle tangency has been broken.
-            this._brokenIndices = this._brokenIndices.map((control) => { 
-                if (control >= newIndex) {
-                    return control + 4; 
-                } else {
-                    return control;
-                }
-            });
+            if(brokenIndices){
+                brokenIndices = new List(brokenIndices.map((control) => { 
+                    if (control >= newIndex) {
+                        return control + 4; 
+                    } else {
+                        return control;
+                    }
+                }));
+            }
+            doc.brokenInkIndices = brokenIndices;
             this._currentPoint = -1;
             return newPoints;
         });
@@ -250,7 +258,7 @@ export class InkStrokeProperties {
 
     snapHandleTangent = (controlIndex: number, handleIndexA: number, handleIndexB: number) => {
         this.applyFunction((doc: Doc, ink: InkData) => {
-            this._brokenIndices.splice(this._brokenIndices.indexOf(controlIndex), 1);
+            // doc.brokenIndices.splice(this._brokenIndices.indexOf(controlIndex), 1);
             const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]];
             const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI);
             const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint);
@@ -315,8 +323,9 @@ export class InkStrokeProperties {
             const controlPoint = ink[controlIndex];
             const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale };
             ink[handleIndex] = newHandlePoint;
+            let brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
             // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle).
-            if (!this._brokenIndices.includes(controlIndex) && handleIndex !== 1 && handleIndex !== ink.length - 2) {
+            if (brokenIndices && !brokenIndices.includes(controlIndex) && handleIndex !== 1 && handleIndex !== ink.length - 2) {
                 const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint);
                 oppositeHandlePoint = this.rotatePoint(oppositeHandlePoint, controlPoint, angle);
                 ink[oppositeHandleIndex] = oppositeHandlePoint;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index b630c20cc..bd71aaf19 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -34,8 +34,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         this._properties = InkStrokeProperties.Instance;
     }
 
-    public static LayoutString(fieldStr: string) { 
-        return FieldView.LayoutString(InkingStroke, fieldStr); 
+    public static LayoutString(fieldStr: string) {
+        return FieldView.LayoutString(InkingStroke, fieldStr);
     }
 
     analyzeStrokes() {
@@ -57,9 +57,9 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
      */
     onPointerDown = (e: React.PointerEvent) => {
         if (this.props.isSelected(true)) {
-            setupMoveUpEvents(this, e, returnFalse, emptyFunction, 
+            setupMoveUpEvents(this, e, returnFalse, emptyFunction,
                 action((e: PointerEvent, doubleTap: boolean | undefined) =>
-                doubleTap && this._properties && (this._properties._controlButton = true))
+                    doubleTap && this._properties && (this._properties._controlButton = true))
             );
         }
     }
@@ -68,6 +68,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         TraceMobx();
         // Extracting the ink data and formatting information of the current ink stroke.
         const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+        const inkDoc: Doc = this.layoutDoc;
         const strokeWidth = Number(this.layoutDoc.strokeWidth);
         const lineTop = Math.min(...data.map(p => p.Y));
         const lineBottom = Math.max(...data.map(p => p.Y));
@@ -85,31 +86,31 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
 
         // Visually renders the polygonal line made by the user.
-        const inkLine = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth, 
-            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), 
-            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), 
-            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 
-            this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, 
+        const inkLine = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
+            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
+            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
+            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none",
+            this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1,
             false);
         // Thin blue line indicating that the current ink stroke is selected.
         const selectedLine = InteractionUtils.CreatePolyline(
-            data, lineLeft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6, 
-            strokeWidth / 6,StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
+            data, lineLeft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6,
+            strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
-            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 
-            this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, 
+            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none",
+            this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1,
             false);
         // Invisible polygonal line that enables the ink to be selected by the user.
-        const clickableLine = InteractionUtils.CreatePolyline(data, left, top, 
-            this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, 
-            strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier), 
-            StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "", 
+        const clickableLine = InteractionUtils.CreatePolyline(data, left, top,
+            this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth,
+            strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier),
+            StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "",
             this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
         // Set of points rendered upon the ink that can be added if a user clicks on one.
         const addedPoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth,
             StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
-            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 
+            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none",
             this.props.isSelected() && strokeWidth <= 5, false);
 
         return (
@@ -126,25 +127,26 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     if (cm) {
                         !Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
                         cm.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
-                        cm.addItem({ description: "Edit Points", event: action(() => {if (this._properties) { this._properties._controlButton = !this._properties._controlButton; }} ), icon: "paint-brush" });
+                        cm.addItem({ description: "Edit Points", event: action(() => { if (this._properties) { this._properties._controlButton = !this._properties._controlButton; } }), icon: "paint-brush" });
                     }
                 }}
             >
-                
+
                 {clickableLine}
                 {inkLine}
                 {this.props.isSelected() ? selectedLine : ""}
-                {this.props.isSelected() && this._properties?._controlButton ? 
+                {this.props.isSelected() && this._properties?._controlButton ?
                     <>
-                        <InkControls 
-                            data={data} 
-                            addedPoints={addedPoints} 
-                            format={[left, top, scaleX, scaleY, strokeWidth, dotsize]} 
+                        <InkControls
+                            data={data}
+                            addedPoints={addedPoints}
+                            format={[left, top, scaleX, scaleY, strokeWidth, dotsize]}
+                            ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
+                        <InkHandles
+                            inkDoc={inkDoc}
+                            data={data}
+                            format={[left, top, scaleX, scaleY, strokeWidth, dotsize]}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
-                        <InkHandles 
-                            data={data} 
-                            format={[left, top, scaleX, scaleY, strokeWidth, dotsize]} 
-                            ScreenToLocalTransform={this.props.ScreenToLocalTransform} /> 
                     </> : ""}
             </svg>
         );
-- 
cgit v1.2.3-70-g09d2


From 16c2a900e9116474caa67e4d5de15e2c51477292 Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Tue, 27 Jul 2021 18:54:53 -0400
Subject: changing format of controls/handles + adding color enums

---
 src/client/views/InkControls.tsx        | 73 ++++++++++++++++++---------------
 src/client/views/InkHandles.tsx         | 13 +++---
 src/client/views/InkStrokeProperties.ts | 38 +++++++++--------
 src/client/views/InkingStroke.tsx       | 40 +++++++-----------
 4 files changed, 84 insertions(+), 80 deletions(-)

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx
index 23f22c774..090af10cc 100644
--- a/src/client/views/InkControls.tsx
+++ b/src/client/views/InkControls.tsx
@@ -6,8 +6,13 @@ import { setupMoveUpEvents, emptyFunction } from "../../Utils";
 import { UndoManager } from "../util/UndoManager";
 import { ControlPoint, InkData, PointData } from "../../fields/InkField";
 import { Transform } from "../util/Transform";
+import { Colors } from "./global/globalEnums";
+import { Doc } from "../../fields/Doc";
+import { listSpec } from "../../fields/Schema";
+import { Cast } from "../../fields/Types";
 
 export interface InkControlProps {
+    inkDoc: Doc;
     data: InkData;
     addedPoints: PointData[];
     format: number[];
@@ -30,19 +35,22 @@ export class InkControls extends React.Component<InkControlProps> {
             const controlUndo = UndoManager.StartBatch("DocDecs set radius");
             const screenScale = this.props.ScreenToLocalTransform().Scale;
             const order = controlIndex % 4;
-            const handleIndexA  = order === 2 ? controlIndex - 1 : controlIndex - 2;
+            const handleIndexA = order === 2 ? controlIndex - 1 : controlIndex - 2;
             const handleIndexB = order === 2 ? controlIndex + 2 : controlIndex + 1;
+            const brokenIndices = Cast(this.props.inkDoc, listSpec("number"));
             setupMoveUpEvents(this, e,
                 (e: PointerEvent, down: number[], delta: number[]) => {
                     InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex);
                     return false;
                 },
-                () => controlUndo?.end(), 
-                emptyFunction);
-                // action((e: PointerEvent, doubleTap: boolean | undefined) =>
-                // { if (doubleTap && InkStrokeProperties.Instance?._brokenIndices.includes(controlIndex)) {
-                //     InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
-                // }}));
+                () => controlUndo?.end(),
+                brokenIndices ?
+                    action((e: PointerEvent, doubleTap: boolean | undefined) => {
+                        if (doubleTap && brokenIndices.includes(controlIndex)) {
+                            InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
+                        }
+                    })
+                    : emptyFunction);
         }
     }
 
@@ -75,7 +83,7 @@ export class InkControls extends React.Component<InkControlProps> {
     @action onLeaveControl = () => { this._overControl = -1; };
     @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; };
     @action onLeaveAddPoint = () => { this._overAddPoint = -1; };
- 
+
     render() {
         const formatInstance = InkStrokeProperties.Instance;
         if (!formatInstance) return (null);
@@ -90,41 +98,42 @@ export class InkControls extends React.Component<InkControlProps> {
             }
         }
         const addedPoints = this.props.addedPoints;
-        const [left, top, scaleX, scaleY, strokeWidth, dotsize] = this.props.format;
+        const [left, top, scaleX, scaleY, strokeWidth] = this.props.format;
 
         return (
             <>
                 {addedPoints.map((pts, i) =>
                     <svg height="10" width="10" key={`add${i}`}>
-                        <circle 
-                            cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} 
-                            cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} 
-                            r={strokeWidth / 2} 
-                            stroke={this._overAddPoint === i ? "#1F85DE" : "transparent"} 
-                            strokeWidth={dotsize / 4} fill={this._overAddPoint === i ? "#1F85DE" : "transparent"}
-                            onPointerDown={() => { formatInstance?.addPoints(pts.X, pts.Y, addedPoints, i, controlPoints); }} 
-                            onMouseEnter={() => this.onEnterAddPoint(i)} 
-                            onMouseLeave={this.onLeaveAddPoint} 
-                            pointerEvents="all" 
+                        <circle
+                            cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
+                            cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
+                            r={strokeWidth / 1.5}
+                            stroke={this._overAddPoint === i ? Colors.MEDIUM_BLUE : "transparent"}
+                            strokeWidth={0} fill={this._overAddPoint === i ? Colors.MEDIUM_BLUE : "transparent"}
+                            onPointerDown={() => { formatInstance?.addPoints(pts.X, pts.Y, addedPoints, i, controlPoints); }}
+                            onMouseEnter={() => this.onEnterAddPoint(i)}
+                            onMouseLeave={this.onLeaveAddPoint}
+                            pointerEvents="all"
                             cursor="all-scroll"
                         />
                     </svg>
                 )}
                 {controlPoints.map((control, i) =>
                     <svg height="10" width="10" key={`ctrl${i}`}>
-                        <rect 
-                            x={(control.X - left - strokeWidth / 2) * scaleX} 
-                            y={(control.Y - top - strokeWidth / 2) * scaleY} 
-                            height={this._overControl === i ? strokeWidth * 1.5 : strokeWidth} 
-                            width={this._overControl === i ? strokeWidth * 1.5 : strokeWidth} 
-                            strokeWidth={strokeWidth / 6} stroke="#1F85DE" 
-                            fill={formatInstance?._currentPoint === control.I ? "#1F85DE" : "white"}
-                            onPointerDown={(e) => { 
-                                this.changeCurrPoint(control.I); 
-                                this.onControlDown(e, control.I); }} 
-                            onMouseEnter={() => this.onEnterControl(i)} 
-                            onMouseLeave={this.onLeaveControl} 
-                            pointerEvents="all" 
+                        <rect
+                            x={(control.X - left - strokeWidth / 2) * scaleX}
+                            y={(control.Y - top - strokeWidth / 2) * scaleY}
+                            height={this._overControl === i ? strokeWidth * 1.5 : strokeWidth}
+                            width={this._overControl === i ? strokeWidth * 1.5 : strokeWidth}
+                            strokeWidth={strokeWidth / 6} stroke={Colors.MEDIUM_BLUE}
+                            fill={formatInstance?._currentPoint === control.I ? Colors.MEDIUM_BLUE : Colors.WHITE}
+                            onPointerDown={(e) => {
+                                this.changeCurrPoint(control.I);
+                                this.onControlDown(e, control.I);
+                            }}
+                            onMouseEnter={() => this.onEnterControl(i)}
+                            onMouseLeave={this.onLeaveControl}
+                            pointerEvents="all"
                             cursor="default"
                         />
                     </svg>
diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx
index 28b6dd820..6e890e184 100644
--- a/src/client/views/InkHandles.tsx
+++ b/src/client/views/InkHandles.tsx
@@ -10,6 +10,7 @@ import { Doc } from "../../fields/Doc";
 import { listSpec } from "../../fields/Schema";
 import { List } from "../../fields/List";
 import { Cast } from "../../fields/Types";
+import { Colors } from "./global/globalEnums";
 
 export interface InkHandlesProps {
     inkDoc: Doc;
@@ -80,7 +81,7 @@ export class InkHandles extends React.Component<InkHandlesProps> {
                 handleLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
             }
         }
-        const [left, top, scaleX, scaleY, strokeWidth, dotsize] = this.props.format;
+        const [left, top, scaleX, scaleY, strokeWidth] = this.props.format;
 
         return (
             <>
@@ -91,7 +92,7 @@ export class InkHandles extends React.Component<InkHandlesProps> {
                             cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
                             r={strokeWidth / 2}
                             strokeWidth={0}
-                            fill="#1F85DE"
+                            fill={Colors.MEDIUM_BLUE}
                             onPointerDown={(e) => this.onHandleDown(e, pts.I)}
                             pointerEvents="all"
                             cursor="default"
@@ -104,16 +105,16 @@ export class InkHandles extends React.Component<InkHandlesProps> {
                             y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
                             x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
                             y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
-                            stroke="#1F85DE"
-                            strokeWidth={dotsize / 8}
+                            stroke={Colors.MEDIUM_BLUE}
+                            strokeWidth={strokeWidth / 4}
                             display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
                         <line
                             x1={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
                             y1={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
                             x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
                             y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
-                            stroke="#1F85DE"
-                            strokeWidth={dotsize / 8}
+                            stroke={Colors.MEDIUM_BLUE}
+                            strokeWidth={strokeWidth / 4}
                             display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
                     </svg>)}
             </>
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 1a3585f3e..de8cf80bd 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -35,7 +35,7 @@ export class InkStrokeProperties {
      * @param func The inputted function.
      * @param requireCurrPoint Indicates whether the current selected point is needed.
      */
-     applyFunction = (func: (doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => {
+    applyFunction = (func: (doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => {
         var appliedFunc = false;
         this.selectedInk?.forEach(action(inkView => {
             if (this.selectedInk?.length === 1 && (!requireCurrPoint || this._currentPoint !== -1)) {
@@ -91,16 +91,16 @@ export class InkStrokeProperties {
                     });
                 }
             }
-            if (end === 0) end = points.length-1;
+            if (end === 0) end = points.length - 1;
             // Index of new control point with regards to the ink data.
             const newIndex = Math.floor(counter / 2) * 4 + 2;
             // Creating new ink data with the new control point and handle points inputted.
             for (let i = 0; i < ink.length; i++) {
-                if (i === newIndex) { 
-                    const [handleA, handleB] = this.getNewHandlePoints(points.slice(start, index+1), points.slice(index, end), newControl);
+                if (i === newIndex) {
+                    const [handleA, handleB] = this.getNewHandlePoints(points.slice(start, index + 1), points.slice(index, end), newControl);
                     newPoints.push(handleA, newControl, newControl, handleB);
                     // Adjusting the magnitude of the left handle line of the right neighboring control point.
-                    const [rightControl, rightHandle]  = [points[end], ink[i]];
+                    const [rightControl, rightHandle] = [points[end], ink[i]];
                     const scaledVector = this.getScaledHandlePoint(false, start, end, index, rightControl, rightHandle);
                     rightHandle && newPoints.push({ X: rightControl.X - scaledVector.X, Y: rightControl.Y - scaledVector.Y });
                 } else if (i === newIndex - 1) {
@@ -111,14 +111,14 @@ export class InkStrokeProperties {
                 } else {
                     ink[i] && newPoints.push({ X: ink[i].X, Y: ink[i].Y });
                 }
-                
+
             }
             let brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
             // Updating the indices of the control points whose handle tangency has been broken.
             if (brokenIndices) {
-                brokenIndices = new List(brokenIndices.map((control) => { 
+                brokenIndices = new List(brokenIndices.map((control) => {
                     if (control >= newIndex) {
-                        return control + 4; 
+                        return control + 4;
                     } else {
                         return control;
                     }
@@ -158,7 +158,7 @@ export class InkStrokeProperties {
     getNewHandlePoints = (C: PointData[], D: PointData[], newControl: PointData) => {
         const [m, n] = [C.length, D.length];
         let handleSizeA = Math.sqrt((Math.pow(newControl.X - C[0].X, 2)) + (Math.pow(newControl.Y - C[0].Y, 2)));
-        let handleSizeB = Math.sqrt((Math.pow(D[n-1].X - newControl.X, 2)) + (Math.pow(D[n-1].Y - newControl.Y, 2)));
+        let handleSizeB = Math.sqrt((Math.pow(D[n - 1].X - newControl.X, 2)) + (Math.pow(D[n - 1].Y - newControl.Y, 2)));
         // Scaling adjustments to improve the ratio between the magnitudes of the two handle lines.
         // (Ensures that the new point added doesn't augment the inital shape of the curve much).
         if (handleSizeA < 75 && handleSizeB < 75) {
@@ -173,7 +173,7 @@ export class InkStrokeProperties {
             handleSizeB *= 2;
         }
         // Finding the last leg of the derivative curve of C.
-        const dC = { X: (handleSizeA / n) * (C[m-1].X - C[m-2].X), Y: (handleSizeA / n) * (C[m-1].Y - C[m-2].Y) };
+        const dC = { X: (handleSizeA / n) * (C[m - 1].X - C[m - 2].X), Y: (handleSizeA / n) * (C[m - 1].Y - C[m - 2].Y) };
         // Finding the first leg of the derivative curve of D. 
         const dD = { X: (handleSizeB / m) * (D[1].X - D[0].X), Y: (handleSizeB / m) * (D[1].Y - D[0].Y) };
         const handleA = { X: newControl.X - dC.X, Y: newControl.Y - dC.Y };
@@ -259,13 +259,17 @@ export class InkStrokeProperties {
 
     snapHandleTangent = (controlIndex: number, handleIndexA: number, handleIndexB: number) => {
         this.applyFunction((doc: Doc, ink: InkData) => {
-            // doc.brokenIndices.splice(this._brokenIndices.indexOf(controlIndex), 1);
-            const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]];
-            const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI);
-            const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint);
-            const newHandleB = this.rotatePoint(handleB, controlPoint, angleDifference);
-            ink[handleIndexB] = newHandleB;
-            return ink;
+            const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
+            if (brokenIndices) {
+                brokenIndices.splice(brokenIndices.indexOf(controlIndex), 1);
+                doc.brokenInkIndices = brokenIndices;
+                const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]];
+                const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI);
+                const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint);
+                const newHandleB = this.rotatePoint(handleB, controlPoint, angleDifference);
+                ink[handleIndexB] = newHandleB;
+                return ink;
+            }
         });
     }
 
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index bd71aaf19..a8f32118c 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -19,6 +19,7 @@ import { InkStrokeProperties } from "./InkStrokeProperties";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { InkControls } from "./InkControls";
 import { InkHandles } from "./InkHandles";
+import { Colors } from "./global/globalEnums";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
@@ -66,6 +67,9 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
 
     render() {
         TraceMobx();
+        // if (!this.props.isSelected() && this._properties) {
+        //     this._properties._controlButton = false;
+        // }
         // Extracting the ink data and formatting information of the current ink stroke.
         const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
         const inkDoc: Doc = this.layoutDoc;
@@ -86,32 +90,17 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const dotsize = Math.max(width * scaleX, height * scaleY) / 40;
 
         // Visually renders the polygonal line made by the user.
-        const inkLine = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
-            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
-            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
-            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none",
-            this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1,
-            false);
+        const inkLine = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
+            StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, false);
         // Thin blue line indicating that the current ink stroke is selected.
-        const selectedLine = InteractionUtils.CreatePolyline(
-            data, lineLeft - strokeWidth * 3, lineTop - strokeWidth * 3, "#1F85DE", strokeWidth / 6,
-            strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
-            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
-            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none",
-            this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1,
-            false);
+        const selectedLine = InteractionUtils.CreatePolyline(data, left - strokeWidth / 3, top - strokeWidth / 3, Colors.MEDIUM_BLUE, strokeWidth / 6, strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
+            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, false);
         // Invisible polygonal line that enables the ink to be selected by the user.
-        const clickableLine = InteractionUtils.CreatePolyline(data, left, top,
-            this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth,
-            strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier),
-            StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "",
-            this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
+        const clickableLine = InteractionUtils.CreatePolyline(data, left, top, this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier),
+            StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
         // Set of points rendered upon the ink that can be added if a user clicks on one.
-        const addedPoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth,
-            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
-            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
-            StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none",
-            this.props.isSelected() && strokeWidth <= 5, false);
+        const addedPoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
+            StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
 
         return (
             <svg className="inkStroke"
@@ -138,14 +127,15 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                 {this.props.isSelected() && this._properties?._controlButton ?
                     <>
                         <InkControls
+                            inkDoc={inkDoc}
                             data={data}
                             addedPoints={addedPoints}
-                            format={[left, top, scaleX, scaleY, strokeWidth, dotsize]}
+                            format={[left, top, scaleX, scaleY, strokeWidth]}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                         <InkHandles
                             inkDoc={inkDoc}
                             data={data}
-                            format={[left, top, scaleX, scaleY, strokeWidth, dotsize]}
+                            format={[left, top, scaleX, scaleY, strokeWidth]}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                     </> : ""}
             </svg>
-- 
cgit v1.2.3-70-g09d2


From 09e5f2c58ca87ded318627cbc5dea436989fcff2 Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Thu, 29 Jul 2021 09:51:50 -0400
Subject: deselecting an ink stroke properly exits edit view

---
 src/client/views/InkingStroke.tsx | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index a8f32118c..21059b330 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -65,11 +65,19 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         }
     }
 
+    /**
+     * Ensures the ink controls and handles aren't rendered when the current ink stroke is reselected.
+     */
+    @action
+    toggleControlButton = () => {
+        if (!this.props.isSelected() && this._properties) {
+            this._properties._controlButton = false;
+        }
+    }
+
     render() {
         TraceMobx();
-        // if (!this.props.isSelected() && this._properties) {
-        //     this._properties._controlButton = false;
-        // }
+        this.toggleControlButton();
         // Extracting the ink data and formatting information of the current ink stroke.
         const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
         const inkDoc: Doc = this.layoutDoc;
-- 
cgit v1.2.3-70-g09d2


From 43119dc3fc8b156f9e15deab7b2e3dd5b541d7af Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 4 Aug 2021 20:14:30 -0400
Subject: fixed toggle ink as a mask.

---
 src/client/views/InkingStroke.tsx | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 21059b330..63cefbf67 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -44,14 +44,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
     }
 
-    @action
-    public static toggleMask = (inkDoc: Doc) => {
+    public static toggleMask = action((inkDoc: Doc) => {
         inkDoc.isInkMask = !inkDoc.isInkMask;
         inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
         inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
         inkDoc.color = "#9b9b9bff";
         inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
-    }
+    });
 
     /**
      * Handles the movement of the entire ink object when the user clicks and drags.
-- 
cgit v1.2.3-70-g09d2


From f4da02dac8ea9d92443533b82bee2557cdd8957e Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Wed, 11 Aug 2021 20:02:23 -0400
Subject: added circle shape generation using only 4 Bézier curves
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/client/util/InteractionUtils.tsx    |  4 ---
 src/client/views/GestureOverlay.tsx     | 54 +++++++++++++++------------------
 src/client/views/InkControls.tsx        | 24 ++++++++++++++-
 src/client/views/InkStrokeProperties.ts |  9 ++++--
 src/client/views/InkingStroke.tsx       |  2 +-
 5 files changed, 56 insertions(+), 37 deletions(-)

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index ba935e3bf..cb6a53157 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -299,8 +299,6 @@ export namespace InteractionUtils {
 
                 return points;
             case "circle":
-
-
                 const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
                 const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
                 const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
@@ -315,7 +313,6 @@ export namespace InteractionUtils {
                         points.push({ X: newX, Y: y });
                     }
                     points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((Math.min(top, bottom) - centerY), 2))) + centerX, Y: Math.min(top, bottom) });
-
                 } else {
                     for (var x = Math.min(left, right); x < Math.max(left, right); x++) {
                         const y = Math.sqrt(Math.pow(radius, 2) - (Math.pow((x - centerX), 2))) + centerY;
@@ -327,7 +324,6 @@ export namespace InteractionUtils {
                         points.push({ X: x, Y: newY });
                     }
                     points.push({ X: Math.min(left, right), Y: Math.sqrt(Math.pow(radius, 2) - (Math.pow((Math.min(left, right) - centerX), 2))) + centerY });
-
                 }
                 return points;
             // case "arrow":
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 6a4f55bef..f7b5cc030 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -726,39 +726,35 @@ export class GestureOverlay extends Touchable {
 
                 break;
             case "circle":
-
+                // Approximation of a circle using Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%,
+                // making the curves indistinguishable from a circle.
+                // Source: https://spencermortensen.com/articles/bezier-circle/
+                const c = 0.551915024494;
                 const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
                 const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
                 const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
-                if (centerX - Math.min(left, right) < centerY - Math.min(top, bottom)) {
-                    for (var y = Math.min(top, bottom); y < Math.max(top, bottom); y++) {
-                        const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
-                        this._points.push({ X: x, Y: y });
-                    }
-                    for (var y = Math.max(top, bottom); y > Math.min(top, bottom); y--) {
-                        const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX;
-                        const newX = centerX - (x - centerX);
-                        this._points.push({ X: newX, Y: y });
-                    }
-                    this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((Math.min(top, bottom) - centerY), 2))) + centerX, Y: Math.min(top, bottom) });
-                    this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((Math.min(top, bottom) - centerY), 2))) + centerX, Y: Math.min(top, bottom) - 1 });
-
-
-                } else {
-                    for (var x = Math.min(left, right); x < Math.max(left, right); x++) {
-                        const y = Math.sqrt(Math.pow(radius, 2) - (Math.pow((x - centerX), 2))) + centerY;
-                        this._points.push({ X: x, Y: y });
-                    }
-                    for (var x = Math.max(left, right); x > Math.min(left, right); x--) {
-                        const y = Math.sqrt(Math.pow(radius, 2) - (Math.pow((x - centerX), 2))) + centerY;
-                        const newY = centerY - (y - centerY);
-                        this._points.push({ X: x, Y: newY });
-                    }
-                    this._points.push({ X: Math.min(left, right), Y: Math.sqrt(Math.pow(radius, 2) - (Math.pow((Math.min(left, right) - centerX), 2))) + centerY });
-                    this._points.push({ X: Math.min(left, right), Y: Math.sqrt(Math.pow(radius, 2) - (Math.pow((Math.min(left, right) - centerX), 2))) + centerY - 1 });
-
-                }
+                // Dividing the circle into four equal sections, and fiting each section to a cubic Bézier curve.   
+                this._points.push({ X: centerX - radius, Y: centerY });
+                this._points.push({ X: centerX - radius, Y: centerY + (c * radius) });
+                this._points.push({ X: centerX - (c * radius), Y: centerY + radius });
+                this._points.push({ X: centerX, Y: centerY + radius });
+
+                this._points.push({ X: centerX, Y: centerY + radius });
+                this._points.push({ X: centerX + (c * radius), Y: centerY + radius });
+                this._points.push({ X: centerX + radius, Y: centerY + (c * radius) });
+                this._points.push({ X: centerX + radius, Y: centerY });
+
+                this._points.push({ X: centerX + radius, Y: centerY });
+                this._points.push({ X: centerX + radius, Y: centerY - (c * radius) });
+                this._points.push({ X: centerX + (c * radius), Y: centerY - radius });
+                this._points.push({ X: centerX, Y: centerY - radius });
+                
+                this._points.push({ X: centerX, Y: centerY - radius });
+                this._points.push({ X: centerX - (c * radius), Y: centerY - radius });
+                this._points.push({ X: centerX - radius, Y: centerY - (c * radius) });
+                this._points.push({ X: centerX - radius, Y: centerY });
                 break;
+
             case "line":
                 if (Math.abs(firstx - lastx) < 20) {
                     lastx = firstx;
diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx
index 6213a4075..55a70ccd2 100644
--- a/src/client/views/InkControls.tsx
+++ b/src/client/views/InkControls.tsx
@@ -87,16 +87,38 @@ export class InkControls extends React.Component<InkControlProps> {
         if (!formatInstance) return (null);
 
         // Accessing the current ink's data and extracting all control points.
+        // Separate case for circle shape (?)
         const data = this.props.data;
+        const [left, right, top, bottom, scaleX, scaleY, strokeWidth] = this.props.format;
+        const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
+        const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
+        const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
         const controlPoints: ControlPoint[] = [];
         if (data.length >= 4) {
+            // const distance = Math.sqrt((Math.pow(data[0].X - centerX, 2)) + (Math.pow(data[0].Y - centerY, 2)));
+            // if (Math.abs(distance - radius) <= 2.5) {
+            //     controlPoints.push({ X: data[0].X, Y: data[0].Y, I: 0 });
+            //     const topPoint = formatInstance.rotatePoint(data[0], { X: centerX, Y: centerY }, Math.PI / 2);
+            //     const rightPoint = formatInstance.rotatePoint(data[0], { X: centerX, Y: centerY }, Math.PI);
+            //     const bottomPoint = formatInstance.rotatePoint(data[0], { X: centerX, Y: centerY }, Math.PI * 1.5);
+            //     for (let i = 0; i <= data.length - 4; i += 4) {
+            //         const currPoint = data[i];
+            //         const isTopPoint = Math.sqrt((Math.pow(currPoint.X - topPoint.X, 2)) + (Math.pow(currPoint.Y - topPoint.Y, 2))) <= 2.5;
+            //         const isRightPoint = Math.sqrt((Math.pow(currPoint.X - rightPoint.X, 2)) + (Math.pow(currPoint.Y - rightPoint.Y, 2))) <= 2.5;
+            //         const isBottomPoint = Math.sqrt((Math.pow(currPoint.X - bottomPoint.X, 2)) + (Math.pow(currPoint.Y - bottomPoint.Y, 2))) <= 2.5;
+            //         if (isTopPoint || isRightPoint || isBottomPoint) {
+            //             controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
+            //         }
+            //     }
+            //     controlPoints.push({ X: data[0].X, Y: data[0].Y, I: 0 });
+            // } else {
             for (let i = 0; i <= data.length - 4; i += 4) {
                 controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
                 controlPoints.push({ X: data[i + 3].X, Y: data[i + 3].Y, I: i + 3 });
             }
+            // }
         }
         const addedPoints = this.props.addedPoints;
-        const [left, top, scaleX, scaleY, strokeWidth] = this.props.format;
 
         return (
             <>
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 76ca5b5ec..7ef6606c4 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -266,8 +266,13 @@ export class InkStrokeProperties {
         this.applyFunction((doc: Doc, ink: InkData) => {
             const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
             if (brokenIndices) {
-                brokenIndices.splice(brokenIndices.indexOf(controlIndex), 1);
-                doc.brokenInkIndices = brokenIndices;
+                let newBrokenIndices = new List;
+                for (let i = 0; i < brokenIndices.length; i++) {
+                    if (brokenIndices[i] !== controlIndex) {
+                        newBrokenIndices.push(brokenIndices[i]);
+                    }
+                }
+                doc.brokenInkIndices = newBrokenIndices;
                 const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]];
                 const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI);
                 const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint);
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 21059b330..d30244a8f 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -138,7 +138,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                             inkDoc={inkDoc}
                             data={data}
                             addedPoints={addedPoints}
-                            format={[left, top, scaleX, scaleY, strokeWidth]}
+                            format={[left, right, top, bottom, scaleX, scaleY, strokeWidth]}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                         <InkHandles
                             inkDoc={inkDoc}
-- 
cgit v1.2.3-70-g09d2


From 8695343975decf566d8590d314a2f5c5cd89121b Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Thu, 12 Aug 2021 12:48:24 -0400
Subject: fixed positioning of selection line

---
 src/client/util/InteractionUtils.tsx |  1 +
 src/client/views/GestureOverlay.tsx  | 10 ++++++++--
 src/client/views/InkHandles.tsx      |  3 +++
 src/client/views/InkingStroke.tsx    |  7 ++++++-
 4 files changed, 18 insertions(+), 3 deletions(-)

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index cb6a53157..32b70c4b4 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -210,6 +210,7 @@ export namespace InteractionUtils {
                 style={{
                     // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
                     fill: fill ? fill : "none",
+                    // opacity: 1.0,
                     opacity: strokeWidth !== width ? 0.5 : undefined,
                     pointerEvents: pevents as any,
                     stroke: color ?? "rgb(0, 0, 0)",
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index f7b5cc030..cf57ac7a1 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -726,7 +726,7 @@ export class GestureOverlay extends Touchable {
 
                 break;
             case "circle":
-                // Approximation of a circle using Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%,
+                // Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%,
                 // making the curves indistinguishable from a circle.
                 // Source: https://spencermortensen.com/articles/bezier-circle/
                 const c = 0.551915024494;
@@ -748,11 +748,17 @@ export class GestureOverlay extends Touchable {
                 this._points.push({ X: centerX + radius, Y: centerY - (c * radius) });
                 this._points.push({ X: centerX + (c * radius), Y: centerY - radius });
                 this._points.push({ X: centerX, Y: centerY - radius });
-                
+
                 this._points.push({ X: centerX, Y: centerY - radius });
                 this._points.push({ X: centerX - (c * radius), Y: centerY - radius });
                 this._points.push({ X: centerX - radius, Y: centerY - (c * radius) });
                 this._points.push({ X: centerX - radius, Y: centerY });
+
+                // this._points.push({ X: centerX - radius, Y: centerY });
+                // this._points.push({ X: centerX - radius, Y: centerY - (c * radius) });
+                // this._points.push({ X: centerX - (c * radius), Y: centerY - radius });
+                // this._points.push({ X: centerX, Y: centerY - radius });
+
                 break;
 
             case "line":
diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx
index f1eb4b9db..0b24c3c32 100644
--- a/src/client/views/InkHandles.tsx
+++ b/src/client/views/InkHandles.tsx
@@ -11,10 +11,12 @@ import { listSpec } from "../../fields/Schema";
 import { List } from "../../fields/List";
 import { Cast } from "../../fields/Types";
 import { Colors } from "./global/globalEnums";
+import { GestureOverlay } from "./GestureOverlay";
 
 export interface InkHandlesProps {
     inkDoc: Doc;
     data: InkData;
+    shape?: string;
     format: number[];
     ScreenToLocalTransform: () => Transform;
 }
@@ -68,6 +70,7 @@ export class InkHandles extends React.Component<InkHandlesProps> {
 
         // Accessing the current ink's data and extracting all handle points and handle lines.
         const data = this.props.data;
+        const shape = this.props.shape;
         const handlePoints: HandlePoint[] = [];
         const handleLines: HandleLine[] = [];
         if (data.length >= 4) {
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index d30244a8f..4beab6a62 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -20,6 +20,7 @@ import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { InkControls } from "./InkControls";
 import { InkHandles } from "./InkHandles";
 import { Colors } from "./global/globalEnums";
+import { GestureOverlay } from "./GestureOverlay";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
@@ -28,6 +29,7 @@ const InkDocument = makeInterface(documentSchema);
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
     static readonly MaskDim = 50000;
     @observable private _properties?: InkStrokeProperties;
+    // static InkShape = GestureOverlay.Instance.InkShape;
 
     constructor(props: FieldViewProps & InkDocument) {
         super(props);
@@ -79,6 +81,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         TraceMobx();
         this.toggleControlButton();
         // Extracting the ink data and formatting information of the current ink stroke.
+        // console.log(InkingStroke.InkShape);
+        const InkShape = GestureOverlay.Instance.InkShape;
         const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
         const inkDoc: Doc = this.layoutDoc;
         const strokeWidth = Number(this.layoutDoc.strokeWidth);
@@ -101,7 +105,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const inkLine = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
             StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, false);
         // Thin blue line indicating that the current ink stroke is selected.
-        const selectedLine = InteractionUtils.CreatePolyline(data, left - strokeWidth / 3, top - strokeWidth / 3, Colors.MEDIUM_BLUE, strokeWidth / 6, strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
+        const selectedLine = InteractionUtils.CreatePolyline(data, left, top, Colors.MEDIUM_BLUE, strokeWidth, strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, false);
         // Invisible polygonal line that enables the ink to be selected by the user.
         const clickableLine = InteractionUtils.CreatePolyline(data, left, top, this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier),
@@ -143,6 +147,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                         <InkHandles
                             inkDoc={inkDoc}
                             data={data}
+                            shape={InkShape}
                             format={[left, top, scaleX, scaleY, strokeWidth]}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                     </> : ""}
-- 
cgit v1.2.3-70-g09d2


From 200585ebdc40b1c9710c8028e4e25965eaa73a9d Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Thu, 12 Aug 2021 13:02:26 -0400
Subject: increasing opacity (selected lines are no longer slightly
 transparent)

---
 src/client/util/InteractionUtils.tsx | 4 ++--
 src/client/views/InkingStroke.tsx    | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 32b70c4b4..e009fb3a9 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -210,8 +210,8 @@ export namespace InteractionUtils {
                 style={{
                     // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
                     fill: fill ? fill : "none",
-                    // opacity: 1.0,
-                    opacity: strokeWidth !== width ? 0.5 : undefined,
+                    opacity: 1.0,
+                    // opacity: strokeWidth !== width ? 0.5 : undefined,
                     pointerEvents: pevents as any,
                     stroke: color ?? "rgb(0, 0, 0)",
                     strokeWidth: strokeWidth,
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 4beab6a62..affea61e3 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -108,7 +108,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const selectedLine = InteractionUtils.CreatePolyline(data, left, top, Colors.MEDIUM_BLUE, strokeWidth, strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
             StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, false);
         // Invisible polygonal line that enables the ink to be selected by the user.
-        const clickableLine = InteractionUtils.CreatePolyline(data, left, top, this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier),
+        const clickableLine = InteractionUtils.CreatePolyline(data, left, top, "transparent", strokeWidth, strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier),
             StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
         // Set of points rendered upon the ink that can be added if a user clicks on one.
         const addedPoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
-- 
cgit v1.2.3-70-g09d2


From b69fe78eadeb2bfa1f284e3e0333734b03b718a9 Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Fri, 13 Aug 2021 10:15:31 -0400
Subject: cleaning up

---
 src/client/views/GestureOverlay.tsx     |  7 +------
 src/client/views/InkControls.tsx        | 24 +-----------------------
 src/client/views/InkStrokeProperties.ts | 12 ++++++------
 src/client/views/InkingStroke.tsx       |  3 +--
 4 files changed, 9 insertions(+), 37 deletions(-)

(limited to 'src/client/views/InkingStroke.tsx')

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index cf57ac7a1..bbf21f22c 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -733,7 +733,7 @@ export class GestureOverlay extends Touchable {
                 const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
                 const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
                 const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
-                // Dividing the circle into four equal sections, and fiting each section to a cubic Bézier curve.   
+                // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve.   
                 this._points.push({ X: centerX - radius, Y: centerY });
                 this._points.push({ X: centerX - radius, Y: centerY + (c * radius) });
                 this._points.push({ X: centerX - (c * radius), Y: centerY + radius });
@@ -754,11 +754,6 @@ export class GestureOverlay extends Touchable {
                 this._points.push({ X: centerX - radius, Y: centerY - (c * radius) });
                 this._points.push({ X: centerX - radius, Y: centerY });
 
-                // this._points.push({ X: centerX - radius, Y: centerY });
-                // this._points.push({ X: centerX - radius, Y: centerY - (c * radius) });
-                // this._points.push({ X: centerX - (c * radius), Y: centerY - radius });
-                // this._points.push({ X: centerX, Y: centerY - radius });
-
                 break;
 
             case "line":
diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx
index 55a70ccd2..6213a4075 100644
--- a/src/client/views/InkControls.tsx
+++ b/src/client/views/InkControls.tsx
@@ -87,38 +87,16 @@ export class InkControls extends React.Component<InkControlProps> {
         if (!formatInstance) return (null);
 
         // Accessing the current ink's data and extracting all control points.
-        // Separate case for circle shape (?)
         const data = this.props.data;
-        const [left, right, top, bottom, scaleX, scaleY, strokeWidth] = this.props.format;
-        const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
-        const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
-        const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
         const controlPoints: ControlPoint[] = [];
         if (data.length >= 4) {
-            // const distance = Math.sqrt((Math.pow(data[0].X - centerX, 2)) + (Math.pow(data[0].Y - centerY, 2)));
-            // if (Math.abs(distance - radius) <= 2.5) {
-            //     controlPoints.push({ X: data[0].X, Y: data[0].Y, I: 0 });
-            //     const topPoint = formatInstance.rotatePoint(data[0], { X: centerX, Y: centerY }, Math.PI / 2);
-            //     const rightPoint = formatInstance.rotatePoint(data[0], { X: centerX, Y: centerY }, Math.PI);
-            //     const bottomPoint = formatInstance.rotatePoint(data[0], { X: centerX, Y: centerY }, Math.PI * 1.5);
-            //     for (let i = 0; i <= data.length - 4; i += 4) {
-            //         const currPoint = data[i];
-            //         const isTopPoint = Math.sqrt((Math.pow(currPoint.X - topPoint.X, 2)) + (Math.pow(currPoint.Y - topPoint.Y, 2))) <= 2.5;
-            //         const isRightPoint = Math.sqrt((Math.pow(currPoint.X - rightPoint.X, 2)) + (Math.pow(currPoint.Y - rightPoint.Y, 2))) <= 2.5;
-            //         const isBottomPoint = Math.sqrt((Math.pow(currPoint.X - bottomPoint.X, 2)) + (Math.pow(currPoint.Y - bottomPoint.Y, 2))) <= 2.5;
-            //         if (isTopPoint || isRightPoint || isBottomPoint) {
-            //             controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
-            //         }
-            //     }
-            //     controlPoints.push({ X: data[0].X, Y: data[0].Y, I: 0 });
-            // } else {
             for (let i = 0; i <= data.length - 4; i += 4) {
                 controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
                 controlPoints.push({ X: data[i + 3].X, Y: data[i + 3].Y, I: i + 3 });
             }
-            // }
         }
         const addedPoints = this.props.addedPoints;
+        const [left, top, scaleX, scaleY, strokeWidth] = this.props.format;
 
         return (
             <>
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 7ef6606c4..d527b2a05 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -266,12 +266,12 @@ export class InkStrokeProperties {
         this.applyFunction((doc: Doc, ink: InkData) => {
             const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
             if (brokenIndices) {
-                let newBrokenIndices = new List;
-                for (let i = 0; i < brokenIndices.length; i++) {
-                    if (brokenIndices[i] !== controlIndex) {
-                        newBrokenIndices.push(brokenIndices[i]);
+                const newBrokenIndices = new List;
+                brokenIndices.forEach(brokenIndex => {
+                    if (brokenIndex !== controlIndex) {
+                        newBrokenIndices.push(brokenIndex);
                     }
-                }
+                });
                 doc.brokenInkIndices = newBrokenIndices;
                 const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]];
                 const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI);
@@ -288,7 +288,7 @@ export class InkStrokeProperties {
      */
     @action
     rotatePoint = (target: PointData, origin: PointData, angle: number) => {
-        let rotatedTarget = { X: target.X - origin.X, Y: target.Y - origin.Y };
+        const rotatedTarget = { X: target.X - origin.X, Y: target.Y - origin.Y };
         const newX = Math.cos(angle) * rotatedTarget.X - Math.sin(angle) * rotatedTarget.Y;
         const newY = Math.sin(angle) * rotatedTarget.X + Math.cos(angle) * rotatedTarget.Y;
         rotatedTarget.X = newX + origin.X;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index affea61e3..834c3b745 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -29,7 +29,6 @@ const InkDocument = makeInterface(documentSchema);
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
     static readonly MaskDim = 50000;
     @observable private _properties?: InkStrokeProperties;
-    // static InkShape = GestureOverlay.Instance.InkShape;
 
     constructor(props: FieldViewProps & InkDocument) {
         super(props);
@@ -142,7 +141,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                             inkDoc={inkDoc}
                             data={data}
                             addedPoints={addedPoints}
-                            format={[left, right, top, bottom, scaleX, scaleY, strokeWidth]}
+                            format={[left, top, scaleX, scaleY, strokeWidth]}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                         <InkHandles
                             inkDoc={inkDoc}
-- 
cgit v1.2.3-70-g09d2

