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 ----
 1 file changed, 4 deletions(-)

(limited to 'src/client/util/InteractionUtils.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":
-- 
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/util/InteractionUtils.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/util/InteractionUtils.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 7e439440a1d7fc3e3b29333b0502d6ed4d178309 Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Wed, 18 Aug 2021 12:12:23 -0400
Subject: highlighter tool added & working

---
 src/client/util/InteractionUtils.tsx            |  6 ++++--
 src/client/views/GestureOverlay.tsx             |  6 ++++--
 src/client/views/InkHandles.tsx                 |  2 --
 src/client/views/InkingStroke.tsx               | 23 +++++++++++++++++------
 src/client/views/collections/CollectionMenu.tsx | 21 ++++++++++++++-------
 5 files changed, 39 insertions(+), 19 deletions(-)

(limited to 'src/client/util/InteractionUtils.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index e009fb3a9..15a6709a2 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -3,6 +3,8 @@ import * as beziercurve from 'bezier-curve';
 import * as fitCurve from 'fit-curve';
 import "./InteractionUtils.scss";
 import { Utils } from "../../Utils";
+import { CurrentUserUtils } from "./CurrentUserUtils";
+import { InkTool } from "../../fields/InkField";
 
 export namespace InteractionUtils {
     export const MOUSETYPE = "mouse";
@@ -139,7 +141,7 @@ export namespace InteractionUtils {
 
     export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number,
         color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
-        dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) {
+        dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string,  opacity: number, nodefs: boolean) {
         let pts: { X: number; Y: number; }[] = [];
         if (shape) { //if any of the shape are true
             pts = makePolygon(shape, points);
@@ -210,7 +212,7 @@ export namespace InteractionUtils {
                 style={{
                     // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
                     fill: fill ? fill : "none",
-                    opacity: 1.0,
+                    opacity: opacity,
                     // 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 bbf21f22c..c77521572 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -494,6 +494,7 @@ export class GestureOverlay extends Touchable {
         if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
             this._points.push({ X: e.clientX, Y: e.clientY });
             setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
+            // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)");
         }
     }
 
@@ -733,6 +734,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 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) });
@@ -842,12 +844,12 @@ export class GestureOverlay extends Touchable {
                 return <svg key={i} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
                     {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), width, width,
                         ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(),
-                        ActiveDash(), 1, 1, this.InkShape, "none", false, false)}
+                        ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)}
                 </svg>;
             }),
             this._points.length <= 1 ? (null) : <svg key="svg" width={B.width} height={B.height}
                 style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
-                {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false, false)}
+                {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)}
             </svg>]
         ];
     }
diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx
index 0b24c3c32..b9f75f8d7 100644
--- a/src/client/views/InkHandles.tsx
+++ b/src/client/views/InkHandles.tsx
@@ -16,7 +16,6 @@ import { GestureOverlay } from "./GestureOverlay";
 export interface InkHandlesProps {
     inkDoc: Doc;
     data: InkData;
-    shape?: string;
     format: number[];
     ScreenToLocalTransform: () => Transform;
 }
@@ -70,7 +69,6 @@ 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 5fc159f14..7200d6da1 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -29,11 +29,13 @@ const InkDocument = makeInterface(documentSchema);
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
     static readonly MaskDim = 50000;
     @observable private _properties?: InkStrokeProperties;
+    // @observable private _previousColor: string;
 
     constructor(props: FieldViewProps & InkDocument) {
         super(props);
 
         this._properties = InkStrokeProperties.Instance;
+        // this._previousColor = ActiveInkColor();
     }
 
     public static LayoutString(fieldStr: string) {
@@ -75,12 +77,22 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         }
     }
 
+    @action
+    checkHighlighter = () => {
+        if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) {
+            // this._previousColor = ActiveInkColor();
+            SetActiveInkColor("rgba(245, 230, 95, 0.75)");
+        }
+        // } else {
+        //     SetActiveInkColor(this._previousColor);
+        // }
+    }
+
     render() {
         TraceMobx();
         this.toggleControlButton();
+        // this.checkHighlighter();
         // 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,13 +113,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
 
         // 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);
+            StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 1.0, false);
         // Thin blue line indicating that the current ink stroke is selected.
         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);
+            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 1.0, false);
         // Invisible polygonal line that enables the ink to be selected by the user.
         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);
+            StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", 0.0, 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);
@@ -145,7 +157,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                         <InkHandles
                             inkDoc={inkDoc}
                             data={data}
-                            shape={InkShape}
                             format={[left, top, scaleX, scaleY, strokeWidth]}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                     </> : ""}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 8f4df4a92..835b3e575 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -614,12 +614,12 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
     private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
     private _width = ["1", "5", "10", "100"];
     private _dotsize = [10, 20, 30, 40];
-    private _draw = ["∿", "⎯", "→", "↔︎", "ロ", "O"];
-    private _head = ["", "", "", "arrow", "", ""];
-    private _end = ["", "", "arrow", "arrow", "", ""];
-    private _shapePrims = ["", "line", "line", "line", "rectangle", "circle"];
-    private _title = ["pen", "line", "line with arrow", "line with double arrows", "square", "circle",];
-    private _faName = ["pen-fancy", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"];
+    private _draw = ["∿", "=", "⎯", "→", "↔︎", "ロ", "O"];
+    private _head = ["", "", "", "", "arrow", "", ""];
+    private _end = ["", "", "", "arrow", "arrow", "", ""];
+    private _shapePrims = ["", "", "line", "line", "line", "rectangle", "circle"];
+    private _title = ["pen", "highlighter", "line", "line with arrow", "line with double arrows", "square", "circle"];
+    private _faName = ["pen-fancy", "highlighter", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"];
     @observable _selectedPrimitive = this._shapePrims.length;
     @observable _keepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode
     @observable _colorBtn = false;
@@ -629,6 +629,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
     @action clearKeepPrimitiveMode() { this._selectedPrimitive = this._shapePrims.length; }
     @action primCreated() {
         if (!this._keepPrimitiveMode) { //get out of ink mode after each stroke=
+            if (CurrentUserUtils.SelectedTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
             CurrentUserUtils.SelectedTool = InkTool.None;
             this._selectedPrimitive = this._shapePrims.length;
             SetActiveArrowStart("none");
@@ -669,7 +670,13 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
             this._keepPrimitiveMode = keep;
             if (this._selectedPrimitive !== i) {
                 this._selectedPrimitive = i;
-                CurrentUserUtils.SelectedTool = InkTool.Pen;
+                if (this._title[i] === "highlighter") {
+                    CurrentUserUtils.SelectedTool = InkTool.Highlighter;
+                    GestureOverlay.Instance.SavedColor = ActiveInkColor();
+                    SetActiveInkColor("rgba(245, 230, 95, 0.75)");
+                } else {
+                    CurrentUserUtils.SelectedTool = InkTool.Pen;
+                }
                 SetActiveArrowStart(this._head[i]);
                 SetActiveArrowEnd(this._end[i]);
                 SetActiveBezierApprox("300");
-- 
cgit v1.2.3-70-g09d2


From b04ec7f52265a27e9d72a3738fa474cfaefcdcd9 Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Sat, 21 Aug 2021 16:55:55 -0400
Subject: added unrounded endpoints for highlight ink strokes

---
 src/client/util/InteractionUtils.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/client/util/InteractionUtils.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 15a6709a2..a11c090a7 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -217,8 +217,8 @@ export namespace InteractionUtils {
                     pointerEvents: pevents as any,
                     stroke: color ?? "rgb(0, 0, 0)",
                     strokeWidth: strokeWidth,
-                    strokeLinejoin: "round",
-                    strokeLinecap: "round",
+                    strokeLinejoin: color === "rgba(245, 230, 95, 0.75)" ? "miter" : "round",
+                    strokeLinecap: color === "rgba(245, 230, 95, 0.75)" ? "square" : "round",
                     strokeDasharray: dashArray
                 }}
                 markerStart={`url(#${arrowStart + "Start" + defGuid})`}
-- 
cgit v1.2.3-70-g09d2


From c9c9d00b581bc7f80edbf7848a8ffb3a8e64f005 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 10 Sep 2021 10:14:04 -0400
Subject: event handling fixes for ink.  double click on deselected ink takes
 shows handles.  lightbox view works.  transparent fills are not selectable.

---
 src/client/util/InteractionUtils.tsx               |  2 +-
 src/client/views/InkingStroke.tsx                  | 48 ++++++++++++++++------
 src/client/views/nodes/DocumentContentsView.tsx    |  1 -
 src/client/views/nodes/DocumentView.tsx            |  8 ++--
 .../views/nodes/formattedText/FormattedTextBox.tsx |  3 +-
 5 files changed, 41 insertions(+), 21 deletions(-)

(limited to 'src/client/util/InteractionUtils.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index e009fb3a9..8429a806a 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -209,7 +209,7 @@ export namespace InteractionUtils {
                 points={strpts}
                 style={{
                     // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
-                    fill: fill ? fill : "none",
+                    fill: fill && fill !== "transparent" ? fill : "none",
                     opacity: 1.0,
                     // opacity: strokeWidth !== width ? 0.5 : undefined,
                     pointerEvents: pevents as any,
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index db09849fd..dc2a5b39b 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,26 +1,26 @@
 import React = require("react");
-import { action, observable } from "mobx";
+import { action, IReactionDisposer, observable, reaction } 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, NumCast } from "../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { TraceMobx } from "../../fields/util";
-import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
+import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
 import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
 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 { GestureOverlay } from "./GestureOverlay";
+import { Colors } from "./global/globalEnums";
 import { InkControls } from "./InkControls";
 import { InkHandles } from "./InkHandles";
-import { Colors } from "./global/globalEnums";
-import { GestureOverlay } from "./GestureOverlay";
+import "./InkStroke.scss";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { FieldView, FieldViewProps } from "./nodes/FieldView";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
@@ -29,6 +29,8 @@ const InkDocument = makeInterface(documentSchema);
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
     static readonly MaskDim = 50000;
     @observable private _properties?: InkStrokeProperties;
+    _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated
+    _selDisposer: IReactionDisposer | undefined;
 
     constructor(props: FieldViewProps & InkDocument) {
         super(props);
@@ -36,6 +38,14 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         this._properties = InkStrokeProperties.Instance;
     }
 
+    componentDidMount() {
+        this._selDisposer = reaction(() => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
+            selected => !selected && this.toggleControlButton());
+    }
+    componentWillUnmount() {
+        this._selDisposer?.();
+    }
+
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(InkingStroke, fieldStr);
     }
@@ -53,14 +63,26 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
     });
 
+    onClick = (e: React.MouseEvent) => {
+        if (this._handledClick) {
+            e.stopPropagation(); //stop the event so that docView won't open the lightbox
+        }
+    }
+
     /**
      * Handles the movement of the entire ink object when the user clicks and drags.
      */
     onPointerDown = (e: React.PointerEvent) => {
+        this._handledClick = false;
         if (this.props.isSelected(true)) {
             setupMoveUpEvents(this, e, returnFalse, emptyFunction,
-                action((e: PointerEvent, doubleTap: boolean | undefined) =>
-                    doubleTap && this._properties && (this._properties._controlButton = true))
+                action((e: PointerEvent, doubleTap: boolean | undefined) => {
+                    doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
+                    if (doubleTap && this._properties) {
+                        this._properties._controlButton = true;
+                        this._handledClick = true;  // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
+                    }
+                }), this._properties?._controlButton, this._properties?._controlButton
             );
         }
     }
@@ -77,7 +99,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
 
     render() {
         TraceMobx();
-        this.toggleControlButton();
         // Extracting the ink data and formatting information of the current ink stroke.
         // console.log(InkingStroke.InkShape);
         const InkShape = GestureOverlay.Instance.InkShape;
@@ -115,12 +136,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         return (
             <svg className="inkStroke"
                 style={{
-                    pointerEvents: this.props.Document.isInkMask && this.props.layerProvider?.(this.props.Document) !== false ? "all" : "none",
+                    pointerEvents: "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}
+                onClick={this.onClick}
                 onContextMenu={() => {
                     const cm = ContextMenu.Instance;
                     if (cm) {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index fad905d6d..dbab5e762 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -113,7 +113,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
     scaling?: () => number,
     setHeight: (height: number) => void,
     layoutKey: string,
-    hideOnLeave?: boolean,
 }> {
     @computed get layout(): string {
         TraceMobx();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8b19fb204..a2d2f17b6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -808,7 +808,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>;
         return <div className="documentView-contentsView"
             style={{
-                pointerEvents: (this.props.contentPointerEvents as any) || (this.isContentActive() ? "all" : "none"),
+                pointerEvents: this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none",
                 height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
             }}>
             <DocumentContentsView key={1} {...this.props}
@@ -943,13 +943,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const showTitleHover = this.ShowTitle?.includes(":hover");
         const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined;
         const captionView = !showCaption ? (null) :
-            <div className="documentView-captionWrapper">
+            <div className="documentView-captionWrapper"
+                style={{ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined, }}>
                 <FormattedTextBox {...OmitKeys(this.props, ['children']).omit}
                     yPadding={10}
                     xPadding={10}
                     fieldKey={showCaption}
                     fontSize={Math.min(32, 12 * this.props.ScreenToLocalTransform().Scale)}
-                    hideOnLeave={true}
                     styleProvider={this.captionStyleProvider}
                     dontRegisterView={true}
                     isContentActive={this.isContentActive}
@@ -964,7 +964,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 height: this.titleHeight,
                 color: lightOrDark(background),
                 background,
-                pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
+                pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
             }}>
                 <EditableView ref={this._titleRef}
                     contents={showTitle.split(";").map(field => field.trim()).map(field => targetDoc[field]?.toString()).join("\\")}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 78de1fd89..e7a44f113 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -69,7 +69,6 @@ const translateGoogleApi = require("translate-google-api");
 
 export interface FormattedTextBoxProps {
     makeLink?: () => Opt<Doc>;  // bcz: hack: notifies the text document when the container has made a link.  allows the text doc to react and setup a hyeprlink for any selected text
-    hideOnLeave?: boolean;  // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar)
     xPadding?: number;   // used to override document's settings for xMargin --- see CollectionCarouselView
     yPadding?: number;
     noSidebar?: boolean;
@@ -1558,7 +1557,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         TraceMobx();
         const selected = this.props.isSelected();
         const active = this.props.isContentActive();
-        const scale = this.props.hideOnLeave ? 1 : (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
+        const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
         const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
         const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false);
         if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
-- 
cgit v1.2.3-70-g09d2


From 1561e37eb966607564938530a71aeb7e3ba27583 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 27 Sep 2021 23:03:47 -0400
Subject: fixed ink stroke editing to work with closed curves (eg break /
 restore tangent & drag start/end point).   changed ink rendering to use svg
 bezier curves.  fixed bugs with keydown handler to break tangent.

---
 src/client/util/InteractionUtils.tsx    | 51 +++++++++------------------------
 src/client/views/GestureOverlay.tsx     |  4 +--
 src/client/views/InkControls.tsx        | 10 +++++--
 src/client/views/InkHandles.tsx         | 38 +++++++++++++-----------
 src/client/views/InkStrokeProperties.ts | 11 ++++---
 src/client/views/InkingStroke.tsx       |  4 +--
 6 files changed, 53 insertions(+), 65 deletions(-)

(limited to 'src/client/util/InteractionUtils.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 4a8011e3c..66afc849e 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -146,7 +146,7 @@ export namespace InteractionUtils {
         if (shape) { //if any of the shape are true
             pts = makePolygon(shape, points);
         }
-        else if ((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) {
+        else if (((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) && !bezier) {
             for (var i = 0; i < points.length - 3; i += 4) {
                 const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]];
                 for (var t = 0; t < 1; t += 0.01) {
@@ -154,47 +154,22 @@ export namespace InteractionUtils {
                     pts.push({ X: point[0], Y: point[1] });
                 }
             }
-        }
-        else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) {
-            //pointer is up (first and last points are the same)
-            const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
-            newPoints.pop();
-
-            const bezierCurves = fitCurve(newPoints, parseInt(bezier));
-            for (const curve of bezierCurves) {
-                for (var t = 0; t < 1; t += 0.01) {
-                    const point = beziercurve(t, curve);
-                    pts.push({ X: point[0], Y: point[1] });
-                }
-            }
         } else {
             pts = points.slice();
-            // bcz: Ugh... this is ugly, but shapes apprently have an extra point added that is = (p[0].x,p[0].y+1) as some sort of flag.  need to remove it here.
-            if (pts.length > 2 && pts[pts.length - 2].X === pts[0].X && pts[pts.length - 2].Y === pts[0].Y) {
-                pts.pop();
-            }
-        }
-        if (isNaN(scalex)) {
-            scalex = 1;
-        }
-        if (isNaN(scaley)) {
-            scaley = 1;
         }
-        const strpts = pts.reduce((acc: string, pt: { X: number, Y: number }) => acc +
-            `${(pt.X - left - width / 2) * scalex + width / 2},
-         ${(pt.Y - top - width / 2) * scaley + width / 2} `, "");
+        if (isNaN(scalex)) scalex = 1;
+        if (isNaN(scaley)) scaley = 1;
+
+        const toScr = (p: { X: number, Y: number }) => ` ${(p.X - left - width / 2) * scalex + width / 2}, ${(p.Y - top - width / 2) * scaley + width / 2} `;
+        var strpts = bezier ?
+            pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pts[i]) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") :
+            pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, "");
+
         const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined;
         const defGuid = Utils.GenerateGuid();
         const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth)));
 
-        const addables = pts.map((pts, i) =>
-            <svg height="10" width="10">
-                <circle cx={(pts.X - left - width / 2) * scalex + width / 2} cy={(pts.Y - top - width / 2) * scaley + width / 2} r={strokeWidth / 2} stroke="black" strokeWidth={1} fill="blue"
-                    onDoubleClick={(e) => { console.log(i); }} pointerEvents="all" cursor="all-scroll"
-                />
-            </svg>);
-
-
+        const Tag = (bezier ? "path" : "polyline") as keyof JSX.IntrinsicElements;
         return (<svg fill={color}> {/* setting the svg fill sets the arrowStart fill */}
             {nodefs ? (null) : <defs>
                 {arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : <marker id={`dot${defGuid}`} orient="auto" overflow="visible">
@@ -207,8 +182,10 @@ export namespace InteractionUtils {
                     <polygon points={`${2 - arrowDim} ${-Math.max(1, arrowDim / 2)}, ${2 - arrowDim} ${Math.max(1, arrowDim / 2)}, 3 0`} />
                 </marker>}
             </defs>}
-            <polyline
-                points={strpts}
+
+            <Tag
+                d={bezier ? strpts : undefined}
+                points={bezier ? undefined : strpts}
                 style={{
                     // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
                     fill: fill && fill !== "transparent" ? fill : "none",
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 6ccbd3fd7..465e6b2df 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -708,7 +708,7 @@ export class GestureOverlay extends Touchable {
                 this._points.push({ X: left, Y: top });
 
                 break;
-                
+
             case "triangle":
                 this._points.push({ X: left, Y: bottom });
                 this._points.push({ X: left, Y: bottom });
@@ -851,7 +851,7 @@ export class GestureOverlay extends Touchable {
             }),
             this._points.length <= 1 ? (null) : <svg key="svg" width={B.width} height={B.height}
                 style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
-                {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)}
+                {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, "", ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)}
             </svg>]
         ];
     }
diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx
index 4df7ee813..7e685288d 100644
--- a/src/client/views/InkControls.tsx
+++ b/src/client/views/InkControls.tsx
@@ -37,8 +37,8 @@ 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 handleIndexB = order === 2 ? controlIndex + 2 : controlIndex + 1;
+            const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length;
+            const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length;
             const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"));
             setupMoveUpEvents(this, e,
                 (e: PointerEvent, down: number[], delta: number[]) => {
@@ -47,7 +47,11 @@ export class InkControls extends React.Component<InkControlProps> {
                 },
                 () => controlUndo?.end(),
                 action((e: PointerEvent, doubleTap: boolean | undefined) => {
-                    if (doubleTap && brokenIndices && brokenIndices.includes(controlIndex)) {
+                    const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
+                    if (doubleTap && brokenIndices?.includes(equivIndex)) {
+                        InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB);
+                    }
+                    if (doubleTap && brokenIndices?.includes(controlIndex)) {
                         InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
                     }
                 }));
diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx
index afe94cdfb..1a514bdce 100644
--- a/src/client/views/InkHandles.tsx
+++ b/src/client/views/InkHandles.tsx
@@ -31,10 +31,11 @@ export class InkHandles extends React.Component<InkHandlesProps> {
             const controlUndo = UndoManager.StartBatch("DocDecs set radius");
             const screenScale = this.props.ScreenToLocalTransform().Scale;
             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);
+            const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
+            const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.data.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.data.length;
+            const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.data.length;
             setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
+                if (e.altKey) this.onBreakTangent(controlIndex);
                 InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
                 return false;
             }, () => controlUndo?.end(), emptyFunction
@@ -48,15 +49,14 @@ export class InkHandles extends React.Component<InkHandlesProps> {
      * @param handleNum The index of the currently selected handle point.
      */
     @action
-    onBreakTangent = (e: KeyboardEvent, controlIndex: number) => {
-        const doc: Doc = this.props.inkDoc;
-        if (["Alt"].includes(e.key)) {
-            e.stopPropagation();
-            if (doc) {
-                const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List;
-                if (brokenIndices && !brokenIndices.includes(controlIndex)) {
-                    brokenIndices.push(controlIndex);
-                }
+    onBreakTangent = (controlIndex: number) => {
+        const doc = this.props.inkDoc;
+        if (doc) {
+            const closed = this.props.data.lastElement().X === this.props.data[0].X && this.props.data.lastElement().Y === this.props.data[0].Y;
+            const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List;
+            if (!brokenIndices?.includes(controlIndex) &&
+                ((controlIndex > 0 && controlIndex < this.props.data.length - 1) || closed)) {
+                brokenIndices.push(controlIndex);
                 doc.brokenInkIndices = brokenIndices;
             }
         }
@@ -70,14 +70,20 @@ export class InkHandles extends React.Component<InkHandlesProps> {
         const data = this.props.data;
         const handlePoints: HandlePoint[] = [];
         const handleLines: HandleLine[] = [];
+        const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y;
         if (data.length >= 4) {
             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 });
+                handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : 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 ? (closed ? (i + 4) % data.length : i + 3) : i + 4 });
             }
             // Adding first and last (single) handle lines.
-            handleLines.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 });
-            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, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
+            if (closed) {
+                handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 });
+            }
+            else {
+                handleLines.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 });
+                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, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
+            }
             for (let i = 2; i < data.length - 4; i += 4) {
                 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 });
             }
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 42190238e..2073497b9 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,14 +1,14 @@
 import { action, computed, observable, reaction } from "mobx";
-import { Doc, DocListCast, Field, Opt } from "../../fields/Doc";
+import { Doc, Field, Opt } from "../../fields/Doc";
 import { Document } from "../../fields/documentSchemas";
-import { InkField, InkData, PointData, ControlPoint, InkTool } from "../../fields/InkField";
+import { InkData, InkField, InkTool, PointData } 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 { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch } from "../util/UndoManager";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
 
 export class InkStrokeProperties {
     static Instance: InkStrokeProperties | undefined;
@@ -241,6 +241,7 @@ export class InkStrokeProperties {
         this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
             const newPoints: { X: number, Y: number }[] = [];
             const order = controlIndex % 4;
+            const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y;
             for (var i = 0; i < ink.length; i++) {
                 const leftHandlePoint = order === 0 && i === controlIndex + 1;
                 const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2;
@@ -248,6 +249,7 @@ export class InkStrokeProperties {
                     leftHandlePoint ||
                     rightHandlePoint ||
                     (order === 0 && controlIndex !== 0 && i === controlIndex - 1) ||
+                    ((order === 0 || order === 3) && (controlIndex === 0 || controlIndex === ink.length - 1) && (i === 1 || i === ink.length - 2) && closed) ||
                     (order === 3 && i === controlIndex - 1) ||
                     (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) ||
                     (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) ||
@@ -335,6 +337,7 @@ export class InkStrokeProperties {
     @action
     moveHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) =>
         this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
+            const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y;
             const oldHandlePoint = ink[handleIndex];
             let oppositeHandlePoint = ink[oppositeHandleIndex];
             const controlPoint = ink[controlIndex];
@@ -342,7 +345,7 @@ export class InkStrokeProperties {
             ink[handleIndex] = newHandlePoint;
             const 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 ((!brokenIndices || !brokenIndices?.includes(controlIndex)) && handleIndex !== 1 && handleIndex !== ink.length - 2) {
+            if ((!brokenIndices || !brokenIndices?.includes(controlIndex)) && (closed || (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 ca39bdaa1..a518bf07b 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -11,14 +11,11 @@ import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
 import { CognitiveServices } from "../cognitive_services/CognitiveServices";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { InteractionUtils } from "../util/InteractionUtils";
-import { Scripting } from "../util/Scripting";
 import { ContextMenu } from "./ContextMenu";
 import { ViewBoxBaseComponent } from "./DocComponent";
 import { Colors } from "./global/globalEnums";
 import { InkControls } from "./InkControls";
 import { InkHandles } from "./InkHandles";
-import { GestureOverlay } from "./GestureOverlay";
-import { isThisTypeNode } from "typescript";
 import "./InkStroke.scss";
 import { InkStrokeProperties } from "./InkStrokeProperties";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
@@ -83,6 +80,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
                     if (doubleTap && this._properties) {
                         this._properties._controlButton = true;
+                        InkStrokeProperties.Instance && (InkStrokeProperties.Instance._currentPoint = -1);
                         this._handledClick = true;  // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
                     }
                 }), this._properties?._controlButton, this._properties?._controlButton
-- 
cgit v1.2.3-70-g09d2


From 85c3a9ec3590ebfbe85209a64c7e41cae9663923 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 28 Sep 2021 13:59:28 -0400
Subject: converted ink addpoints to not use sampling.  needs addPoints() to be
 filled in.

---
 package-lock.json                       |  10 +++
 package.json                            |   2 +
 src/client/util/InteractionUtils.tsx    |  65 +-----------------
 src/client/views/InkControls.tsx        |  46 ++++++-------
 src/client/views/InkStrokeProperties.ts | 116 +++++++++++++++++---------------
 src/client/views/InkingStroke.tsx       |  50 +++++++++++---
 6 files changed, 138 insertions(+), 151 deletions(-)

(limited to 'src/client/util/InteractionUtils.tsx')

diff --git a/package-lock.json b/package-lock.json
index 7810e3120..c1dd8506f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -616,6 +616,11 @@
             "integrity": "sha1-TN2WtJKTs5MhIuS34pVD415rrlg=",
             "dev": true
         },
+        "@types/bezier-js": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/@types/bezier-js/-/bezier-js-4.1.0.tgz",
+            "integrity": "sha512-ElU16s8E6Pr6magp8ihwH1O8pbUJASbMND/qgUc9RsLmP3lMLHiDMRXdjtaObwW5GPtOVYOsXDUIhTIluT+yaw=="
+        },
         "@types/bluebird": {
             "version": "3.5.32",
             "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.32.tgz",
@@ -2628,6 +2633,11 @@
             "resolved": "https://registry.npmjs.org/bezier-curve/-/bezier-curve-1.0.0.tgz",
             "integrity": "sha1-o9+v6rEqlMRicw1QeYxSqEBdc3k="
         },
+        "bezier-js": {
+            "version": "4.1.1",
+            "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-4.1.1.tgz",
+            "integrity": "sha512-oVOS6SSFFFlfnZdzC+lsfvhs/RRcbxJ47U04M4s5QIBaJmr3YWmTIL3qmrOK9uW+nUUcl9Jccmo/xpTrG+bBoQ=="
+        },
         "big.js": {
             "version": "5.2.2",
             "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
diff --git a/package.json b/package.json
index 6cbde9e1f..5d10c0d54 100644
--- a/package.json
+++ b/package.json
@@ -126,6 +126,7 @@
         "@hig/theme-data": "^2.16.1",
         "@material-ui/core": "^4.11.0",
         "@react-three/fiber": "^6.0.16",
+        "@types/bezier-js": "^4.1.0",
         "@types/cors": "^2.8.8",
         "@types/d3-axis": "^2.0.0",
         "@types/d3-color": "^2.0.1",
@@ -145,6 +146,7 @@
         "babel-runtime": "^6.26.0",
         "bcrypt-nodejs": "0.0.3",
         "bezier-curve": "^1.0.0",
+        "bezier-js": "^4.1.1",
         "bluebird": "^3.7.2",
         "body-parser": "^1.18.3",
         "bootstrap": "^4.5.0",
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 66afc849e..f748188d7 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -1,10 +1,8 @@
 import React = require("react");
 import * as beziercurve from 'bezier-curve';
 import * as fitCurve from 'fit-curve';
-import "./InteractionUtils.scss";
 import { Utils } from "../../Utils";
-import { CurrentUserUtils } from "./CurrentUserUtils";
-import { InkTool } from "../../fields/InkField";
+import "./InteractionUtils.scss";
 
 export namespace InteractionUtils {
     export const MOUSETYPE = "mouse";
@@ -93,70 +91,13 @@ export namespace InteractionUtils {
         return myTouches;
     }
 
-    export function CreatePoints(points: { X: number, Y: number }[], left: number, top: number,
-        color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
-        dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) {
-        let pts: { X: number; Y: number; }[] = [];
-        if (shape) { //if any of the shape are true
-            pts = makePolygon(shape, points);
-        }
-        else if ((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) {
-            for (var i = 0; i < points.length - 3; i += 4) {
-                const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]];
-                for (var t = 0; t < 1; t += 0.01) {
-                    const point = beziercurve(t, array);
-                    pts.push({ X: point[0], Y: point[1] });
-                }
-            }
-        }
-        else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) {
-            //pointer is up (first and last points are the same)
-            const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
-            newPoints.pop();
-
-            const bezierCurves = fitCurve(newPoints, parseInt(bezier));
-            for (const curve of bezierCurves) {
-                for (var t = 0; t < 1; t += 0.01) {
-                    const point = beziercurve(t, curve);
-                    pts.push({ X: point[0], Y: point[1] });
-                }
-            }
-        } else {
-            pts = points.slice();
-            // bcz: Ugh... this is ugly, but shapes apprently have an extra point added that is = (p[0].x,p[0].y+1) as some sort of flag.  need to remove it here.
-            if (pts.length > 2 && pts[pts.length - 2].X === pts[0].X && pts[pts.length - 2].Y === pts[0].Y) {
-                pts.pop();
-            }
-        }
-        if (isNaN(scalex)) {
-            scalex = 1;
-        }
-        if (isNaN(scaley)) {
-            scaley = 1;
-        }
-        return pts;
-    }
-
-
-
     export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number,
         color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
         dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, opacity: number, nodefs: boolean) {
         let pts: { X: number; Y: number; }[] = [];
         if (shape) { //if any of the shape are true
-            pts = makePolygon(shape, points);
-        }
-        else if (((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) && !bezier) {
-            for (var i = 0; i < points.length - 3; i += 4) {
-                const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]];
-                for (var t = 0; t < 1; t += 0.01) {
-                    const point = beziercurve(t, array);
-                    pts.push({ X: point[0], Y: point[1] });
-                }
-            }
-        } else {
-            pts = points.slice();
-        }
+        const pts = shape ? makePolygon(shape, points) : points;
+
         if (isNaN(scalex)) scalex = 1;
         if (isNaN(scaley)) scaley = 1;
 
diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx
index 7e685288d..5fe0c0f8a 100644
--- a/src/client/views/InkControls.tsx
+++ b/src/client/views/InkControls.tsx
@@ -5,7 +5,7 @@ import { Doc } from "../../fields/Doc";
 import { ControlPoint, InkData, PointData } from "../../fields/InkField";
 import { listSpec } from "../../fields/Schema";
 import { Cast } from "../../fields/Types";
-import { setupMoveUpEvents } from "../../Utils";
+import { setupMoveUpEvents, Utils } from "../../Utils";
 import { Transform } from "../util/Transform";
 import { UndoManager } from "../util/UndoManager";
 import { Colors } from "./global/globalEnums";
@@ -15,17 +15,16 @@ export interface InkControlProps {
     inkDoc: Doc;
     inkCtrlPoints: InkData;
     screenCtrlPoints: InkData;
-    inkStrokeSamplePts: PointData[];
-    screenStrokeSamplePoints: PointData[];
     format: number[];
     ScreenToLocalTransform: () => Transform;
+    nearestScreenPt: () => PointData | undefined;
 }
 
 @observer
 export class InkControls extends React.Component<InkControlProps> {
+
     @observable private _overControl = -1;
     @observable private _overAddPoint = -1;
-
     /**
      * Handles the movement of a selected control point when the user clicks and drags.
      * @param controlIndex The index of the currently selected control point.
@@ -57,6 +56,14 @@ 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; };
 
     /**
      * Deletes the currently selected point.
@@ -79,19 +86,11 @@ 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 scrData = this.props.screenCtrlPoints;
         const sreenCtrlPoints: ControlPoint[] = [];
@@ -109,24 +108,23 @@ export class InkControls extends React.Component<InkControlProps> {
 
         const [left, top, scaleX, scaleY, strokeWidth, screenSpaceLineWidth] = this.props.format;
         const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4;
+
+        const nearestScreenPt = this.props.nearestScreenPt();
         return (<svg>
             {/*   should really have just one circle here that represents the neqraest point on the stroke to the users hover point.
                    This points should be passed as a prop from InkingStroke's UI which should set it in its onPointerOver method  */}
-            {this.props.screenStrokeSamplePoints.map((pts, i) =>
-                <circle key={i}
-                    cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
-                    cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
+            {!nearestScreenPt ? (null) :
+                <circle key={"npt"}
+                    cx={(nearestScreenPt.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2}
+                    cy={(nearestScreenPt.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
                     r={screenSpaceLineWidth * 4}
-                    fill={this._overAddPoint === i ? "#00007777" : "transparent"}
-                    stroke={this._overAddPoint === i ? "#00007777" : "transparent"}
+                    fill={"#00007777"}
+                    stroke={"#00007777"}
                     strokeWidth={0}
-                    onPointerDown={() => formatInstance?.addPoints(this.props.inkStrokeSamplePts[i].X, this.props.inkStrokeSamplePts[i].Y, this.props.inkStrokeSamplePts, i, inkCtrlPts)}
-                    onMouseEnter={() => this.onEnterAddPoint(i)}
-                    onMouseLeave={this.onLeaveAddPoint}
-                    pointerEvents="all"
+                    pointerEvents="none"
                     cursor="all-scroll"
                 />
-            )}
+            }
             {sreenCtrlPoints.map((control, i) =>
                 <rect key={i}
                     x={(control.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2 - rectHdlSize(i) / 2}
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 2073497b9..03946bb60 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,3 +1,4 @@
+import { Bezier } from "bezier-js";
 import { action, computed, observable, reaction } from "mobx";
 import { Doc, Field, Opt } from "../../fields/Doc";
 import { Document } from "../../fields/documentSchemas";
@@ -70,67 +71,70 @@ export class InkStrokeProperties {
 
     /**
      * Adds a new control point to the ink instance when editing its format.
-     * @param index The index of the new point.
+     * @param t T-Value of new control point
+     * @param i index of first control point of segment being split
      * @param control The list of all control points of the ink.
      */
     @undoBatch
     @action
-    addPoints = (x: number, y: number, points: InkData, index: number, controls: { X: number, Y: number }[]) => {
-        this.applyFunction((doc: Doc, ink: InkData) => {
-            const newControl = { X: x, Y: y };
-            const newPoints: InkData = [];
-            let [counter, start, end] = [0, 0, 0];
-            for (let k = 0; k < points.length; k++) {
-                if (end === 0) {
-                    controls.forEach((control) => {
-                        if (control.X === points[k].X && control.Y === points[k].Y) {
-                            if (k < index) {
-                                counter++;
-                                start = k;
-                            } else if (k > index) {
-                                end = k;
-                            }
-                        }
-                    });
-                }
-            }
-            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);
-                    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 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) {
-                    // Adjusting the magnitude of the right handle line of the left neighboring control point.
-                    const [leftControl, leftHandle] = [points[start], ink[i]];
-                    const scaledVector = this.getScaledHandlePoint(true, start, end, index, leftControl, leftHandle);
-                    leftHandle && newPoints.push({ X: leftControl.X - scaledVector.X, Y: leftControl.Y - scaledVector.Y });
-                } else {
-                    ink[i] && newPoints.push({ X: ink[i].X, Y: ink[i].Y });
-                }
+    addPoints = (t: number, i: number, controls: { X: number, Y: number }[]) => {
+        const array = [{ x: controls[i].X, y: controls[i].Y }, { x: controls[i + 1].X, y: controls[i + 1].Y }, { x: controls[i + 2].X, y: controls[i + 2].Y }, { x: controls[i + 3].X, y: controls[i + 3].Y }];
+        const newsegs = new Bezier(array).split(t);
+        // this.applyFunction((doc: Doc, ink: InkData) => {
+        //     const newControl = { X: x, Y: y };
+        //     const newPoints: InkData = [];
+        //     let [counter, start, end] = [0, 0, 0];
+        //     for (let k = 0; k < points.length; k++) {
+        //         if (end === 0) {
+        //             controls.forEach((control) => {
+        //                 if (control.X === points[k].X && control.Y === points[k].Y) {
+        //                     if (k < index) {
+        //                         counter++;
+        //                         start = k;
+        //                     } else if (k > index) {
+        //                         end = k;
+        //                     }
+        //                 }
+        //             });
+        //         }
+        //     }
+        //     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);
+        //             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 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) {
+        //             // Adjusting the magnitude of the right handle line of the left neighboring control point.
+        //             const [leftControl, leftHandle] = [points[start], ink[i]];
+        //             const scaledVector = this.getScaledHandlePoint(true, start, end, index, leftControl, leftHandle);
+        //             leftHandle && newPoints.push({ X: leftControl.X - scaledVector.X, Y: leftControl.Y - scaledVector.Y });
+        //         } 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) => {
-                    if (control >= newIndex) {
-                        return control + 4;
-                    } else {
-                        return control;
-                    }
-                }));
-            }
-            doc.brokenInkIndices = brokenIndices;
-            this._currentPoint = -1;
-            return newPoints;
-        });
+        //     }
+        //     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) => {
+        //             if (control >= newIndex) {
+        //                 return control + 4;
+        //             } else {
+        //                 return control;
+        //             }
+        //         }));
+        //     }
+        //     doc.brokenInkIndices = brokenIndices;
+        //     this._currentPoint = -1;
+        //     return newPoints;
+        // });
     }
 
     /**
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index a518bf07b..efe2e5f66 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -16,6 +16,7 @@ import { ViewBoxBaseComponent } from "./DocComponent";
 import { Colors } from "./global/globalEnums";
 import { InkControls } from "./InkControls";
 import { InkHandles } from "./InkHandles";
+import { Bezier } from "bezier-js";
 import "./InkStroke.scss";
 import { InkStrokeProperties } from "./InkStrokeProperties";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
@@ -82,6 +83,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                         this._properties._controlButton = true;
                         InkStrokeProperties.Instance && (InkStrokeProperties.Instance._currentPoint = -1);
                         this._handledClick = true;  // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
+                    } else {
+                        this._nearestT && this._nearestSeg && InkStrokeProperties.Instance?.addPoints(this._nearestT, this._nearestSeg, this.inkScaledData().inkData);
                     }
                 }), this._properties?._controlButton, this._properties?._controlButton
             );
@@ -129,6 +132,39 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             inkScaleY: inkWidth === inkStrokeWidth ? 1 : (this.props.PanelHeight() - inkStrokeWidth) / (inkHeight - inkStrokeWidth)
         };
     }
+    @observable _nearestT: number | undefined;
+    @observable _nearestSeg: number | undefined;
+    @observable _nearestScrPt: { X: number, Y: number } | undefined;
+    @observable _inkSamplePts: { X: number, Y: number }[] | undefined;
+    nearestScreenPt = () => this._nearestScrPt;
+
+    @action
+    onPointerOver = (e: React.PointerEvent) => {
+        const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
+        const screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+        const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth)[0];
+        const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(point.X, point.Y)).map(p => ({ X: p[0], Y: p[1] }));
+        const screenTop = Math.min(...screenPts.map(p => p.Y)) - screenInkWidth / 2;
+        const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth / 2;
+        var nearest = Number.MAX_SAFE_INTEGER;
+        this._nearestT = -1;
+        this._nearestSeg = -1;
+        this._nearestScrPt = { X: 0, Y: 0 };
+        for (var i = 0; i < screenPts.length - 3; i += 4) {
+            const array = [{ x: screenPts[i].X, y: screenPts[i].Y }, { x: screenPts[i + 1].X, y: screenPts[i + 1].Y }, { x: screenPts[i + 2].X, y: screenPts[i + 2].Y }, { x: screenPts[i + 3].X, y: screenPts[i + 3].Y }];
+            const point = new Bezier(array).project({ x: e.clientX + screenLeft - screenOrigin[0], y: e.clientY + screenTop - screenOrigin[1] });
+            if (point.t) {
+                const dist = (point.x - screenLeft - e.clientX + screenOrigin[0]) * (point.x - screenLeft - e.clientX + screenOrigin[0]) +
+                    (point.y - screenTop - e.clientY + screenOrigin[1]) * (point.y - screenTop - e.clientY + screenOrigin[1]);
+                if (dist < nearest) {
+                    nearest = dist;
+                    this._nearestT = point.t;
+                    this._nearestSeg = i;
+                    this._nearestScrPt = { X: point.x, Y: point.y };
+                }
+            }
+        }
+    }
 
     componentUI = (boundsLeft: number, boundsTop: number) => {
         const inkDoc = this.props.Document;
@@ -141,13 +177,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth[0] / 2;
         const screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
 
-        const screenSpaceSamplePoints = InteractionUtils.CreatePoints(screenPts, screenLeft, screenTop, StrCast(inkDoc.strokeColor, "none"), screenInkWidth[0], screenSpaceCenterlineStrokeWidth,
-            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
-            StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", this.props.isSelected() && inkStrokeWidth <= 5, false);
-        const inkSpaceSamplePoints = InteractionUtils.CreatePoints(inkData, inkLeft, inkTop, StrCast(inkDoc.strokeColor, "none"), inkStrokeWidth, screenSpaceCenterlineStrokeWidth,
-            StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
-            StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), 1, 1, "", "none", this.props.isSelected() && inkStrokeWidth <= 5, false);
-
         return <div className="inkstroke-UI" style={{
             left: screenOrigin[0],
             top: screenOrigin[1],
@@ -163,8 +192,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                         inkDoc={inkDoc}
                         inkCtrlPoints={inkData}
                         screenCtrlPoints={screenPts}
-                        inkStrokeSamplePts={inkSpaceSamplePoints}
-                        screenStrokeSamplePoints={screenSpaceSamplePoints}
+                        nearestScreenPt={this.nearestScreenPt}
                         format={[screenLeft, screenTop, inkScaleX, inkScaleY, screenInkWidth[0], screenSpaceCenterlineStrokeWidth]}
                         ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                     <InkHandles
@@ -205,6 +233,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
                     overflow: "visible",
                 }}
+                onPointerLeave={action(e =>
+                    this._nearestScrPt = undefined
+                )}
+                onPointerMove={this.onPointerOver}
                 onPointerDown={this.onPointerDown}
                 onClick={this.onClick}
                 onContextMenu={() => {
-- 
cgit v1.2.3-70-g09d2


From 2a6385ad330ac4d7caf6b23e64cb13b7546ea44a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 28 Sep 2021 13:59:37 -0400
Subject: from last

---
 src/client/util/InteractionUtils.tsx | 2 --
 1 file changed, 2 deletions(-)

(limited to 'src/client/util/InteractionUtils.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index f748188d7..9f3159d83 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -94,8 +94,6 @@ export namespace InteractionUtils {
     export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number,
         color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
         dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, opacity: number, nodefs: boolean) {
-        let pts: { X: number; Y: number; }[] = [];
-        if (shape) { //if any of the shape are true
         const pts = shape ? makePolygon(shape, points) : points;
 
         if (isNaN(scalex)) scalex = 1;
-- 
cgit v1.2.3-70-g09d2


From bc654229325e8bbd30c0b3e464c7e66fa0fbc609 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 28 Sep 2021 18:03:07 -0400
Subject: a bunch of code cleanup for inkingstrokes

---
 src/client/util/InteractionUtils.tsx    |   2 -
 src/client/views/InkControls.tsx        |  24 +++----
 src/client/views/InkHandles.tsx         |  44 ++++++-------
 src/client/views/InkStrokeProperties.ts |   1 -
 src/client/views/InkingStroke.tsx       | 109 ++++++++++----------------------
 5 files changed, 68 insertions(+), 112 deletions(-)

(limited to 'src/client/util/InteractionUtils.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 9f3159d83..3e7a4924f 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -1,6 +1,4 @@
 import React = require("react");
-import * as beziercurve from 'bezier-curve';
-import * as fitCurve from 'fit-curve';
 import { Utils } from "../../Utils";
 import "./InteractionUtils.scss";
 
diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx
index ee09273e3..eb0eebcdf 100644
--- a/src/client/views/InkControls.tsx
+++ b/src/client/views/InkControls.tsx
@@ -5,7 +5,7 @@ import { Doc } from "../../fields/Doc";
 import { ControlPoint, InkData, PointData } from "../../fields/InkField";
 import { listSpec } from "../../fields/Schema";
 import { Cast } from "../../fields/Types";
-import { setupMoveUpEvents, Utils } from "../../Utils";
+import { setupMoveUpEvents } from "../../Utils";
 import { Transform } from "../util/Transform";
 import { UndoManager } from "../util/UndoManager";
 import { Colors } from "./global/globalEnums";
@@ -15,13 +15,13 @@ export interface InkControlProps {
     inkDoc: Doc;
     inkCtrlPoints: InkData;
     screenCtrlPoints: InkData;
-    format: number[];
+    screenSpaceLineWidth: number;
     ScreenToLocalTransform: () => Transform;
     nearestScreenPt: () => PointData | undefined;
 }
 
 @observer
-export class InkControls extends React.Component<InkControlProps> {
+export class InkControlPtHandles extends React.Component<InkControlProps> {
 
     @observable private _overControl = -1;
     @observable private _overAddPoint = -1;
@@ -94,18 +94,18 @@ export class InkControls extends React.Component<InkControlProps> {
         const scrData = this.props.screenCtrlPoints;
         const sreenCtrlPoints: ControlPoint[] = [];
         for (let i = 0; i <= scrData.length - 4; i += 4) {
-            sreenCtrlPoints.push({ X: scrData[i].X, Y: scrData[i].Y, I: i });
-            sreenCtrlPoints.push({ X: scrData[i + 3].X, Y: scrData[i + 3].Y, I: i + 3 });
+            sreenCtrlPoints.push({ ...scrData[i], I: i });
+            sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 });
         }
 
         const inkData = this.props.inkCtrlPoints;
         const inkCtrlPts: ControlPoint[] = [];
         for (let i = 0; i <= inkData.length - 4; i += 4) {
-            inkCtrlPts.push({ X: inkData[i].X, Y: inkData[i].Y, I: i });
-            inkCtrlPts.push({ X: inkData[i + 3].X, Y: inkData[i + 3].Y, I: i + 3 });
+            inkCtrlPts.push({ ...inkData[i], I: i });
+            inkCtrlPts.push({ ...inkData[i + 3], I: i + 3 });
         }
 
-        const [left, top, scaleX, scaleY, strokeWidth, screenSpaceLineWidth] = this.props.format;
+        const screenSpaceLineWidth = this.props.screenSpaceLineWidth;
         const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4;
 
         const nearestScreenPt = this.props.nearestScreenPt();
@@ -124,18 +124,18 @@ export class InkControls extends React.Component<InkControlProps> {
             }
             {sreenCtrlPoints.map((control, i) =>
                 <rect key={i}
-                    x={(control.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2 - rectHdlSize(i) / 2}
-                    y={(control.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2 - rectHdlSize(i) / 2}
+                    x={control.X - rectHdlSize(i) / 2}
+                    y={control.Y - rectHdlSize(i) / 2}
                     height={rectHdlSize(i)}
                     width={rectHdlSize(i)}
                     strokeWidth={screenSpaceLineWidth / 2}
                     stroke={Colors.MEDIUM_BLUE}
                     fill={formatInstance?._currentPoint === control.I ? Colors.MEDIUM_BLUE : Colors.WHITE}
-                    onPointerDown={(e) => {
+                    onPointerDown={e => {
                         this.changeCurrPoint(control.I);
                         this.onControlDown(e, control.I);
                     }}
-                    onMouseEnter={() => this.onEnterControl(i)}
+                    onMouseEnter={e => this.onEnterControl(i)}
                     onMouseLeave={this.onLeaveControl}
                     pointerEvents="all"
                     cursor="default"
diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx
index 1a514bdce..dbe9ca027 100644
--- a/src/client/views/InkHandles.tsx
+++ b/src/client/views/InkHandles.tsx
@@ -14,13 +14,13 @@ import { InkStrokeProperties } from "./InkStrokeProperties";
 
 export interface InkHandlesProps {
     inkDoc: Doc;
-    data: InkData;
-    format: number[];
+    screenCtrlPoints: InkData;
+    screenSpaceLineWidth: number;
     ScreenToLocalTransform: () => Transform;
 }
 
 @observer
-export class InkHandles extends React.Component<InkHandlesProps> {
+export class InkTangentHandles extends React.Component<InkHandlesProps> {
     /**
      * Handles the movement of a selected handle point when the user clicks and drags.
      * @param handleNum The index of the currently selected handle point.
@@ -32,8 +32,8 @@ export class InkHandles extends React.Component<InkHandlesProps> {
             const screenScale = this.props.ScreenToLocalTransform().Scale;
             const order = handleIndex % 4;
             const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
-            const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.data.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.data.length;
-            const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.data.length;
+            const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length;
+            const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length;
             setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
                 if (e.altKey) this.onBreakTangent(controlIndex);
                 InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
@@ -52,10 +52,10 @@ export class InkHandles extends React.Component<InkHandlesProps> {
     onBreakTangent = (controlIndex: number) => {
         const doc = this.props.inkDoc;
         if (doc) {
-            const closed = this.props.data.lastElement().X === this.props.data[0].X && this.props.data.lastElement().Y === this.props.data[0].Y;
+            const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y;
             const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List;
             if (!brokenIndices?.includes(controlIndex) &&
-                ((controlIndex > 0 && controlIndex < this.props.data.length - 1) || closed)) {
+                ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) {
                 brokenIndices.push(controlIndex);
                 doc.brokenInkIndices = brokenIndices;
             }
@@ -67,14 +67,14 @@ export class InkHandles extends React.Component<InkHandlesProps> {
         if (!formatInstance) return (null);
 
         // Accessing the current ink's data and extracting all handle points and handle lines.
-        const data = this.props.data;
+        const data = this.props.screenCtrlPoints;
         const handlePoints: HandlePoint[] = [];
         const handleLines: HandleLine[] = [];
         const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y;
         if (data.length >= 4) {
             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 ? (closed ? data.length - 1 : 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 ? (closed ? (i + 4) % data.length : i + 3) : i + 4 });
+                handlePoints.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 });
+                handlePoints.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 });
             }
             // Adding first and last (single) handle lines.
             if (closed) {
@@ -88,19 +88,19 @@ 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, screenSpaceLineWidth] = this.props.format;
+        const screenSpaceLineWidth = this.props.screenSpaceLineWidth;
 
         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}
+                            cx={pts.X}
+                            cy={pts.Y}
                             r={screenSpaceLineWidth * 2}
                             strokeWidth={0}
                             fill={Colors.MEDIUM_BLUE}
-                            onPointerDown={(e) => this.onHandleDown(e, pts.I)}
+                            onPointerDown={e => this.onHandleDown(e, pts.I)}
                             pointerEvents="all"
                             cursor="default"
                             display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
@@ -108,18 +108,18 @@ export class InkHandles extends React.Component<InkHandlesProps> {
                 {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}
+                            x1={pts.X1}
+                            y1={pts.Y1}
+                            x2={pts.X2}
+                            y2={pts.Y2}
                             stroke={Colors.MEDIUM_BLUE}
                             strokeWidth={screenSpaceLineWidth}
                             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}
+                            x1={pts.X2}
+                            y1={pts.Y2}
+                            x2={pts.X3}
+                            y2={pts.Y3}
                             stroke={Colors.MEDIUM_BLUE}
                             strokeWidth={screenSpaceLineWidth}
                             display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 0b7fe5cd1..cfe6ec523 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -10,7 +10,6 @@ import { DocumentType } from "../documents/DocumentTypes";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch } from "../util/UndoManager";
-import { last } from "lodash";
 
 export class InkStrokeProperties {
     static Instance: InkStrokeProperties | undefined;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 40bed0eca..c9e401ab4 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,4 +1,5 @@
 import React = require("react");
+import { Bezier } from "bezier-js";
 import { action, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../fields/Doc";
@@ -14,9 +15,8 @@ import { InteractionUtils } from "../util/InteractionUtils";
 import { ContextMenu } from "./ContextMenu";
 import { ViewBoxBaseComponent } from "./DocComponent";
 import { Colors } from "./global/globalEnums";
-import { InkControls } from "./InkControls";
-import { InkHandles } from "./InkHandles";
-import { Bezier } from "bezier-js";
+import { InkControlPtHandles } from "./InkControls";
+import { InkTangentHandles } from "./InkHandles";
 import "./InkStroke.scss";
 import { InkStrokeProperties } from "./InkStrokeProperties";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
@@ -26,16 +26,21 @@ const InkDocument = makeInterface(documentSchema);
 
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
+    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
     static readonly MaskDim = 50000;
     @observable private _properties?: InkStrokeProperties;
     _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated
     _selDisposer: IReactionDisposer | undefined;
 
+    @observable _nearestT: number | undefined;
+    @observable _nearestSeg: number | undefined;
+    @observable _nearestScrPt: { X: number, Y: number } | undefined;
+    @observable _inkSamplePts: { X: number, Y: number }[] | undefined;
+
     constructor(props: FieldViewProps & InkDocument) {
         super(props);
 
         this._properties = InkStrokeProperties.Instance;
-        // this._previousColor = ActiveInkColor();
     }
 
     componentDidMount() {
@@ -47,10 +52,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         this._selDisposer?.();
     }
 
-    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]);
@@ -63,13 +64,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         inkDoc.color = "#9b9b9bff";
         inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
     });
-
-    onClick = (e: React.MouseEvent) => {
-        if (this._handledClick) {
-            e.stopPropagation(); //stop the event so that docView won't open the lightbox
-        }
-    }
-
     /**
      * Handles the movement of the entire ink object when the user clicks and drags.
      */
@@ -107,13 +101,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             // this._previousColor = ActiveInkColor();
             SetActiveInkColor("rgba(245, 230, 95, 0.75)");
         }
-        // } else {
-        //     SetActiveInkColor(this._previousColor);
-        // }
     }
 
     inkScaledData = () => {
-        const inkData: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+        const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
         const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1);
         const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2;
         const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2;
@@ -132,20 +123,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             inkScaleY: inkWidth === inkStrokeWidth ? 1 : (this.props.PanelHeight() - inkStrokeWidth) / (inkHeight - inkStrokeWidth)
         };
     }
-    @observable _nearestT: number | undefined;
-    @observable _nearestSeg: number | undefined;
-    @observable _nearestScrPt: { X: number, Y: number } | undefined;
-    @observable _inkSamplePts: { X: number, Y: number }[] | undefined;
-    nearestScreenPt = () => this._nearestScrPt;
 
     @action
     onPointerOver = (e: React.PointerEvent) => {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
-        const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth)[0];
-        const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
+        const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
+            (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
             (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
-        const screenTop = Math.min(...screenPts.map(p => p.Y)) - screenInkWidth / 2;
-        const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth / 2;
         var nearest = Number.MAX_SAFE_INTEGER;
         this._nearestT = -1;
         this._nearestSeg = -1;
@@ -159,59 +143,54 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     nearest = dist;
                     this._nearestT = point.t;
                     this._nearestSeg = i;
-                    this._nearestScrPt = { X: point.x - screenLeft, Y: point.y - screenTop };
+                    this._nearestScrPt = { X: point.x, Y: point.y };
                 }
             }
         }
     }
 
+    nearestScreenPt = () => this._nearestScrPt;
     componentUI = (boundsLeft: number, boundsTop: number) => {
         const inkDoc = this.props.Document;
         const screenSpaceCenterlineStrokeWidth = 3;  // the width of the blue line widget that shows the centerline of the ink stroke
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
 
         const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
-        const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(point.X, point.Y)).map(p => ({ X: p[0], Y: p[1] }));
-        const screenTop = Math.min(...screenPts.map(p => p.Y)) - screenInkWidth[0] / 2;
-        const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth[0] / 2;
+        const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
+            (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
+            (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
         const screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+        const screenHdlPts = screenPts;
 
         return <div className="inkstroke-UI" style={{
-            left: screenOrigin[0],
-            top: screenOrigin[1],
             clip: `rect(${boundsTop - screenOrigin[1]}px, 10000px, 10000px, ${boundsLeft - screenOrigin[0]}px)`
         }} >
-            {InteractionUtils.CreatePolyline(screenPts, screenLeft, screenTop, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth,
+            {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth,
                 StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"),
                 StrCast(inkDoc.strokeStartMarker), StrCast(inkDoc.strokeEndMarker),
-                StrCast(inkDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, false)}
-            {this._properties?._controlButton ?
+                StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
+            {!this._properties?._controlButton ? (null) :
                 <>
-                    <InkControls
+                    <InkControlPtHandles
                         inkDoc={inkDoc}
                         inkCtrlPoints={inkData}
-                        screenCtrlPoints={screenPts}
+                        screenCtrlPoints={screenHdlPts}
                         nearestScreenPt={this.nearestScreenPt}
-                        format={[screenLeft, screenTop, inkScaleX, inkScaleY, screenInkWidth[0], screenSpaceCenterlineStrokeWidth]}
+                        screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
                         ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
-                    <InkHandles
+                    <InkTangentHandles
                         inkDoc={inkDoc}
-                        data={screenPts}
-                        format={[screenLeft, screenTop, inkScaleX, inkScaleY, screenInkWidth[0], screenSpaceCenterlineStrokeWidth]}
+                        screenCtrlPoints={screenHdlPts}
+                        screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
                         ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
-                </> : ""}
+                </>}
         </div>;
     }
 
     render() {
         TraceMobx();
-        // Extracting the ink data and formatting information of the current ink stroke.
-        const inkDoc: Doc = this.layoutDoc;
-
         const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();
-
-        const strokeColor = StrCast(this.layoutDoc.color, "");
-        const dotsize = Math.max(inkWidth * inkScaleX, inkHeight * inkScaleY) / 40;
+        const strokeColor = StrCast(this.layoutDoc.color);
 
         // Visually renders the polygonal line made by the user.
         const inkLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, strokeColor, inkStrokeWidth, inkStrokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
@@ -232,39 +211,19 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
                     overflow: "visible",
                 }}
-                onPointerLeave={action(e =>
-                    console.log("")  //this._nearestScrPt = undefined
-                )}
+                onPointerLeave={action(e => this._nearestScrPt = undefined)}
                 onPointerMove={this.props.isSelected() ? this.onPointerOver : undefined}
                 onPointerDown={this.onPointerDown}
-                onClick={this.onClick}
+                onClick={e => this._handledClick && e.stopPropagation()}
                 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" });
-                    }
+                    !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
-                            inkDoc={inkDoc}
-                            data={inkData}
-                            addedPoints={addedPoints}
-                            format={[inkLeft, inkTop, inkScaleX, inkScaleY, inkStrokeWidth]}
-                            ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
-                        <InkHandles
-                            inkDoc={inkDoc}
-                            data={inkData}
-                            format={[inkLeft, inkTop, inkScaleX, inkScaleY, inkStrokeWidth]}
-                            ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
-                    </> : ""} */}
             </svg>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 244e06ec9873888dcef3cd08322880d73848fe69 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 28 Sep 2021 18:10:26 -0400
Subject: renamed some ink files

---
 src/client/util/InteractionUtils.tsx     |   4 +-
 src/client/views/InkControlPtHandles.tsx | 147 +++++++++++++++++++++++++++++++
 src/client/views/InkControls.tsx         | 147 -------------------------------
 src/client/views/InkHandles.tsx          | 130 ---------------------------
 src/client/views/InkTangentHandles.tsx   | 130 +++++++++++++++++++++++++++
 src/client/views/InkingStroke.tsx        |   4 +-
 6 files changed, 281 insertions(+), 281 deletions(-)
 create mode 100644 src/client/views/InkControlPtHandles.tsx
 delete mode 100644 src/client/views/InkControls.tsx
 delete mode 100644 src/client/views/InkHandles.tsx
 create mode 100644 src/client/views/InkTangentHandles.tsx

(limited to 'src/client/util/InteractionUtils.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 3e7a4924f..71d68262f 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -98,8 +98,8 @@ export namespace InteractionUtils {
         if (isNaN(scaley)) scaley = 1;
 
         const toScr = (p: { X: number, Y: number }) => ` ${(p.X - left - width / 2) * scalex + width / 2}, ${(p.Y - top - width / 2) * scaley + width / 2} `;
-        var strpts = bezier ?
-            pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pts[i]) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") :
+        const strpts = bezier ?
+            pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pt) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") :
             pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, "");
 
         const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined;
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
new file mode 100644
index 000000000..eb0eebcdf
--- /dev/null
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -0,0 +1,147 @@
+import React = require("react");
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Doc } from "../../fields/Doc";
+import { ControlPoint, InkData, PointData } from "../../fields/InkField";
+import { listSpec } from "../../fields/Schema";
+import { Cast } from "../../fields/Types";
+import { setupMoveUpEvents } from "../../Utils";
+import { Transform } from "../util/Transform";
+import { UndoManager } from "../util/UndoManager";
+import { Colors } from "./global/globalEnums";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+
+export interface InkControlProps {
+    inkDoc: Doc;
+    inkCtrlPoints: InkData;
+    screenCtrlPoints: InkData;
+    screenSpaceLineWidth: number;
+    ScreenToLocalTransform: () => Transform;
+    nearestScreenPt: () => PointData | undefined;
+}
+
+@observer
+export class InkControlPtHandles extends React.Component<InkControlProps> {
+
+    @observable private _overControl = -1;
+    @observable private _overAddPoint = -1;
+    /**
+     * Handles the movement of a selected control point when the user clicks and drags.
+     * @param controlIndex The index of the currently selected control point.
+     */
+    @action
+    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;
+            const order = controlIndex % 4;
+            const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length;
+            const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length;
+            const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, 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(),
+                action((e: PointerEvent, doubleTap: boolean | undefined) => {
+                    const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
+                    if (doubleTap && brokenIndices?.includes(equivIndex)) {
+                        InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB);
+                    }
+                    if (doubleTap && brokenIndices?.includes(controlIndex)) {
+                        InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
+                    }
+                }));
+        }
+    }
+    /**
+     * 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; };
+
+    /**
+     * 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);
+        }
+    }
+
+    render() {
+        const formatInstance = InkStrokeProperties.Instance;
+        if (!formatInstance) return (null);
+
+        // Accessing the current ink's data and extracting all control points.
+        const scrData = this.props.screenCtrlPoints;
+        const sreenCtrlPoints: ControlPoint[] = [];
+        for (let i = 0; i <= scrData.length - 4; i += 4) {
+            sreenCtrlPoints.push({ ...scrData[i], I: i });
+            sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 });
+        }
+
+        const inkData = this.props.inkCtrlPoints;
+        const inkCtrlPts: ControlPoint[] = [];
+        for (let i = 0; i <= inkData.length - 4; i += 4) {
+            inkCtrlPts.push({ ...inkData[i], I: i });
+            inkCtrlPts.push({ ...inkData[i + 3], I: i + 3 });
+        }
+
+        const screenSpaceLineWidth = this.props.screenSpaceLineWidth;
+        const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4;
+
+        const nearestScreenPt = this.props.nearestScreenPt();
+        return (<svg>
+            {!nearestScreenPt ? (null) :
+                <circle key={"npt"}
+                    cx={nearestScreenPt.X}
+                    cy={nearestScreenPt.Y}
+                    r={screenSpaceLineWidth * 4}
+                    fill={"#00007777"}
+                    stroke={"#00007777"}
+                    strokeWidth={0}
+                    pointerEvents="none"
+                    cursor="all-scroll"
+                />
+            }
+            {sreenCtrlPoints.map((control, i) =>
+                <rect key={i}
+                    x={control.X - rectHdlSize(i) / 2}
+                    y={control.Y - rectHdlSize(i) / 2}
+                    height={rectHdlSize(i)}
+                    width={rectHdlSize(i)}
+                    strokeWidth={screenSpaceLineWidth / 2}
+                    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={e => this.onEnterControl(i)}
+                    onMouseLeave={this.onLeaveControl}
+                    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
deleted file mode 100644
index eb0eebcdf..000000000
--- a/src/client/views/InkControls.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import React = require("react");
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../fields/Doc";
-import { ControlPoint, InkData, PointData } from "../../fields/InkField";
-import { listSpec } from "../../fields/Schema";
-import { Cast } from "../../fields/Types";
-import { setupMoveUpEvents } from "../../Utils";
-import { Transform } from "../util/Transform";
-import { UndoManager } from "../util/UndoManager";
-import { Colors } from "./global/globalEnums";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-
-export interface InkControlProps {
-    inkDoc: Doc;
-    inkCtrlPoints: InkData;
-    screenCtrlPoints: InkData;
-    screenSpaceLineWidth: number;
-    ScreenToLocalTransform: () => Transform;
-    nearestScreenPt: () => PointData | undefined;
-}
-
-@observer
-export class InkControlPtHandles extends React.Component<InkControlProps> {
-
-    @observable private _overControl = -1;
-    @observable private _overAddPoint = -1;
-    /**
-     * Handles the movement of a selected control point when the user clicks and drags.
-     * @param controlIndex The index of the currently selected control point.
-     */
-    @action
-    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;
-            const order = controlIndex % 4;
-            const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length;
-            const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length;
-            const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, 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(),
-                action((e: PointerEvent, doubleTap: boolean | undefined) => {
-                    const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
-                    if (doubleTap && brokenIndices?.includes(equivIndex)) {
-                        InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB);
-                    }
-                    if (doubleTap && brokenIndices?.includes(controlIndex)) {
-                        InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
-                    }
-                }));
-        }
-    }
-    /**
-     * 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; };
-
-    /**
-     * 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);
-        }
-    }
-
-    render() {
-        const formatInstance = InkStrokeProperties.Instance;
-        if (!formatInstance) return (null);
-
-        // Accessing the current ink's data and extracting all control points.
-        const scrData = this.props.screenCtrlPoints;
-        const sreenCtrlPoints: ControlPoint[] = [];
-        for (let i = 0; i <= scrData.length - 4; i += 4) {
-            sreenCtrlPoints.push({ ...scrData[i], I: i });
-            sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 });
-        }
-
-        const inkData = this.props.inkCtrlPoints;
-        const inkCtrlPts: ControlPoint[] = [];
-        for (let i = 0; i <= inkData.length - 4; i += 4) {
-            inkCtrlPts.push({ ...inkData[i], I: i });
-            inkCtrlPts.push({ ...inkData[i + 3], I: i + 3 });
-        }
-
-        const screenSpaceLineWidth = this.props.screenSpaceLineWidth;
-        const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4;
-
-        const nearestScreenPt = this.props.nearestScreenPt();
-        return (<svg>
-            {!nearestScreenPt ? (null) :
-                <circle key={"npt"}
-                    cx={nearestScreenPt.X}
-                    cy={nearestScreenPt.Y}
-                    r={screenSpaceLineWidth * 4}
-                    fill={"#00007777"}
-                    stroke={"#00007777"}
-                    strokeWidth={0}
-                    pointerEvents="none"
-                    cursor="all-scroll"
-                />
-            }
-            {sreenCtrlPoints.map((control, i) =>
-                <rect key={i}
-                    x={control.X - rectHdlSize(i) / 2}
-                    y={control.Y - rectHdlSize(i) / 2}
-                    height={rectHdlSize(i)}
-                    width={rectHdlSize(i)}
-                    strokeWidth={screenSpaceLineWidth / 2}
-                    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={e => 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
deleted file mode 100644
index dbe9ca027..000000000
--- a/src/client/views/InkHandles.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import React = require("react");
-import { action } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../fields/Doc";
-import { HandleLine, HandlePoint, InkData } from "../../fields/InkField";
-import { List } from "../../fields/List";
-import { listSpec } from "../../fields/Schema";
-import { Cast } from "../../fields/Types";
-import { emptyFunction, setupMoveUpEvents } from "../../Utils";
-import { Transform } from "../util/Transform";
-import { UndoManager } from "../util/UndoManager";
-import { Colors } from "./global/globalEnums";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-
-export interface InkHandlesProps {
-    inkDoc: Doc;
-    screenCtrlPoints: InkData;
-    screenSpaceLineWidth: number;
-    ScreenToLocalTransform: () => Transform;
-}
-
-@observer
-export class InkTangentHandles extends React.Component<InkHandlesProps> {
-    /**
-     * 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 => {
-        if (InkStrokeProperties.Instance) {
-            InkStrokeProperties.Instance.moveControl(0, 0, 1);
-            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
-            const screenScale = this.props.ScreenToLocalTransform().Scale;
-            const order = handleIndex % 4;
-            const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
-            const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length;
-            const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length;
-            setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
-                if (e.altKey) this.onBreakTangent(controlIndex);
-                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 = (controlIndex: number) => {
-        const doc = this.props.inkDoc;
-        if (doc) {
-            const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y;
-            const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List;
-            if (!brokenIndices?.includes(controlIndex) &&
-                ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) {
-                brokenIndices.push(controlIndex);
-                doc.brokenInkIndices = brokenIndices;
-            }
-        }
-    }
-
-    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.screenCtrlPoints;
-        const handlePoints: HandlePoint[] = [];
-        const handleLines: HandleLine[] = [];
-        const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y;
-        if (data.length >= 4) {
-            for (let i = 0; i <= data.length - 4; i += 4) {
-                handlePoints.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 });
-                handlePoints.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 });
-            }
-            // Adding first and last (single) handle lines.
-            if (closed) {
-                handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 });
-            }
-            else {
-                handleLines.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 });
-                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, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
-            }
-            for (let i = 2; i < data.length - 4; i += 4) {
-                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 screenSpaceLineWidth = this.props.screenSpaceLineWidth;
-
-        return (
-            <>
-                {handlePoints.map((pts, i) =>
-                    <svg height="10" width="10" key={`hdl${i}`}>
-                        <circle
-                            cx={pts.X}
-                            cy={pts.Y}
-                            r={screenSpaceLineWidth * 2}
-                            strokeWidth={0}
-                            fill={Colors.MEDIUM_BLUE}
-                            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}
-                            y1={pts.Y1}
-                            x2={pts.X2}
-                            y2={pts.Y2}
-                            stroke={Colors.MEDIUM_BLUE}
-                            strokeWidth={screenSpaceLineWidth}
-                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
-                        <line
-                            x1={pts.X2}
-                            y1={pts.Y2}
-                            x2={pts.X3}
-                            y2={pts.Y3}
-                            stroke={Colors.MEDIUM_BLUE}
-                            strokeWidth={screenSpaceLineWidth}
-                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
-                    </svg>)}
-            </>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
new file mode 100644
index 000000000..dbe9ca027
--- /dev/null
+++ b/src/client/views/InkTangentHandles.tsx
@@ -0,0 +1,130 @@
+import React = require("react");
+import { action } from "mobx";
+import { observer } from "mobx-react";
+import { Doc } from "../../fields/Doc";
+import { HandleLine, HandlePoint, InkData } from "../../fields/InkField";
+import { List } from "../../fields/List";
+import { listSpec } from "../../fields/Schema";
+import { Cast } from "../../fields/Types";
+import { emptyFunction, setupMoveUpEvents } from "../../Utils";
+import { Transform } from "../util/Transform";
+import { UndoManager } from "../util/UndoManager";
+import { Colors } from "./global/globalEnums";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+
+export interface InkHandlesProps {
+    inkDoc: Doc;
+    screenCtrlPoints: InkData;
+    screenSpaceLineWidth: number;
+    ScreenToLocalTransform: () => Transform;
+}
+
+@observer
+export class InkTangentHandles extends React.Component<InkHandlesProps> {
+    /**
+     * 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 => {
+        if (InkStrokeProperties.Instance) {
+            InkStrokeProperties.Instance.moveControl(0, 0, 1);
+            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
+            const screenScale = this.props.ScreenToLocalTransform().Scale;
+            const order = handleIndex % 4;
+            const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
+            const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length;
+            const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length;
+            setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
+                if (e.altKey) this.onBreakTangent(controlIndex);
+                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 = (controlIndex: number) => {
+        const doc = this.props.inkDoc;
+        if (doc) {
+            const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y;
+            const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List;
+            if (!brokenIndices?.includes(controlIndex) &&
+                ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) {
+                brokenIndices.push(controlIndex);
+                doc.brokenInkIndices = brokenIndices;
+            }
+        }
+    }
+
+    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.screenCtrlPoints;
+        const handlePoints: HandlePoint[] = [];
+        const handleLines: HandleLine[] = [];
+        const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y;
+        if (data.length >= 4) {
+            for (let i = 0; i <= data.length - 4; i += 4) {
+                handlePoints.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 });
+                handlePoints.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 });
+            }
+            // Adding first and last (single) handle lines.
+            if (closed) {
+                handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 });
+            }
+            else {
+                handleLines.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 });
+                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, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
+            }
+            for (let i = 2; i < data.length - 4; i += 4) {
+                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 screenSpaceLineWidth = this.props.screenSpaceLineWidth;
+
+        return (
+            <>
+                {handlePoints.map((pts, i) =>
+                    <svg height="10" width="10" key={`hdl${i}`}>
+                        <circle
+                            cx={pts.X}
+                            cy={pts.Y}
+                            r={screenSpaceLineWidth * 2}
+                            strokeWidth={0}
+                            fill={Colors.MEDIUM_BLUE}
+                            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}
+                            y1={pts.Y1}
+                            x2={pts.X2}
+                            y2={pts.Y2}
+                            stroke={Colors.MEDIUM_BLUE}
+                            strokeWidth={screenSpaceLineWidth}
+                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
+                        <line
+                            x1={pts.X2}
+                            y1={pts.Y2}
+                            x2={pts.X3}
+                            y2={pts.Y3}
+                            stroke={Colors.MEDIUM_BLUE}
+                            strokeWidth={screenSpaceLineWidth}
+                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
+                    </svg>)}
+            </>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index c9e401ab4..b921014a3 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -15,10 +15,10 @@ import { InteractionUtils } from "../util/InteractionUtils";
 import { ContextMenu } from "./ContextMenu";
 import { ViewBoxBaseComponent } from "./DocComponent";
 import { Colors } from "./global/globalEnums";
-import { InkControlPtHandles } from "./InkControls";
-import { InkTangentHandles } from "./InkHandles";
+import { InkControlPtHandles } from "./InkControlPtHandles";
 import "./InkStroke.scss";
 import { InkStrokeProperties } from "./InkStrokeProperties";
+import { InkTangentHandles } from "./InkTangentHandles";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
-- 
cgit v1.2.3-70-g09d2


From ca018693224f5f7737f0546c4fa5f4d4210a3020 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 28 Sep 2021 18:33:45 -0400
Subject: prevent crashes when ink points are not multiple of 4.  deleting ink
 ctrl point that leaves one bezier segment no longer converts to a line.

---
 src/client/util/InteractionUtils.tsx    | 2 +-
 src/client/views/InkStrokeProperties.ts | 8 --------
 2 files changed, 1 insertion(+), 9 deletions(-)

(limited to 'src/client/util/InteractionUtils.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 71d68262f..633876683 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -97,7 +97,7 @@ export namespace InteractionUtils {
         if (isNaN(scalex)) scalex = 1;
         if (isNaN(scaley)) scaley = 1;
 
-        const toScr = (p: { X: number, Y: number }) => ` ${(p.X - left - width / 2) * scalex + width / 2}, ${(p.Y - top - width / 2) * scaley + width / 2} `;
+        const toScr = (p: { X: number, Y: number }) => ` ${!p ? 0 : (p.X - left - width / 2) * scalex + width / 2}, ${!p ? 0 : (p.Y - top - width / 2) * scaley + width / 2} `;
         const strpts = bezier ?
             pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pt) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") :
             pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, "");
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index cfe6ec523..510c5f2dd 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -160,14 +160,6 @@ export class InkStrokeProperties {
         if (last) newPoints.splice(newPoints.length - 3, 2);
         this._currentPoint = -1;
         if (newPoints.length < 4) return undefined;
-        if (newPoints.length === 4) {
-            const newerPoints: { X: number, Y: number }[] = [];
-            newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y });
-            newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y });
-            newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y });
-            newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y });
-            return newerPoints;
-        }
         return newPoints;
     }, true)
 
-- 
cgit v1.2.3-70-g09d2


From e5905220a84a62fff36965a3bf74a55b793ae31b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 29 Sep 2021 11:26:58 -0400
Subject: fixed filling of curves.  added toggling of brokenindex with
 right-click.   changed look of ink handles to be lighter weight and to
 reflect brokenindex sttate

---
 src/client/util/InteractionUtils.tsx     |  2 +-
 src/client/views/InkControlPtHandles.tsx | 95 ++++++++++++++++++--------------
 src/client/views/InkTangentHandles.tsx   | 55 +++++++++---------
 src/client/views/InkingStroke.tsx        |  1 +
 4 files changed, 82 insertions(+), 71 deletions(-)

(limited to 'src/client/util/InteractionUtils.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 633876683..d43ebd692 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -99,7 +99,7 @@ export namespace InteractionUtils {
 
         const toScr = (p: { X: number, Y: number }) => ` ${!p ? 0 : (p.X - left - width / 2) * scalex + width / 2}, ${!p ? 0 : (p.Y - top - width / 2) * scaley + width / 2} `;
         const strpts = bezier ?
-            pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pt) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") :
+            pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : (i === 0 ? "M" + toScr(pt) : "") + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") :
             pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, "");
 
         const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined;
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 898c3bf26..f80aca268 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -10,6 +10,7 @@ import { Transform } from "../util/Transform";
 import { UndoManager } from "../util/UndoManager";
 import { Colors } from "./global/globalEnums";
 import { InkStrokeProperties } from "./InkStrokeProperties";
+import { List } from "../../fields/List";
 
 export interface InkControlProps {
     inkDoc: Doc;
@@ -24,6 +25,8 @@ export interface InkControlProps {
 export class InkControlPtHandles extends React.Component<InkControlProps> {
 
     @observable private _overControl = -1;
+
+    @observable controlUndo: UndoManager.Batch | undefined;
     /**
      * Handles the movement of a selected control point when the user clicks and drags.
      * @param controlIndex The index of the currently selected control point.
@@ -31,34 +34,40 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
     @action
     onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
         if (InkStrokeProperties.Instance) {
-            var controlUndo: UndoManager.Batch | undefined;
             const screenScale = this.props.ScreenToLocalTransform().Scale;
             const order = controlIndex % 4;
             const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length;
             const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length;
             const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"));
             setupMoveUpEvents(this, e,
-                (e: PointerEvent, down: number[], delta: number[]) => {
-                    if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
+                action((e: PointerEvent, down: number[], delta: number[]) => {
+                    if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
                     InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex);
                     return false;
-                },
-                () => {
-                    controlUndo?.end();
+                }),
+                action(() => {
+                    this.controlUndo?.end();
+                    this.controlUndo = undefined;
                     UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
-                },
+                }),
                 action((e: PointerEvent, doubleTap: boolean | undefined) => {
                     const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
-                    if (doubleTap) {
-                        if (brokenIndices?.includes(equivIndex)) {
-                            if (!controlUndo) controlUndo = UndoManager.StartBatch("make smooth");
-                            InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB);
-                        }
-                        if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
-                            if (!controlUndo) controlUndo = UndoManager.StartBatch("make smooth");
-                            InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
+                    if (doubleTap || e.button === 2) {
+                        if (!brokenIndices?.includes(equivIndex) && !brokenIndices?.includes(controlIndex)) {
+                            if (brokenIndices) brokenIndices.push(controlIndex);
+                            else this.props.inkDoc.brokenInkIndices = new List<number>([controlIndex]);
+                        } else {
+                            if (brokenIndices?.includes(equivIndex)) {
+                                if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth");
+                                InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB);
+                            }
+                            if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
+                                if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth");
+                                InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
+                            }
                         }
-                        controlUndo?.end();
+                        this.controlUndo?.end();
+                        this.controlUndo = undefined;
                     }
                 }));
         }
@@ -92,9 +101,6 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
     }
 
     render() {
-        const formatInstance = InkStrokeProperties.Instance;
-        if (!formatInstance) return (null);
-
         // Accessing the current ink's data and extracting all control points.
         const scrData = this.props.screenCtrlPoints;
         const sreenCtrlPoints: ControlPoint[] = [];
@@ -111,41 +117,46 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
         }
 
         const screenSpaceLineWidth = this.props.screenSpaceLineWidth;
-        const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4;
-
         const nearestScreenPt = this.props.nearestScreenPt();
+        const TagType = (broken?: boolean) => broken ? "rect" : "circle";
+        const hdl = (control: { X: number, Y: number, I: number }, scale: number, color: string) => {
+            const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"))?.includes(control.I);
+            const Tag = TagType(broken) as keyof JSX.IntrinsicElements;
+            return <circle key={control.I.toString() + scale}
+                x={control.X - screenSpaceLineWidth * 3 * scale}
+                y={control.Y - screenSpaceLineWidth * 3 * scale}
+                cx={control.X}
+                cy={control.Y}
+                r={screenSpaceLineWidth * 2 * scale}
+                opacity={this.controlUndo ? 0.15 : 1}
+                height={screenSpaceLineWidth * 6 * scale}
+                width={screenSpaceLineWidth * 6 * scale}
+                strokeWidth={screenSpaceLineWidth / 2}
+                stroke={Colors.MEDIUM_BLUE}
+                fill={broken ? Colors.MEDIUM_BLUE : color}
+                onPointerDown={(e: any) => {
+                    this.changeCurrPoint(control.I);
+                    this.onControlDown(e, control.I);
+                }}
+                onMouseEnter={() => this.onEnterControl(control.I)}
+                onMouseLeave={this.onLeaveControl}
+                pointerEvents="all"
+                cursor="default"
+            />;
+        }
         return (<svg>
             {!nearestScreenPt ? (null) :
                 <circle key={"npt"}
                     cx={nearestScreenPt.X}
                     cy={nearestScreenPt.Y}
-                    r={screenSpaceLineWidth * 4}
+                    r={screenSpaceLineWidth * 2}
                     fill={"#00007777"}
                     stroke={"#00007777"}
                     strokeWidth={0}
                     pointerEvents="none"
-                    cursor="all-scroll"
                 />
             }
-            {sreenCtrlPoints.map((control, i) =>
-                <rect key={i}
-                    x={control.X - rectHdlSize(i) / 2}
-                    y={control.Y - rectHdlSize(i) / 2}
-                    height={rectHdlSize(i)}
-                    width={rectHdlSize(i)}
-                    strokeWidth={screenSpaceLineWidth / 2}
-                    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={e => this.onEnterControl(i)}
-                    onMouseLeave={this.onLeaveControl}
-                    pointerEvents="all"
-                    cursor="default"
-                />
-            )}
+            {sreenCtrlPoints.map(control => hdl(control, this._overControl !== control.I ? 1 : 3 / 2, Colors.WHITE))}
         </svg>
         );
     }
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index 759e48134..4f1a406c2 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -68,62 +68,61 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
 
         // Accessing the current ink's data and extracting all handle points and handle lines.
         const data = this.props.screenCtrlPoints;
-        const handlePoints: HandlePoint[] = [];
-        const handleLines: HandleLine[] = [];
+        const tangentHandles: HandlePoint[] = [];
+        const tangentLines: HandleLine[] = [];
         const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y;
         if (data.length >= 4) {
             for (let i = 0; i <= data.length - 4; i += 4) {
-                handlePoints.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 });
-                handlePoints.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 });
+                tangentHandles.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 });
+                tangentHandles.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 });
             }
             // Adding first and last (single) handle lines.
             if (closed) {
-                handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 });
+                tangentLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 });
             }
             else {
-                handleLines.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 });
-                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, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
+                tangentLines.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 });
+                tangentLines.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 });
             }
             for (let i = 2; i < data.length - 4; i += 4) {
-                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 });
+                tangentLines.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 screenSpaceLineWidth = this.props.screenSpaceLineWidth;
 
         return (
             <>
-                {handlePoints.map((pts, i) =>
+                {tangentHandles.map((pts, i) =>
                     <svg height="10" width="10" key={`hdl${i}`}>
                         <circle
                             cx={pts.X}
                             cy={pts.Y}
                             r={screenSpaceLineWidth * 2}
-                            strokeWidth={0}
-                            fill={Colors.MEDIUM_BLUE}
+                            fill={"transparent"}
+                            strokeWidth={1}
+                            stroke={Colors.MEDIUM_BLUE}
+                            strokeDasharray={"1 1"}
                             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}
-                            y1={pts.Y1}
-                            x2={pts.X2}
-                            y2={pts.Y2}
-                            stroke={Colors.MEDIUM_BLUE}
-                            strokeWidth={screenSpaceLineWidth}
-                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
+                {tangentLines.map((pts, i) => {
+                    const tangentLine = (x1: number, y1: number, x2: number, y2: number) =>
                         <line
-                            x1={pts.X2}
-                            y1={pts.Y2}
-                            x2={pts.X3}
-                            y2={pts.Y3}
+                            x1={x1}
+                            y1={y1}
+                            x2={x2}
+                            y2={y2}
                             stroke={Colors.MEDIUM_BLUE}
-                            strokeWidth={screenSpaceLineWidth}
-                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
-                    </svg>)}
+                            strokeDasharray={"1 1"}
+                            strokeWidth={1}
+                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />;
+                    return <svg height="100" width="100" key={`line${i}`}>
+                        {tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)}
+                        {tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)}
+                    </svg>;
+                })}
             </>
         );
     }
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 867677005..4cd7f7698 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -210,6 +210,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     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",
+                    cursor: this.props.isSelected() ? "default" : undefined
                 }}
                 onPointerLeave={action(e => this._nearestScrPt = undefined)}
                 onPointerMove={this.props.isSelected() ? this.onPointerMove : undefined}
-- 
cgit v1.2.3-70-g09d2


From c7121eb1756e04eafd6a74e665ef46ada655df5a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 29 Sep 2021 22:13:29 -0400
Subject: cleaned up/fixed line arrowheads & dots.  added linecap options for
 lines, added outline of lines using fillColor. turned off border in comic
 mode for ink.

---
 src/client/util/InteractionUtils.tsx | 33 +++++++++++++++++----------------
 src/client/views/GestureOverlay.tsx  |  9 ++++++---
 src/client/views/InkingStroke.tsx    | 32 +++++++++++++++++++++++---------
 src/client/views/StyleProvider.tsx   |  2 +-
 4 files changed, 47 insertions(+), 29 deletions(-)

(limited to 'src/client/util/InteractionUtils.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index d43ebd692..48fb968cf 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -90,7 +90,7 @@ export namespace InteractionUtils {
     }
 
     export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number,
-        color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
+        color: string, width: number, strokeWidth: number, lineJoin: string, lineCap: string, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
         dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, opacity: number, nodefs: boolean) {
         const pts = shape ? makePolygon(shape, points) : points;
 
@@ -104,20 +104,23 @@ export namespace InteractionUtils {
 
         const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined;
         const defGuid = Utils.GenerateGuid();
-        const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth)));
 
         const Tag = (bezier ? "path" : "polyline") as keyof JSX.IntrinsicElements;
+        const makerStrokeWidth = strokeWidth / 2;
         return (<svg fill={color}> {/* setting the svg fill sets the arrowStart fill */}
             {nodefs ? (null) : <defs>
-                {arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : <marker id={`dot${defGuid}`} orient="auto" overflow="visible">
-                    <circle r={1} fill="context-stroke" />
-                </marker>}
-                {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : <marker id={`arrowStart${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7">
-                    <polygon points={`${arrowDim} ${-Math.max(1, arrowDim / 2)}, ${arrowDim} ${Math.max(1, arrowDim / 2)}, -1 0`} />
-                </marker>}
-                {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : <marker id={`arrowEnd${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7">
-                    <polygon points={`${2 - arrowDim} ${-Math.max(1, arrowDim / 2)}, ${2 - arrowDim} ${Math.max(1, arrowDim / 2)}, 3 0`} />
-                </marker>}
+                {arrowStart !== "dot" && arrowEnd !== "dot" ? (null) :
+                    <marker id={`dot${defGuid}`} orient="auto" markerUnits="userSpaceOnUse" refX={0} refY="0" overflow="visible">
+                        <circle r={strokeWidth * 1.5} fill="context-stroke" />
+                    </marker>}
+                {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) :
+                    <marker id={`arrowStart${defGuid}`} markerUnits="userSpaceOnUse" orient="auto" overflow="visible" refX={makerStrokeWidth * 1.5} refY={0} markerWidth="10" markerHeight="7">
+                        <polygon style={{ stroke: color }} strokeLinejoin={lineJoin as any} strokeWidth={makerStrokeWidth * 2 / 3} points={`${3 * makerStrokeWidth} ${-makerStrokeWidth * 1.5}, ${makerStrokeWidth * 2} 0, ${3 * makerStrokeWidth} ${makerStrokeWidth * 1.5}, 0 0`} />
+                    </marker>}
+                {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) :
+                    <marker id={`arrowEnd${defGuid}`} markerUnits="userSpaceOnUse" orient="auto" overflow="visible" refX={makerStrokeWidth * 1.5} refY={0} markerWidth="10" markerHeight="7">
+                        <polygon style={{ stroke: color }} strokeLinejoin={lineJoin as any} strokeWidth={makerStrokeWidth * 2 / 3} points={`0 ${-makerStrokeWidth * 1.5}, ${makerStrokeWidth} 0, 0 ${makerStrokeWidth * 1.5}, ${3 * makerStrokeWidth} 0`} />
+                    </marker>}
             </defs>}
 
             <Tag
@@ -131,14 +134,12 @@ export namespace InteractionUtils {
                     pointerEvents: pevents as any,
                     stroke: color ?? "rgb(0, 0, 0)",
                     strokeWidth: strokeWidth,
-                    strokeLinejoin: color === "rgba(245, 230, 95, 0.75)" ? "miter" : "round",
-                    strokeLinecap: color === "rgba(245, 230, 95, 0.75)" ? "square" : "round",
+                    strokeLinecap: lineCap as any,
                     strokeDasharray: dashArray
                 }}
-                markerStart={`url(#${arrowStart + "Start" + defGuid})`}
-                markerEnd={`url(#${arrowEnd + "End" + defGuid})`}
+                markerStart={`url(#${arrowStart === "dot" ? arrowStart + defGuid : arrowStart + "Start" + defGuid})`}
+                markerEnd={`url(#${arrowEnd === "dot" ? arrowEnd + defGuid : arrowEnd + "End" + defGuid})`}
             />
-            {/* {addables} */}
 
         </svg>);
     }
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index a95660409..f28485e43 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -841,20 +841,23 @@ export class GestureOverlay extends Touchable {
         B.bottom = B.bottom + width / 2;
         B.width += width;
         B.height += width;
+        const fillColor = ActiveFillColor();
+        const strokeColor = fillColor && fillColor !== "transparent" ? fillColor : ActiveInkColor();
         return [
             this.props.children,
             this._palette,
             [this._strokes.map((l, i) => {
                 const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 };//this.getBounds(l, true);
                 return <svg key={i} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
-                    {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), width, width,
-                        ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(),
+                    {InteractionUtils.CreatePolyline(l, b.left, b.top, strokeColor, width, width, "miter", "round",
+                        ActiveInkBezierApprox(), "none" /*ActiveFillColor()*/, ActiveArrowStart(), ActiveArrowEnd(),
                         ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)}
                 </svg>;
             }),
             this._points.length <= 1 ? (null) : <svg key="svg" width={B.width} height={B.height}
                 style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
-                {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, "", ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)}
+                {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, "miter", "round", "",
+                    "none" /*ActiveFillColor()*/, ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)}
             </svg>]
         ];
     }
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 8b1b3ea32..952af3019 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -20,6 +20,7 @@ import "./InkStroke.scss";
 import { InkStrokeProperties } from "./InkStrokeProperties";
 import { InkTangentHandles } from "./InkTangentHandles";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
+import { SnappingManager } from "../util/SnappingManager";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
@@ -143,6 +144,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const inkDoc = this.props.Document;
         const screenSpaceCenterlineStrokeWidth = 3;  // the width of the blue line widget that shows the centerline of the ink stroke
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
+        const closed = inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
 
         const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
         const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
@@ -150,13 +152,15 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
         const screenHdlPts = screenPts;
 
-        return <div className="inkstroke-UI" style={{
+        const startMarker = StrCast(this.layoutDoc.strokeStartMarker);
+        const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
+        return SnappingManager.GetIsDragging() ? (null) : <div className="inkstroke-UI" style={{
             clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)`
         }} >
             {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth,
-                StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"),
-                StrCast(inkDoc.strokeStartMarker), StrCast(inkDoc.strokeEndMarker),
-                StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
+                StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier),
+                !closed ? "none" : StrCast(this.layoutDoc.fillColor, "none"),
+                startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
             {!this._properties?._controlButton ? (null) :
                 <>
                     <InkControlPtHandles
@@ -178,17 +182,27 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
     render() {
         TraceMobx();
         const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();
-        const strokeColor = StrCast(this.layoutDoc.color);
+
+        const startMarker = StrCast(this.layoutDoc.strokeStartMarker);
+        const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
+        const closed = inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
+        const fillColor = StrCast(this.layoutDoc.fillColor, "transparent");
+        const strokeColor = !closed && fillColor && fillColor !== "transparent" ? fillColor : StrCast(this.layoutDoc.color);
 
         // Visually renders the polygonal line made by the user.
-        const inkLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, strokeColor, inkStrokeWidth, inkStrokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
-            StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, false);
+        const inkLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, strokeColor, inkStrokeWidth, inkStrokeWidth,
+            StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
+            StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
+            StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, false);
         // Thin blue line indicating that the current ink stroke is selected.
         // 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", 1.0, false);
         // Invisible polygonal line that enables the ink to be selected by the user.
-        const clickableLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, "transparent", inkStrokeWidth, inkStrokeWidth + 15, StrCast(this.layoutDoc.strokeBezier),
-            StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, inkScaleX, inkScaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", 0.0, true);
+        const clickableLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop,
+            StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== "transparent" ? StrCast(this.layoutDoc.color, "transparent") : "transparent"), inkStrokeWidth, inkStrokeWidth + 15,
+            StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
+            StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
+            undefined, inkScaleX, inkScaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", 0.0, false);
         // Set of points rendered upon the ink that can be added if a user clicks on one.
 
         return (
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index e86a877f6..13afe4b3f 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -108,7 +108,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.Hidden: return BoolCast(doc?._hidden);
         case StyleProp.BorderRounding: return StrCast(doc?.[fieldKey + "borderRounding"], doc?._viewType === CollectionViewType.Pile ? "50%" : "");
         case StyleProp.TitleHeight: return 15;
-        case StyleProp.BorderPath: return comicStyle() && props?.renderDepth ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, .08), width: 3 } : { path: undefined, width: 0 };
+        case StyleProp.BorderPath: return comicStyle() && props?.renderDepth && doc?.type !== DocumentType.INK ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, .08), width: 3 } : { path: undefined, width: 0 };
         case StyleProp.JitterRotation: return comicStyle() ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
         case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) ||
             doc?.type === DocumentType.RTF) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0;
-- 
cgit v1.2.3-70-g09d2


From 4ccaecc185bed251ee74d1d9168cf3602c969680 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 30 Sep 2021 10:20:02 -0400
Subject: fixed dragging lines with arrows to display arrowhead

---
 src/client/util/DragManager.ts       | 16 ++++++++++++++++
 src/client/util/InteractionUtils.tsx |  4 ++--
 2 files changed, 18 insertions(+), 2 deletions(-)

(limited to 'src/client/util/InteractionUtils.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 421e4c6bb..6fde4b06e 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -365,6 +365,22 @@ export namespace DragManager {
         const dragElements = eles.map(ele => {
             if (!ele.parentNode) dragDiv.appendChild(ele);
             const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement;
+            let children = Array.from(dragElement.children);
+            while (children.length) { // need to replace all the maker node reference ids with new unique ids. otherwise, the clone nodes will reference the original nodes which are all hidden during the drag
+                const next = children.pop();
+                next && children.push(...Array.from(next.children));
+                if (next) {
+                    if (next.localName.startsWith("path") && (next.attributes as any)["marker-start"]) {
+                        next.setAttribute("marker-start", (next.attributes as any)["marker-start"].value.replace("#", "#X"));
+                    }
+                    if (next.localName.startsWith("path") && (next.attributes as any)["marker-end"]) {
+                        next.setAttribute("marker-end", (next.attributes as any)["marker-end"].value.replace("#", "#X"));
+                    }
+                    if (next.localName.startsWith("marker")) {
+                        next.id = "X" + next.id;
+                    }
+                }
+            }
             const rect = ele.getBoundingClientRect();
             const scaleX = rect.width / ele.offsetWidth;
             const scaleY = ele.offsetHeight ? rect.height / ele.offsetHeight : scaleX;
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 48fb968cf..a32a8eecc 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -113,11 +113,11 @@ export namespace InteractionUtils {
                     <marker id={`dot${defGuid}`} orient="auto" markerUnits="userSpaceOnUse" refX={0} refY="0" overflow="visible">
                         <circle r={strokeWidth * 1.5} fill="context-stroke" />
                     </marker>}
-                {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) :
+                {arrowStart !== "arrow" ? (null) :
                     <marker id={`arrowStart${defGuid}`} markerUnits="userSpaceOnUse" orient="auto" overflow="visible" refX={makerStrokeWidth * 1.5} refY={0} markerWidth="10" markerHeight="7">
                         <polygon style={{ stroke: color }} strokeLinejoin={lineJoin as any} strokeWidth={makerStrokeWidth * 2 / 3} points={`${3 * makerStrokeWidth} ${-makerStrokeWidth * 1.5}, ${makerStrokeWidth * 2} 0, ${3 * makerStrokeWidth} ${makerStrokeWidth * 1.5}, 0 0`} />
                     </marker>}
-                {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) :
+                {arrowEnd !== "arrow" ? (null) :
                     <marker id={`arrowEnd${defGuid}`} markerUnits="userSpaceOnUse" orient="auto" overflow="visible" refX={makerStrokeWidth * 1.5} refY={0} markerWidth="10" markerHeight="7">
                         <polygon style={{ stroke: color }} strokeLinejoin={lineJoin as any} strokeWidth={makerStrokeWidth * 2 / 3} points={`0 ${-makerStrokeWidth * 1.5}, ${makerStrokeWidth} 0, 0 ${makerStrokeWidth * 1.5}, ${3 * makerStrokeWidth} 0`} />
                     </marker>}
-- 
cgit v1.2.3-70-g09d2

