From 3381bbb0ef5160707513f4bbbe551ca551b64b0d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 13 Nov 2021 10:38:53 -0500
Subject: change isContentActive to a tri-state to allow turning on/off and
 default - fixes issues with videobox and others so that content can be turned
 off reliably.   added annotation overlay for treeViews for ppt like slides. 
 lots of fixes to tree view to get layout to be more robust.

---
 .../views/nodes/formattedText/FormattedTextBox.tsx     | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index aa53f751d..1170528df 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -123,7 +123,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     @computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
     @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); }
-    @computed get autoHeight() { return this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight; }
+    @computed get autoHeight() { return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; }
     @computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); }
     @computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); }
     @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + "-height"]); }
@@ -1565,7 +1565,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     setHeight={this.setSidebarHeight}
                     fitContentsToDoc={this.fitToBox}
                     noSidebar={true}
-                    fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : this.SidebarKey} />;
+                    fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} />;
         };
         return <div className={"formattedTextBox-sidebar" + (CurrentUserUtils.SelectedTool !== InkTool.None ? "-inking" : "")}
             style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
@@ -1581,10 +1581,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         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);
         const minimal = this.props.ignoreAutoHeight;
-        const margins = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
-        const selPad = Math.min(margins, 10);
-        const padding = Math.max(margins + ((selected && !this.layoutDoc._singleLine) || minimal ? -selPad : 0), 0);
-        const selPaddingClass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : "";
+        const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);
+        const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
+        const selPad = ((selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0);
+        const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? "-selected" : "";
         const styleFromString = this.styleFromLayoutString(scale);   // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
         return (styleFromString?.height === "0px" ? (null) :
             <div className="formattedTextBox-cont"
@@ -1628,7 +1628,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         onScroll={this.onScroll} onDrop={this.ondrop} >
                         <div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selPaddingClass}`} ref={this.createDropTarget}
                             style={{
-                                padding: StrCast(this.layoutDoc._textBoxPadding, `${padding}px`),
+                                padding: StrCast(this.layoutDoc._textBoxPadding),
+                                paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
+                                paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
+                                paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
+                                paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
                                 pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? "none" : undefined) : undefined
                             }}
                         />
-- 
cgit v1.2.3-70-g09d2


From 4ee47eae8735adf4c543c0de4859a09dee10cbf0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 15 Nov 2021 12:25:31 -0500
Subject: fixed initial fontsize for treeView slides.  made fontsize use
 styleprovider.  fixed caption width to cover the whole document by fixing
 overflow-y setting

---
 src/client/views/StyleProvider.tsx                        | 4 +++-
 src/client/views/nodes/DocumentView.scss                  | 2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ++--
 3 files changed, 6 insertions(+), 4 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3f120d385..8d8630907 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -46,6 +46,7 @@ export enum StyleProp {
     JitterRotation = "jitterRotation",    // whether documents should be randomly rotated
     BorderPath = "customBorder",          // border path for document view
     FontSize = "fontSize",                // size of text font
+    FontFamily = "fontFamily",                // size of text font
 }
 
 function darkScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark; }
@@ -91,7 +92,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? "lightgrey" : "dimgrey";
         case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
         case StyleProp.HideLinkButton: return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
-        case StyleProp.FontSize: return StrCast(doc?.[fieldKey + "fontSize"]);
+        case StyleProp.FontSize: return StrCast(doc?.[fieldKey + "fontSize"], StrCast(doc?.fontSize, StrCast(Doc.UserDoc().fontSize)));
+        case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + "fontFamily"], StrCast(doc?.["fontFamily"], StrCast(Doc.UserDoc().fontFamily)));
         case StyleProp.ShowTitle: return (doc && !doc.presentationTargetDoc &&
             StrCast(doc._showTitle,
                 props?.showTitle?.() ||
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 1ec7bf72a..9fcd45e72 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -175,7 +175,7 @@
             position: absolute;
             bottom: 0;
             width: 100%;
-            overflow-y: scroll;
+            overflow-y: auto;
             transform-origin: bottom left;
             opacity: 0.1;
             transition: opacity 0.5s;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1170528df..d9a5f2b48 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1174,8 +1174,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             ...(Doc.UserDoc().fontColor !== "transparent" && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
             ...(Doc.UserDoc().fontStyle === "italics" ? [schema.mark(schema.marks.em)] : []),
             ...(Doc.UserDoc().textDecoration === "underline" ? [schema.mark(schema.marks.underline)] : []),
-            ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: StrCast(Doc.UserDoc().fontFamily) })] : []),
-            ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: StrCast(Doc.UserDoc().fontSize, "") })] : []),
+            ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []),
+            ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
             ...(Doc.UserDoc().fontWeight === "bold" ? [schema.mark(schema.marks.strong)] : [])];
         }
     }
-- 
cgit v1.2.3-70-g09d2


From 80b27ef548936dbddaecc62f1068fff12546bf32 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 16 Nov 2021 20:36:25 -0500
Subject: added textboxes inside closed ink strokes

---
 package-lock.json                                  |  6 ++---
 src/client/views/InkStroke.scss                    | 31 ++++++++++++++--------
 src/client/views/InkingStroke.tsx                  | 30 ++++++++++++++++++---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 14 +++++++---
 4 files changed, 59 insertions(+), 22 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/package-lock.json b/package-lock.json
index d03f8af6b..128d6fba8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7709,14 +7709,14 @@
             "resolved": "https://registry.npmjs.org/image-size-stream/-/image-size-stream-1.1.0.tgz",
             "integrity": "sha1-Ivou2mbG31AQh0bacUkmSy0l+Gs=",
             "requires": {
-                "image-size": "image-size@git+https://github.com/netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1",
+                "image-size": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1",
                 "readable-stream": "^1.0.33",
                 "tryit": "^1.0.1"
             },
             "dependencies": {
                 "image-size": {
-                    "version": "git+ssh://git@github.com/netroy/image-size.git#da2c863807a3e9602617bdd357b0de3ab4a064c1",
-                    "from": "image-size@git+https://github.com/netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1"
+                    "version": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1",
+                    "from": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1"
                 },
                 "isarray": {
                     "version": "0.0.1",
diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss
index 55e06c6ca..2127826b4 100644
--- a/src/client/views/InkStroke.scss
+++ b/src/client/views/InkStroke.scss
@@ -13,16 +13,25 @@
   }
 }
 
-.inkStroke {
-  mix-blend-mode: multiply;
-  stroke-linejoin: round;
-  stroke-linecap: round;
-  overflow: visible !important;
-  transform-origin: top left;
-  width: 100%;
-  height: 100%;
+.inkStroke-wrapper {
+    .inkStroke {
+        mix-blend-mode: multiply;
+        stroke-linejoin: round;
+        stroke-linecap: round;
+        overflow: visible !important;
+        transform-origin: top left;
+        width: 100%;
+        height: 100%;
 
-  svg:not(:root) {
-    overflow: visible !important;
-  }
+        svg:not(:root) {
+            overflow: visible !important;
+        }
+    }
+
+    .inkStroke-text {
+        position: absolute;
+        &:hover {
+            background: #9f9f9f0a;
+        }
+    }
 }
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index ecb46a5b3..95dad5c6e 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,13 +1,13 @@
 import React = require("react");
 import { action, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../fields/Doc";
+import { Doc, HeightSym, WidthSym } from "../../fields/Doc";
 import { documentSchema } from "../../fields/documentSchemas";
 import { InkData, InkField, InkTool } from "../../fields/InkField";
 import { makeInterface } from "../../fields/Schema";
 import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
 import { TraceMobx } from "../../fields/util";
-import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
+import { emptyFunction, returnFalse, setupMoveUpEvents, OmitKeys } from "../../Utils";
 import { CognitiveServices } from "../cognitive_services/CognitiveServices";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { InteractionUtils } from "../util/InteractionUtils";
@@ -22,6 +22,7 @@ import { InkTangentHandles } from "./InkTangentHandles";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
 import Color = require("color");
 import { Transform } from "../util/Transform";
+import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
@@ -252,9 +253,11 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents ?? (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 (
+        return <div className="inkStroke-wrapper" style={{ display: "flex", alignItems: "center", height: "100%" }}>
             <svg className="inkStroke"
                 style={{
+                    width: "100%",
+                    height: "100%",
                     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",
@@ -275,7 +278,26 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                 {clickableLine}
                 {inkLine}
             </svg>
-        );
+            {!closed ? (null) :
+                <div className="inkStroke-text" style={{
+                    color: StrCast(this.layoutDoc.textColor, "black"),
+                    pointerEvents: this.props.isDocumentActive?.() ? "all" : undefined,
+                    width: this.layoutDoc[WidthSym](),
+                }}>
+                    <FormattedTextBox
+                        {...OmitKeys(this.props, ['children']).omit}
+                        yPadding={10}
+                        xPadding={10}
+                        fieldKey={"text"}
+                        fontSize={12}
+                        dontRegisterView={true}
+                        noSidebar={true}
+                        dontScale={true}
+                        isContentActive={this.isContentActive}
+                    />
+                </div>
+            }
+        </div>;
     }
 }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index aa53f751d..d9c417b41 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1140,10 +1140,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
             });
-            const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
-            if (startupText) {
-                const { state: { tr }, dispatch } = this._editorView;
-                dispatch(tr.insertText(startupText));
+            const { state, dispatch } = this._editorView;
+            if (!rtfField) {
+                const startupText = Field.toString(this.dataDoc[fieldKey] as Field);
+                if (startupText) {
+                    dispatch(state.tr.insertText(startupText));
+                } else {
+                    selectAll(this._editorView!.state, (tr) => {
+                        this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: "center" })));
+                    });
+                }
             }
             (this._editorView as any).TextView = this;
         }
-- 
cgit v1.2.3-70-g09d2


From 1c0ca018dce4392a91b37ab20b936aa137c1e8a9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 17 Nov 2021 21:09:02 -0500
Subject: changed link line curves a bit.  fixed formatted text box not to
 center text for typed notes.

---
 src/client/views/InkingStroke.tsx                  |  2 +-
 .../CollectionFreeFormLinkView.tsx                 | 34 ++++++++++++++++------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 3 files changed, 27 insertions(+), 11 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 95dad5c6e..59efb36dd 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -247,7 +247,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex];
         // Invisible polygonal line that enables the ink to be selected by the user.
         const clickableLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor,
-            inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && (new Color(fillColor)).alpha() < 1 ? 6 : 15),
+            inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && fillColor && (new Color(fillColor)).alpha() < 1 ? 6 : 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.pointerEvents ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0, false);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 9769453a0..9cc887e3d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -153,28 +153,44 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         const atop = this.visibleY(adiv);
         const btop = this.visibleY(bdiv);
         if (!a.width || !b.width) return undefined;
+        const aDocBounds = (A.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
+        const bDocBounds = (B.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
+        const acentX = (a.left + a.right) / 2;
+        const acentY = (a.top + a.bottom) / 2;
+        const bcentX = (b.left + b.right) / 2;
+        const bcentY = (b.top + b.bottom) / 2;
+        const pt1Arc = ((acentX - aDocBounds.left) > 0.1 && (aDocBounds.right - acentX) > 0.1) ||
+            ((acentY - aDocBounds.top) > 0.1 && (aDocBounds.bottom - acentY) > 0.1);
+        const pt2Arc = ((bcentX - bDocBounds.left) > 0.1 && (bDocBounds.right - bcentX) > 0.1) ||
+            ((bcentY - bDocBounds.top) > 0.1 && (bDocBounds.bottom - bcentY) > 0.1);
         const atop2 = this.visibleY(adiv);
         const btop2 = this.visibleY(bdiv);
         const aleft = this.visibleX(adiv);
         const bleft = this.visibleX(bdiv);
         const clipped = aleft !== a.left || atop !== a.top || bleft !== b.left || btop !== b.top;
-        const apt = Utils.closestPtBetweenRectangles(aleft, atop, a.width, a.height, bleft, btop, b.width, b.height, a.left + a.width / 2, a.top + a.height / 2);
-        const bpt = Utils.closestPtBetweenRectangles(bleft, btop, b.width, b.height, aleft, atop, a.width, a.height, apt.point.x, apt.point.y);
-        const pt1 = [apt.point.x, apt.point.y];
-        const pt2 = [bpt.point.x, bpt.point.y];
-        const pt1vec = [pt1[0] - (aleft + a.width / 2), pt1[1] - (atop + a.height / 2)];
-        const pt2vec = [pt2[0] - (bleft + b.width / 2), pt2[1] - (btop + b.height / 2)];
+        const pt1 = [aleft + a.width / 2, atop + a.height / 2];
+        const pt2 = [bleft + b.width / 2, btop + b.width / 2];
+        const pt1vec = [pt1[0] - (aDocBounds.left + aDocBounds.right) / 2, pt1[1] - (aDocBounds.top + aDocBounds.bottom) / 2];
+        const pt2vec = [pt2[0] - (bDocBounds.left + bDocBounds.right) / 2, pt2[1] - (bDocBounds.top + bDocBounds.bottom) / 2];
         const pt1len = Math.sqrt((pt1vec[0] * pt1vec[0]) + (pt1vec[1] * pt1vec[1]));
         const pt2len = Math.sqrt((pt2vec[0] * pt2vec[0]) + (pt2vec[1] * pt2vec[1]));
         const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2;
-        const pt1norm = clipped ? [0, 0] : [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen];
-        const pt2norm = clipped ? [0, 0] : [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen];
+        const pt1norm = clipped ? [0, 0] : !pt1Arc ? [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen] :
+            Math.abs(acentY - aDocBounds.top) < 0.01 ||
+                Math.abs(acentY - aDocBounds.bottom) < 0.01 ? [0, (pt2[1] - pt1[1]) / 2] : [(pt2[0] - pt1[0]) / 2, 0];
+        const pt2norm = clipped ? [0, 0] : !pt2Arc ? [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen] :
+            Math.abs(bcentY - bDocBounds.top) < 0.01 ||
+                Math.abs(bcentY - bDocBounds.bottom) < 0.01 ? [0, (pt1[1] - pt2[1]) / 2] : [(pt1[0] - pt2[0]) / 2, 0];
+        const pt1normlen = Math.sqrt(pt1norm[0] * pt1norm[0] + pt1norm[1] * pt1norm[1]) || 1;
+        const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1;
+        const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen];
+        const pt2normalized = [pt2norm[0] / pt2normlen, pt2norm[1] / pt2normlen];
         const aActive = A.isSelected() || Doc.IsBrushed(A.rootDoc);
         const bActive = B.isSelected() || Doc.IsBrushed(B.rootDoc);
 
         const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].linkOffsetX);
         const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].linkOffsetY);
-        return { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 };
+        return { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13], pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13] };
     }
 
     render() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d9c417b41..77f33b432 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1145,7 +1145,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 const startupText = Field.toString(this.dataDoc[fieldKey] as Field);
                 if (startupText) {
                     dispatch(state.tr.insertText(startupText));
-                } else {
+                } else if (!FormattedTextBox.LiveTextUndo) {
                     selectAll(this._editorView!.state, (tr) => {
                         this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: "center" })));
                     });
-- 
cgit v1.2.3-70-g09d2


From 91247d583e5e4c7205a1ed764dd0e3a12af3be25 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 30 Nov 2021 23:37:49 -0500
Subject: fixed warnings/errors.  added inkingStroke comments.  need to
 double-click now to add a point to an ink stroke.

---
 src/client/util/bezierFit.ts                       | 242 ++++++++++-----------
 src/client/views/DocumentDecorations.tsx           |   8 +-
 src/client/views/GlobalKeyHandler.ts               |   2 +-
 src/client/views/InkControlPtHandles.tsx           |  34 ++-
 src/client/views/InkStroke.scss                    |   5 +-
 src/client/views/InkStrokeProperties.ts            |  15 +-
 src/client/views/InkTangentHandles.tsx             |  28 +--
 src/client/views/InkingStroke.tsx                  | 174 ++++++++-------
 src/client/views/MainView.tsx                      |   1 -
 src/client/views/PropertiesView.tsx                |  21 +-
 src/client/views/StyleProvider.tsx                 |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 src/client/views/search/SearchBox.tsx              |  18 +-
 13 files changed, 269 insertions(+), 283 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index 57c6dbbde..784bb2e18 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -1,5 +1,4 @@
 import { Point } from "../../pen-gestures/ndollar";
-import { max } from "lodash";
 
 class SmartRect {
     minx: number = 0;
@@ -21,45 +20,38 @@ class SmartRect {
 
     public ContainsPercentage(other: SmartRect, axis: Point) {
         var ret = 0;
-        var minx = Math.max(other.TopLeft.X * axis.X + other.TopLeft.Y * axis.Y, this.TopLeft.X * axis.X + this.TopLeft.Y * axis.Y);
-        var maxx = Math.max(other.BotRight.X * axis.X + other.BotRight.Y * axis.Y, this.BotRight.X * axis.X + this.BotRight.Y * axis.Y);
-        ret = maxx > minx ? (maxx - minx) / (axis == new Point(1, 0) ? other.Width : other.Height) : 0;
+        const minx = Math.max(other.TopLeft.X * axis.X + other.TopLeft.Y * axis.Y, this.TopLeft.X * axis.X + this.TopLeft.Y * axis.Y);
+        const maxx = Math.max(other.BotRight.X * axis.X + other.BotRight.Y * axis.Y, this.BotRight.X * axis.X + this.BotRight.Y * axis.Y);
+        ret = maxx > minx ? (maxx - minx) / (axis === new Point(1, 0) ? other.Width : other.Height) : 0;
         return ret;
     }
     public static Bounds(p: Point[]) {
-        var r = new SmartRect();
+        const r = new SmartRect();
         if (p.length > 0) {
             r.minx = p[0].X; // These are the most likely to be extremal
             r.maxx = p.lastElement().X;
             r.miny = p[0].Y;
             r.maxy = p.lastElement().Y;
 
-            if (r.minx > r.maxx) {
-                var tmp = r.minx;
-                r.minx = r.maxx;
-                r.maxx = tmp;
-            }
-            if (r.miny > r.maxy) {
-                var tmp = r.miny;
-                r.miny = r.maxy;
-                r.maxy = tmp;
-            }
+            if (r.minx > r.maxx) [r.minx, r.maxx] = [r.maxx, r.minx];
+            if (r.miny > r.maxy) [r.miny, r.maxy] = [r.maxy, r.miny];
 
-            for (var pt of p) {
-                if (pt.X < r.minx)
+            for (const pt of p) {
+                if (pt.X < r.minx) {
                     r.minx = pt.X;
-                else if (pt.X > r.maxx)
+                } else if (pt.X > r.maxx) {
                     r.maxx = pt.X;
-
-                if (pt.Y < r.miny)
+                }
+                if (pt.Y < r.miny) {
                     r.miny = pt.Y;
-                else if (pt.Y > r.maxy)
+                } else if (pt.Y > r.maxy) {
                     r.maxy = pt.Y;
+                }
             }
         }
         return r;
     }
-};
+}
 
 function Normalize(p: Point) {
     const len = Math.sqrt(p.X * p.X + p.Y * p.Y);
@@ -67,7 +59,7 @@ function Normalize(p: Point) {
 }
 
 function ReparameterizeBezier(d: Point[], first: number, last: number, u: number[], bezCurve: Point[]) {
-    var uPrime = new Array<number>(last - first + 1); //  New parameter values
+    const uPrime = new Array<number>(last - first + 1); //  New parameter values
 
     for (var i = first; i <= last; i++) {
         uPrime[i - first] = NewtonRaphsonRootFind(bezCurve, d[i], u[i - first]);
@@ -76,36 +68,36 @@ function ReparameterizeBezier(d: Point[], first: number, last: number, u: number
 }
 function ComputeMaxError(d: Point[], first: number, last: number, bezCurve: Point[], u: number[]) {
     var maxError = 0; //  Maximum error
-
     var splitPoint2D = (last - first + 1) / 2;
     for (var i = first + 1; i < last; i++) {
-        var P = [0, 0];  //  point on curve
+        const P = [0, 0];  //  point on curve
         EvalBezierFast(bezCurve, u[i - first], P);
-        var dx = P[0] - d[i].X;//  offset from point to curve
-        var dy = P[1] - d[i].Y;
-        var dist = Math.sqrt(dx * dx + dy * dy); //  Current error
+        const dx = P[0] - d[i].X;//  offset from point to curve
+        const dy = P[1] - d[i].Y;
+        const dist = Math.sqrt(dx * dx + dy * dy); //  Current error
         if (dist >= maxError) {
             maxError = dist;
-            if (splitPoint2D)
+            if (splitPoint2D) {
                 splitPoint2D = i;
+            }
         }
     }
     return { maxError, splitPoint2D };
 }
 function ChordLengthParameterize(d: Point[], first: number, last: number) {
-    var u = new Array<number>(last - first + 1);//  Parameterization
+    const u = new Array<number>(last - first + 1);//  Parameterization
 
     var prev = 0.0;
     u[0] = prev;
     for (var i = first + 1; i <= last; i++) {
-        var lastd = d[i - 1];
-        var curd = d[i];
-        var dx = lastd.X - curd.X;
-        var dy = lastd.Y - curd.Y;
+        const lastd = d[i - 1];
+        const curd = d[i];
+        const dx = lastd.X - curd.X;
+        const dy = lastd.Y - curd.Y;
         prev = u[i - first] = prev + Math.sqrt(dx * dx + dy * dy);
     }
 
-    var ulastfirst = u[last - first];
+    const ulastfirst = u[last - first];
     for (var i = first + 1; i <= last; i++) {
         u[i - first] /= ulastfirst;
     }
@@ -116,12 +108,12 @@ function ChordLengthParameterize(d: Point[], first: number, last: number) {
 *  B0, B1, B2, B3 :
 *  Bezier multipliers
 */
-function B0(u: number) { var tmp = 1.0 - u; return tmp * tmp * tmp; }
-function B1(u: number) { var tmp = 1.0 - u; return 3 * u * tmp * tmp; }
-function B2(u: number) { var tmp = 1.0 - u; return 3 * u * u * tmp; }
+function B0(u: number) { const tmp = 1.0 - u; return tmp * tmp * tmp; }
+function B1(u: number) { const tmp = 1.0 - u; return 3 * u * tmp * tmp; }
+function B2(u: number) { const tmp = 1.0 - u; return 3 * u * u * tmp; }
 function B3(u: number) { return u * u * u; }
 function bounds(p: Point[]) {
-    var r = new SmartRect(p[0].X, p[0].Y, p[3].X, p[3].Y); // These are the most likely to be extremal
+    const r = new SmartRect(p[0].X, p[0].Y, p[3].X, p[3].Y); // These are the most likely to be extremal
 
     if (r.minx > r.maxx) (r.minx, r.maxx);
     if (r.miny > r.maxy) [r.miny, r.maxy] = [r.maxy, r.miny]; // swap min & max
@@ -137,10 +129,9 @@ function bounds(p: Point[]) {
 }
 
 
-
 function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
-    var sz = 4;
-    var Vtemp = new Array<Array<Point>>(4);
+    const sz = 4;
+    const Vtemp = new Array<Array<Point>>(4);
     for (var i = 0; i < 4; i++) Vtemp[i] = new Array<Point>(4);
 
     /* Copy control points */
@@ -153,8 +144,8 @@ function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
     /* Triangle computation */
     for (var i = 1; i < sz; i++) {
         for (var j = 0; j < sz - i; j++) {
-            var a = Vtemp[i - 1][j];
-            var b = Vtemp[i - 1][j + 1];
+            const a = Vtemp[i - 1][j];
+            const b = Vtemp[i - 1][j + 1];
             Vtemp[i][j].X = b.X * t + a.X * (1 - t);
             Vtemp[i][j].Y = b.Y * t + a.Y * (1 - t);  // Vtemp[i][j] = Point2D::Lerp(Vtemp[i - 1][j], Vtemp[i - 1][j + 1], t);
         }
@@ -214,14 +205,14 @@ function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
 */
 function recursively_intersect(a: Point[], t0: number, t1: number, deptha: number, b: Point[], u0: number, u1: number, depthb: number, parameters: number[][]) {
     if (deptha > 0) {
-        var a1 = new Array<Point>(4), a2 = new Array<Point>(4);
+        const a1 = new Array<Point>(4), a2 = new Array<Point>(4);
         splitCubic(a, 0.5, a1, a2);
-        var tmid = (t0 + t1) * 0.5;
+        const tmid = (t0 + t1) * 0.5;
         deptha--;
         if (depthb > 0) {
-            var b1 = new Array<Point>(4), b2 = new Array<Point>(4);
+            const b1 = new Array<Point>(4), b2 = new Array<Point>(4);
             splitCubic(b, 0.5, b1, b2);
-            var umid = (u0 + u1) * 0.5;
+            const umid = (u0 + u1) * 0.5;
             depthb--;
             if (SmartRect.Intersect(bounds(a1), bounds(b1))) {
                 recursively_intersect(a1, t0, tmid, deptha, b1, u0, umid, depthb, parameters);
@@ -247,9 +238,9 @@ function recursively_intersect(a: Point[], t0: number, t1: number, deptha: numbe
     }
     else {
         if (depthb > 0) {
-            var b1 = new Array<Point>(4), b2 = new Array<Point>(4);
+            const b1 = new Array<Point>(4), b2 = new Array<Point>(4);
             splitCubic(b, 0.5, b1, b2);
-            var umid = (u0 + u1) * 0.5;
+            const umid = (u0 + u1) * 0.5;
             depthb--;
             if (SmartRect.Intersect(bounds(a), bounds(b1))) {
                 recursively_intersect(a, t0, t1, deptha, b1, u0, umid, depthb, parameters);
@@ -260,20 +251,20 @@ function recursively_intersect(a: Point[], t0: number, t1: number, deptha: numbe
         }
         else // Both segments are fully subdivided; now do line segments
         {
-            var xlk = a[3].X - a[0].X;
-            var ylk = a[3].Y - a[0].Y;
-            var xnm = b[3].X - b[0].X;
-            var ynm = b[3].Y - b[0].Y;
-            var xmk = b[0].X - a[0].X;
-            var ymk = b[0].Y - a[0].Y;
-            var det = xnm * ylk - ynm * xlk;
-            if (1.0 + det == 1.0) {
+            const xlk = a[3].X - a[0].X;
+            const ylk = a[3].Y - a[0].Y;
+            const xnm = b[3].X - b[0].X;
+            const ynm = b[3].Y - b[0].Y;
+            const xmk = b[0].X - a[0].X;
+            const ymk = b[0].Y - a[0].Y;
+            const det = xnm * ylk - ynm * xlk;
+            if (1.0 + det === 1.0) {
                 return;
             }
             else {
-                var detinv = 1.0 / det;
-                var s = (xnm * ymk - ynm * xmk) * detinv;
-                var t = (xlk * ymk - ylk * xmk) * detinv;
+                const detinv = 1.0 / det;
+                const s = (xnm * ymk - ynm * xmk) * detinv;
+                const t = (xlk * ymk - ylk * xmk) * detinv;
                 if ((s < 0.0) || (s > 1.0) || (t < 0.0) || (t > 1.0) || Number.isNaN(s) || Number.isNaN(t)) {
                     return;
                 }
@@ -296,7 +287,7 @@ function EvalBezier(V: Point[], degree: number, t: number, result: number[]) {
         return;
     }
 
-    var Vtemp = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)]; // Local copy of control points
+    const Vtemp = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)]; // Local copy of control points
 
     /* Copy array   */
     for (var i = 0; i <= degree; i++) {
@@ -317,14 +308,11 @@ function EvalBezier(V: Point[], degree: number, t: number, result: number[]) {
 }
 
 function EvalBezierFast(p: Point[], t: number, result: number[]) {
-    var n = 3;
-    var u: number, bc: number, tn: number, tmpX: number, tmpY: number;
-    u = 1.0 - t;
-    bc = 1;
-    tn = 1;
-
-    tmpX = p[0].X * u;
-    tmpY = p[0].Y * u;
+    const n = 3;
+    const u = 1.0 - t;
+    var bc = 1, tn = 1;
+    var tmpX = p[0].X * u;
+    var tmpY = p[0].Y * u;
     tn = tn * t;
     bc = bc * (n - 1 + 1) / 1;
     tmpX = (tmpX + tn * bc * p[1].X) * u;
@@ -342,22 +330,21 @@ function EvalBezierFast(p: Point[], t: number, result: number[]) {
 *Approximate unit tangents at endpoints and "center" of digitized curve
 */
 function ComputeLeftTangent(d: Point[], end: number) {
-    var use = 1;
-    var tHat1 = new Point(d[end + use].X - d[end].X, d[end + use].Y - d[end].Y);
+    const use = 1;
+    const tHat1 = new Point(d[end + use].X - d[end].X, d[end + use].Y - d[end].Y);
     return Normalize(tHat1);
 }
 function ComputeRightTangent(d: Point[], end: number) {
-    var available = end;
-    var use = 1;
-    var tHat2 = new Point(d[end - use].X - d[end].X, d[end - use].Y - d[end].Y);
+    const use = 1;
+    const tHat2 = new Point(d[end - use].X - d[end].X, d[end - use].Y - d[end].Y);
     return Normalize(tHat2);
 }
 function ComputeCenterTangent(d: Point[], center: number) {
-    if (center == 0) {
+    if (center === 0) {
         return ComputeLeftTangent(d, center);
     }
-    var V1 = ComputeLeftTangent(d, center); // d[center] - d[center-1];
-    var V2 = ComputeRightTangent(d, center); // d[center] - d[center + 1];
+    const V1 = ComputeLeftTangent(d, center); // d[center] - d[center-1];
+    const V2 = ComputeRightTangent(d, center); // d[center] - d[center + 1];
     var tHatCenter = new Point((-V1.X + V2.X) / 2.0, (-V1.Y + V2.Y) / 2.0);
     if (tHatCenter === new Point(0, 0)) {
         tHatCenter = new Point(-V1.Y, -V1.X);// V1.Perp();
@@ -365,15 +352,15 @@ function ComputeCenterTangent(d: Point[], center: number) {
     return Normalize(tHatCenter);
 }
 function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[], tHat1: Point, tHat2: Point, result: Point[] /* must be prealloacted to size 4 */) {
-    var nPts = last - first + 1; // Number of pts in sub-curve
-    var Ax = new Array<number>(nPts * 2);// Precomputed rhs for eqn  //std::vector<Vector2D> A(nPts * 2);
-    var Ay = new Array<number>(nPts * 2);// Precomputed rhs for eqn  //std::vector<Vector2D> A(nPts * 2);
+    const nPts = last - first + 1; // Number of pts in sub-curve
+    const Ax = new Array<number>(nPts * 2);// Precomputed rhs for eqn  //std::vector<Vector2D> A(nPts * 2);
+    const Ay = new Array<number>(nPts * 2);// Precomputed rhs for eqn  //std::vector<Vector2D> A(nPts * 2);
 
     /* Compute the A's  */
     for (var i = 0; i < nPts; i++) {
-        var uprime = uPrime[i];
-        var b1 = B1(uprime);
-        var b2 = B2(uprime);
+        const uprime = uPrime[i];
+        const b1 = B1(uprime);
+        const b2 = B2(uprime);
         Ax[i] = tHat1.X * b1;
         Ay[i] = tHat1.Y * b1;
         Ax[i + 1 * nPts] = tHat2.X * b2;
@@ -381,44 +368,41 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
     }
 
     /* Create the C and X matrices  */
-    var C = [[0, 0], [0, 0]];
-    var df = d[first];
-    var dl = d[last];
+    const C = [[0, 0], [0, 0]];
+    const df = d[first];
+    const dl = d[last];
 
-    var X = [0, 0];    // Matrix X
+    const X = [0, 0];    // Matrix X
     for (var i = 0; i < nPts; i++) {
         C[0][0] += Ax[i] * Ax[i] + Ay[i] * Ay[i];     //A[i+0*nPts].Dot(A[i+0*nPts]);
         C[0][1] += Ax[i] * Ax[i + nPts] + Ay[i] * Ay[i + nPts];//A[i+0*nPts].Dot(A[i+1*nPts]);
         C[1][0] = C[0][1];
         C[1][1] += Ax[i + nPts] * Ax[i + nPts] + Ay[i + nPts] * Ay[i + nPts];// A[i+1*nPts].Dot(A[i+1*nPts]);
-        var uprime = uPrime[i];
-        var b0plb1 = B0(uprime) + B1(uprime);
-        var b2plb3 = B2(uprime) + B3(uprime);
-        var df1 = d[first + i];
-        var tmpX = df1.X - (df.X * b0plb1 + (dl.X * b2plb3));
-        var tmpY = df1.Y - (df.Y * b0plb1 + (dl.Y * b2plb3));
+        const uprime = uPrime[i];
+        const b0plb1 = B0(uprime) + B1(uprime);
+        const b2plb3 = B2(uprime) + B3(uprime);
+        const df1 = d[first + i];
+        const tmpX = df1.X - (df.X * b0plb1 + (dl.X * b2plb3));
+        const tmpY = df1.Y - (df.Y * b0plb1 + (dl.Y * b2plb3));
 
         X[0] += Ax[i] * tmpX + Ay[i] * tmpY; // A[i+0*nPts].Dot(tmp)
         X[1] += Ax[i + nPts] * tmpX + Ay[i + nPts] * tmpY; //A[i+1*nPts].Dot(tmp)
     }
 
     /* Compute the determinants of C and X  */
-    var det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
-    var det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
-    var det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
+    const det_C0_C1 = (C[0][0] * C[1][1] - C[1][0] * C[0][1]) || (C[0][0] * C[1][1]) * 10e-12;
+    const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
+    const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
 
     /* Finally, derive alpha values */
-    if (det_C0_C1 == 0.0) {
-        det_C0_C1 = (C[0][0] * C[1][1]) * 10e-12;
-    }
-    var alpha_l = (det_C0_C1 == 0) ? 0.0 : det_X_C1 / det_C0_C1;
-    var alpha_r = (det_C0_C1 == 0) ? 0.0 : det_C0_X / det_C0_C1;
+    var alpha_l = (det_C0_C1 === 0) ? 0.0 : det_X_C1 / det_C0_C1;
+    var alpha_r = (det_C0_C1 === 0) ? 0.0 : det_C0_X / det_C0_C1;
 
     /* If alpha negative, use the Wu/Barsky heuristic (see text) */
     /* (if alpha is 0, you get coincident control points that lead to
     * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
-    var segLength = Math.sqrt((df.X - dl.X) * (df.X - dl.X) + (df.Y - dl.Y) * (df.Y - dl.Y));
-    var epsilon = 1.0e-6 * segLength;
+    const segLength = Math.sqrt((df.X - dl.X) * (df.X - dl.X) + (df.Y - dl.Y) * (df.Y - dl.Y));
+    const epsilon = 1.0e-6 * segLength;
     if (alpha_l < epsilon || alpha_r < epsilon) {
         /* fall back on standard (probably inaccurate) formula, and subdivide further if needed. */
         alpha_l = alpha_r = segLength / 3.0;
@@ -432,15 +416,15 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
     result[3] = dl;
     result[1] = new Point(df.X + (tHat1.X * alpha_l), df.Y + (tHat1.Y * alpha_l));
     result[2] = new Point(dl.X + (tHat2.X * alpha_r), dl.Y + (tHat2.Y * alpha_r));
-
 }
+
 /*
  *  NewtonRaphsonRootFind :
  *  Use Newton-Raphson iteration to find better root.
  */
 function NewtonRaphsonRootFind(Q: Point[], P: Point, u: number) {
-    var Q1 = [new Point(0, 0), new Point(0, 0), new Point(0, 0)], Q2 = [new Point(0, 0), new Point(0, 0)]; //  Q' and Q''
-    var Q_u = [0, 0], Q1_u = [0, 0], Q2_u = [0, 0]; //u evaluated at Q, Q', & Q''
+    const Q1 = [new Point(0, 0), new Point(0, 0), new Point(0, 0)], Q2 = [new Point(0, 0), new Point(0, 0)]; //  Q' and Q''
+    const Q_u = [0, 0], Q1_u = [0, 0], Q2_u = [0, 0]; //u evaluated at Q, Q', & Q''
 
     /* Compute Q(u) */
     var uPrime: number; //  Improved u
@@ -463,25 +447,24 @@ function NewtonRaphsonRootFind(Q: Point[], P: Point, u: number) {
     EvalBezier(Q2, 1, u, Q2_u);
 
     /* Compute f(u)/f'(u) */
-    var numerator = (Q_u[0] - P.X) * (Q1_u[0]) + (Q_u[1] - P.Y) * (Q1_u[1]);
-    var denominator = (Q1_u[0]) * (Q1_u[0]) + (Q1_u[1]) * (Q1_u[1]) + (Q_u[0] - P.X) * (Q2_u[0]) + (Q_u[1] - P.Y) * (Q2_u[1]);
-    if (denominator == 0.0)
+    const numerator = (Q_u[0] - P.X) * (Q1_u[0]) + (Q_u[1] - P.Y) * (Q1_u[1]);
+    const denominator = (Q1_u[0]) * (Q1_u[0]) + (Q1_u[1]) * (Q1_u[1]) + (Q_u[0] - P.X) * (Q2_u[0]) + (Q_u[1] - P.Y) * (Q2_u[1]);
+    if (denominator === 0.0) {
         uPrime = u;
-    else uPrime = u - (numerator / denominator);/* u = u - f(u)/f'(u) */
+    } else uPrime = u - (numerator / denominator);/* u = u - f(u)/f'(u) */
 
     return uPrime;
 }
 function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2: Point, error: number, result: Point[]) {
-    var bezCurve = new Array<Point>(4);          //  Control points of fitted Bezier curve
-    var splitPoint2D: number;           //  Point2D to split point set at
-    var maxIterations = 4;      //  Max times to try iterating
+    const bezCurve = new Array<Point>(4);          //  Control points of fitted Bezier curve
+    const maxIterations = 4;      //  Max times to try iterating
 
-    var iterationError = error * error; //  Error below which you try iterating
-    var nPts = last - first + 1;        //  Number of points in subset
+    const iterationError = error * error; //  Error below which you try iterating
+    const nPts = last - first + 1;        //  Number of points in subset
 
     /*  Use heuristic if region only has two points in it */
-    if (nPts == 2) {
-        var dist = Math.sqrt((d[first].X - d[last].X) * (d[first].X - d[last].X) + (d[first].Y - d[last].Y) * (d[first].Y - d[last].Y)) / 3;
+    if (nPts === 2) {
+        const dist = Math.sqrt((d[first].X - d[last].X) * (d[first].X - d[last].X) + (d[first].Y - d[last].Y) * (d[first].Y - d[last].Y)) / 3;
 
         bezCurve[0] = d[first];
         bezCurve[3] = d[last];
@@ -499,7 +482,7 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
     GenerateBezier(d, first, last, u, tHat1, tHat2, bezCurve);
 
     /*  Find max deviation of points to fitted curve */
-    var { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, u);  //  Maximum fitting error
+    const { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, u);  //  Maximum fitting error
     if (maxError < Math.abs(error)) {
         result.push(bezCurve[1]);
         result.push(bezCurve[2]);
@@ -511,9 +494,9 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
     /*  and iteration */
     if (maxError < iterationError) {
         for (var i = 0; i < maxIterations; i++) {
-            var uPrime = ReparameterizeBezier(d, first, last, u, bezCurve);   //  Improved parameter values
+            const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve);   //  Improved parameter values
             GenerateBezier(d, first, last, uPrime, tHat1, tHat2, bezCurve);
-            var { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, uPrime);
+            const { maxError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
             if (maxError < error) {
                 result.push(bezCurve[1]);
                 result.push(bezCurve[2]);
@@ -525,16 +508,15 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
     }
 
     /* Fitting failed -- split at max error point and fit recursively */
-    var tHatCenter = splitPoint2D >= last - 1 ? ComputeRightTangent(d, splitPoint2D) : ComputeCenterTangent(d, splitPoint2D);
+    const tHatCenter = splitPoint2D >= last - 1 ? ComputeRightTangent(d, splitPoint2D) : ComputeCenterTangent(d, splitPoint2D);
     FitCubic(d, first, splitPoint2D, tHat1, tHatCenter, error, result);
-    var negThatCenter = new Point(-tHatCenter.X, -tHatCenter.Y);
+    const negThatCenter = new Point(-tHatCenter.X, -tHatCenter.Y);
     FitCubic(d, splitPoint2D, last, negThatCenter, tHat2, error, result);
 }
 export function FitCurve(d: Point[], error: number) {
-    var tHat1 = ComputeLeftTangent(d, 0); //  Unit tangent vectors at endpoints
-    var tHat2 = ComputeRightTangent(d, d.length - 1);
-    var result: Point[] = [];
-    result.push(d[0]);
+    const tHat1 = ComputeLeftTangent(d, 0); //  Unit tangent vectors at endpoints
+    const tHat2 = ComputeRightTangent(d, d.length - 1);
+    const result = [d[0]];
     FitCubic(d, 0, d.length - 1, tHat1, tHat2, error, result);
     return result;
 }
@@ -543,14 +525,14 @@ export function FitOneCurve(d: Point[], tHat1?: Point, tHat2?: Point) {
     tHat2 = tHat2 ?? Normalize(ComputeRightTangent(d, d.length - 1));
     tHat2 = new Point(-tHat2.X, -tHat2.Y);
     var u = ChordLengthParameterize(d, 0, d.length - 1);
-    var bezCurveCtrls = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)];
+    const bezCurveCtrls = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)];
     GenerateBezier(d, 0, d.length - 1, u, tHat1, tHat2, bezCurveCtrls);    /*  Find max deviation of points to fitted curve */
     var finalCtrls = bezCurveCtrls.slice();
     var { maxError: error } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, u);
     for (var i = 0; i < 10; i++) {
-        var uPrime = ReparameterizeBezier(d, 0, d.length - 1, u, bezCurveCtrls);   //  Improved parameter values
+        const uPrime = ReparameterizeBezier(d, 0, d.length - 1, u, bezCurveCtrls);   //  Improved parameter values
         GenerateBezier(d, 0, d.length - 1, uPrime, tHat1, tHat2, bezCurveCtrls);
-        var { maxError } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, uPrime);
+        const { maxError } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, uPrime);
         if (maxError < error) {
             error = maxError;
             finalCtrls = bezCurveCtrls.slice();
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 1c0b1b995..7d3959eba 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -203,9 +203,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                 const centerPoint = { X: (left + right) / 2, Y: (top + bottom) / 2 };
                 const previousPoint = { X: e.clientX, Y: e.clientY };
                 const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] };
-                const angle = InkStrokeProperties.Instance?.angleChange(previousPoint, movedPoint, centerPoint);
+                const angle = InkStrokeProperties.Instance.angleChange(previousPoint, movedPoint, centerPoint);
                 const selectedInk = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK);
-                angle && InkStrokeProperties.Instance?.rotateInk(selectedInk, -angle, pt);
+                angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, pt);
                 return false;
             },
             () => {
@@ -226,7 +226,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         this._inkDragDocs = DragManager.docsBeingDragged
             .filter(doc => doc.type === DocumentType.INK)
             .map(doc => {
-                if (InkStrokeProperties.Instance?._lock) {
+                if (InkStrokeProperties.Instance._lock) {
                     Doc.SetNativeHeight(doc, NumCast(doc._height));
                     Doc.SetNativeWidth(doc, NumCast(doc._width));
                 }
@@ -249,7 +249,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         const first = SelectionManager.Views()[0];
         let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY };
         var fixedAspect = Doc.NativeAspect(first.layoutDoc);
-        InkStrokeProperties.Instance?._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
+        InkStrokeProperties.Instance._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
             .forEach(dv => fixedAspect = Doc.NativeAspect(dv.rootDoc));
 
         const resizeHdl = this._resizeHdlId.split(" ")[0];
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 364bf05e2..d5e0ed962 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -112,7 +112,7 @@ export class KeyManager {
             case "escape":
                 DocumentLinksButton.StartLink = undefined;
                 DocumentLinksButton.StartLinkView = undefined;
-                InkStrokeProperties.Instance && (InkStrokeProperties.Instance._controlButton = false);
+                InkStrokeProperties.Instance._controlButton = false;
                 CurrentUserUtils.SelectedTool = InkTool.None;
                 var doDeselect = true;
                 if (SnappingManager.GetIsDragging()) {
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index a91e74c44..76ce73b0d 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -44,23 +44,23 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
     @action
     onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
         const ptFromScreen = this.props.inkView.ComponentView?.ptFromScreen;
-        if (InkStrokeProperties.Instance && ptFromScreen) {
+        if (ptFromScreen) {
             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"));
-            const wasSelected = InkStrokeProperties.Instance?._currentPoint === controlIndex;
+            const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
             setupMoveUpEvents(this, e,
                 action((e: PointerEvent, down: number[], delta: number[]) => {
                     if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
                     const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
                     const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
-                    InkStrokeProperties.Instance?.moveControlPtHandle(this.props.inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
+                    InkStrokeProperties.Instance.moveControlPtHandle(this.props.inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
                     return false;
                 }),
                 action(() => {
                     if (this.controlUndo) {
-                        InkStrokeProperties.Instance?.snapControl(this.props.inkView, controlIndex);
+                        InkStrokeProperties.Instance.snapControl(this.props.inkView, controlIndex);
                     }
                     this.controlUndo?.end();
                     this.controlUndo = undefined;
@@ -75,11 +75,11 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
                         } else {
                             if (brokenIndices?.includes(equivIndex)) {
                                 if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth");
-                                InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, equivIndex, handleIndexA, handleIndexB);
+                                InkStrokeProperties.Instance.snapHandleTangent(this.props.inkView, equivIndex, handleIndexA, handleIndexB);
                             }
                             if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
                                 if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth");
-                                InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, controlIndex, handleIndexA, handleIndexB);
+                                InkStrokeProperties.Instance.snapHandleTangent(this.props.inkView, controlIndex, handleIndexA, handleIndexB);
                             }
                         }
                         this.controlUndo?.end();
@@ -102,7 +102,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
     @action
     onDelete = (e: KeyboardEvent) => {
         if (["-", "Backspace", "Delete"].includes(e.key)) {
-            InkStrokeProperties.Instance?.deletePoints(this.props.inkView, e.shiftKey);
+            InkStrokeProperties.Instance.deletePoints(this.props.inkView, e.shiftKey);
             e.stopPropagation();
         }
     }
@@ -111,11 +111,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
      * Changes the current selected control point.
      */
     @action
-    changeCurrPoint = (i: number) => {
-        if (InkStrokeProperties.Instance) {
-            InkStrokeProperties.Instance._currentPoint = i;
-        }
-    }
+    changeCurrPoint = (i: number) => InkStrokeProperties.Instance._currentPoint = i
 
     render() {
         // Accessing the current ink's data and extracting all control points.
@@ -202,9 +198,9 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
             const scaling = v2len / v1len;
             const v1n = { x: v1.x / v1len, y: v1.y / v1len };
             const v2n = { x: v2.x / v2len, y: v2.y / v2len };
-            const angle = Math.acos(v1n.x * v2n.x + v1n.y * v2n.y) * Math.sign(v1.x * v2.y - v2.x * v1.y)
-            InkStrokeProperties.Instance?.stretchInk([this.props.inkView], scaling, { x: p2().X, y: p2().Y }, v1n);
-            InkStrokeProperties.Instance?.rotateInk([this.props.inkView], angle, { x: p2().X, y: p2().Y });
+            const angle = Math.acos(v1n.x * v2n.x + v1n.y * v2n.y) * Math.sign(v1.x * v2.y - v2.x * v1.y);
+            InkStrokeProperties.Instance.stretchInk([this.props.inkView], scaling, { x: p2().X, y: p2().Y }, v1n);
+            InkStrokeProperties.Instance.rotateInk([this.props.inkView], angle, { x: p2().X, y: p2().Y });
             return false;
         }, action(() => {
             this.controlUndo?.end();
@@ -214,7 +210,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
     }
 
     render() {
-        const hdl = (pt: PointData, dragFunc: (e: React.PointerEvent) => void) => <circle key={"npt"}
+        const hdl = (key: string, pt: PointData, dragFunc: (e: React.PointerEvent) => void) => <circle key={key}
             cx={pt.X}
             cy={pt.Y}
             r={this.props.screenSpaceLineWidth * 2}
@@ -223,12 +219,12 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
             strokeWidth={0}
             onPointerLeave={action(() => this._overStart = false)}
             onPointerEnter={action(() => this._overStart = true)}
-            onPointerDown={e => dragFunc(e)}
+            onPointerDown={dragFunc}
             pointerEvents="all"
         />;
         return (<svg>
-            {hdl(this.props.startPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.startPt, () => this.props.endPt))}
-            {hdl(this.props.endPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.endPt, () => this.props.startPt))}
+            {hdl("start", this.props.startPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.startPt, () => this.props.endPt))}
+            {hdl("end", this.props.endPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.endPt, () => this.props.startPt))}
         </svg>
         );
     }
diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss
index 2127826b4..664f2448b 100644
--- a/src/client/views/InkStroke.scss
+++ b/src/client/views/InkStroke.scss
@@ -14,6 +14,9 @@
 }
 
 .inkStroke-wrapper {
+    display: flex;
+    align-items: center;
+    height: 100%;
     .inkStroke {
         mix-blend-mode: multiply;
         stroke-linejoin: round;
@@ -22,7 +25,7 @@
         transform-origin: top left;
         width: 100%;
         height: 100%;
-
+        pointer-events: none;
         svg:not(:root) {
             overflow: visible !important;
         }
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 4808bbc77..7ab631b03 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -15,14 +15,15 @@ import { InkingStroke } from "./InkingStroke";
 import { DocumentView } from "./nodes/DocumentView";
 
 export class InkStrokeProperties {
-    static Instance: InkStrokeProperties | undefined;
+    static _Instance: InkStrokeProperties | undefined;
+    public static get Instance() { return this._Instance || new InkStrokeProperties(); }
 
     @observable _lock = false;
     @observable _controlButton = false;
     @observable _currentPoint = -1;
 
     constructor() {
-        InkStrokeProperties.Instance = this;
+        InkStrokeProperties._Instance = this;
         reaction(() => this._controlButton, button => button && (CurrentUserUtils.SelectedTool = InkTool.None));
         reaction(() => CurrentUserUtils.SelectedTool, tool => (tool !== InkTool.None) && (this._controlButton = false));
     }
@@ -150,15 +151,15 @@ export class InkStrokeProperties {
         } else {
             const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
             const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
-            var samples: Point[] = [];
+            const samples: Point[] = [];
             var startDir = { x: 0, y: 0 };
             var endDir = { x: 0, y: 0 };
             for (var i = 0; i < splicedPoints.length / 4; i++) {
-                var bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
+                const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
                 if (i === 0) startDir = bez.derivative(0);
                 if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
                 for (var t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) {
-                    var pt = bez.compute(t);
+                    const pt = bez.compute(t);
                     samples.push(new Point(pt.x, pt.y));
                 }
             }
@@ -209,12 +210,12 @@ export class InkStrokeProperties {
             const ptFromScreen = view.ComponentView?.ptFromScreen;
             const ptToScreen = view.ComponentView?.ptToScreen;
             return !ptToScreen || !ptFromScreen ? ink :
-                ink.map(i => ptToScreen(i)).map(i => {
+                ink.map(ptToScreen).map(i => {
                     const pvec = { X: i.X - scrpt.x, Y: i.Y - scrpt.y };
                     const svec = pvec.X * scrVec.x * scaling + pvec.Y * scrVec.y * scaling;
                     const ovec = -pvec.X * scrVec.y + pvec.Y * (scrVec.x);
                     const newscrpt = { X: scrpt.x + svec * scrVec.x - ovec * scrVec.y, Y: scrpt.y + svec * scrVec.y + ovec * scrVec.x };
-                    const newpt = ptFromScreen!(newscrpt);
+                    const newpt = ptFromScreen(newscrpt);
                     return newpt;
                 });
         });
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index f88a20448..ab73e58a4 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -29,24 +29,23 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
      * @param handleNum The index of the currently selected handle point.
      */
     onHandleDown = (e: React.PointerEvent, handleIndex: number): void => {
-        if (InkStrokeProperties.Instance) {
-            var controlUndo: UndoManager.Batch | undefined;
-            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[]) => {
+        var controlUndo: UndoManager.Batch | undefined;
+        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 (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent");
                 if (e.altKey) this.onBreakTangent(controlIndex);
-                InkStrokeProperties.Instance?.moveTangentHandle(this.props.inkView, -delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
+                InkStrokeProperties.Instance.moveTangentHandle(this.props.inkView, -delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
                 return false;
             }, () => {
                 controlUndo?.end();
                 UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
             }, emptyFunction
-            );
-        }
+        );
     }
 
     /**
@@ -66,9 +65,6 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
     }
 
     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 tangentHandles: HandlePoint[] = [];
@@ -107,7 +103,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
                             onPointerDown={e => this.onHandleDown(e, pts.I)}
                             pointerEvents="all"
                             cursor="default"
-                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
+                            display={(pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint) ? "inherit" : "none"} />
                     </svg>)}
                 {tangentLines.map((pts, i) => {
                     const tangentLine = (x1: number, y1: number, x2: number, y2: number) =>
@@ -119,7 +115,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
                             stroke={Colors.MEDIUM_BLUE}
                             strokeDasharray={"1 1"}
                             strokeWidth={1}
-                            display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />;
+                            display={(pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._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)}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 8ff080f81..0b3619b22 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,3 +1,25 @@
+/*
+    InkingStroke - a document that represents an individual vector stroke drawn as a Bezier curve (open or closed) and optionally filled.
+
+    The primary data is:
+        data - an InkField which is an array of PointData (X,Y values).  The data is laid out as a sequence of simple bezier segments:
+              point 1, tangent pt 1, tangent pt 2, point 2, point 3, tangent pt 3, ...  (Note that segment endpoints are duplicated ie Point2 = Point 3)
+        brokenIndices - an array of indexes into the data field where the incoming and outgoing tangents are not constrained to be equal
+        text - a text field that will be centered within a closed ink stroke
+        isInkMask - a flag that makes the ink stroke render as a mask over its collection where the stroke itself is mixBlendMode multiplied by 
+               the underlying collection content, and everything outside the stroke is covered by a semi-opaque dark gray mask.
+
+    The coordinates of the ink data need to be mapped to the screen since ink points are not changed when the DocumentView is translated or scaled.
+        Thus the mapping can roughly be described by:
+        the Top/Left of the ink data (minus 1/2 the ink width) maps to the Top/Left of the DocumentView
+        the Width/Height of the ink data (minus the ink width) is scaled to the PanelWidth/PanelHeight of the documentView
+        NOTE: use ptToScreen() and ptFromScreen() to transform between ink and screen space
+
+    InkStrokes have a specialized 'componentUI' method that is called by MainView to render all of the interactive editing controls in 
+        screen space (to avoid scaling artifacts)
+    
+    Most of the operations that can be performed on an InkStroke (eg delete a point, rotate, stretch) are implemented in the InkStrokeProperties helper class
+*/
 import React = require("react");
 import { action, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
@@ -30,35 +52,32 @@ const InkDocument = makeInterface(documentSchema);
 
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
+    static readonly MaskDim = 50000; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
-    static readonly MaskDim = 50000;
     public static IsClosed(inkData: InkData) {
         return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
     }
-    @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;
+    private _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated
+    private _selDisposer?: IReactionDisposer;
 
-    @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;
-    }
+    @observable _nearestSeg?: number; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight)
+    @observable _nearestT?: number;  // nearest t value within the nearest Bezier segment "
+    @observable _nearestScrPt?: { X: number, Y: number }; // nearst screen point on the ink stroke ""
 
     componentDidMount() {
         this.props.setContentView?.(this);
         this._selDisposer = reaction(() => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
-            selected => !selected && this.toggleControlButton());
+            selected => !selected && (InkStrokeProperties.Instance._controlButton = false));
     }
     componentWillUnmount() {
         this._selDisposer?.();
     }
 
+    /**
+    * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space); 
+    * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since
+    * the center of the ink stroke changes as the stroke is rotated.
+    */
     getCenter = (xf: Transform) => {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const angle = -NumCast(this.layoutDoc.rotation);
@@ -80,6 +99,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
     }
 
+    /**
+     * Toggles whether the ink stroke is displayed as an overlay mask or as a regular stroke.
+     *    When displayed as a mask, the stroke is rendered with mixBlendMode set to multiply so that the stroke will
+     *  appear to illuminate what it covers up.  At the same time, all pixels that are not under the stroke will be
+     *  dimmed by a semi-opaque overlay mask.
+    */
     public static toggleMask = action((inkDoc: Doc) => {
         inkDoc.isInkMask = !inkDoc.isInkMask;
         inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
@@ -88,70 +113,47 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
     });
     /**
-     * Handles the movement of the entire ink object when the user clicks and drags.
+     * Drags the a simple bezier segment of the stroke.
+     * Also adds a control point when double clicking on the stroke.
      */
     @action
     onPointerDown = (e: React.PointerEvent) => {
-        const ptFromScreen = this.ptFromScreen;
         this._handledClick = false;
-        if (InkStrokeProperties.Instance && ptFromScreen) {
-            const inkView = this.props.docViewPath().lastElement();
-            const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
-            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 { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
-            const controlIndex = nearestSeg;
-            const wasSelected = InkStrokeProperties.Instance?._currentPoint === controlIndex;
-            var controlUndo: UndoManager.Batch | undefined;
-            const isEditing = this._properties?._controlButton && this.props.isSelected();
-            setupMoveUpEvents(this, e,
-                !isEditing ? returnFalse : action((e: PointerEvent, down: number[], delta: number[]) => {
-                    if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
-                    const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
-                    const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
-                    InkStrokeProperties.Instance?.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
-                    InkStrokeProperties.Instance?.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3);
-                    return false;
-                }),
-                !isEditing ? returnFalse : action(() => {
-                    if (controlUndo) {
-                        InkStrokeProperties.Instance?.snapControl(inkView, controlIndex);
-                        InkStrokeProperties.Instance?.snapControl(inkView, controlIndex + 3);
-                    }
-                    controlUndo?.end();
-                    controlUndo = undefined;
-                    UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
-                }),
-                action((e: PointerEvent, doubleTap: boolean | undefined) => {
-                    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
-                    } else if (isEditing) {
-                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance?.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
+        const inkView = this.props.docViewPath().lastElement();
+        const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
+        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 { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
+        const controlIndex = nearestSeg;
+        const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
+        var controlUndo: UndoManager.Batch | undefined;
+        const isEditing = InkStrokeProperties.Instance._controlButton && this.props.isSelected();
+        setupMoveUpEvents(this, e,
+            !isEditing ? returnFalse : action((e: PointerEvent, down: number[], delta: number[]) => {
+                if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
+                const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] });
+                const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 });
+                InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
+                InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3);
+                return false;
+            }),
+            !isEditing ? returnFalse : action(() => {
+                controlUndo?.end();
+                controlUndo = undefined;
+                UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
+            }),
+            action((e: PointerEvent, doubleTap: boolean | undefined) => {
+                doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
+                if (doubleTap) {
+                    InkStrokeProperties.Instance._controlButton = true;
+                    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
+                    if (isEditing) {
+                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
                     }
-                }), isEditing, isEditing, action(() => wasSelected && InkStrokeProperties.Instance && (InkStrokeProperties.Instance._currentPoint = -1)));
-        }
-    }
-
-    /**
-     * Ensures the ink controls and handles aren't rendered when the current ink stroke is reselected.
-     */
-    @action
-    toggleControlButton = () => {
-        if (!this.props.isSelected() && this._properties) {
-            this._properties._controlButton = false;
-        }
-    }
-
-    @action
-    checkHighlighter = () => {
-        if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) {
-            // this._previousColor = ActiveInkColor();
-            SetActiveInkColor("rgba(245, 230, 95, 0.75)");
-        }
+                }
+            }), isEditing, isEditing, action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1)));
     }
 
     ptFromScreen = (scrPt: { X: number, Y: number }) => {
@@ -173,13 +175,23 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         return { X: scrPt[0], Y: scrPt[1] };
     }
 
+    /**
+    * Snaps a screen space point to this stroke, optionally skipping bezier segments indicated by 'excludeSegs'
+    * @param scrPt - the point to snap to this stroke
+    * @param excludeSegs - optional segments in this stroke to skip (this is used when dragging a point on the stroke and not wanting the drag point to snap to its neighboring segments)
+    *
+    * @returns the nearest ink space point on this stroke to the screen point AND the screen space distance from the snapped point to the nearest point
+    */
     snapPt = (scrPt: { X: number, Y: number }, excludeSegs?: number[]) => {
         const { inkData } = this.inkScaledData();
-        const inkPt = this.ptFromScreen(scrPt);
-        const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, inkPt, excludeSegs ?? []);
+        const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, this.ptFromScreen(scrPt), excludeSegs ?? []);
         return { nearestPt, distance: distance * this.props.ScreenToLocalTransform().inverse().Scale };
     }
 
+    /**
+    * extracts key features from the inkData, including: the data points, the ink width, the ink bounds (top,left, width, height), and the scale
+    * factor for converting between ink and screen space.
+    */
     inkScaledData = () => {
         const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
         const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1);
@@ -229,7 +241,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const startMarker = StrCast(this.layoutDoc.strokeStartMarker);
         const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
         return SnappingManager.GetIsDragging() ? (null) :
-            !this._properties?._controlButton ?
+            !InkStrokeProperties.Instance._controlButton ?
                 (!this.props.isSelected() || InkingStroke.IsClosed(inkData) ? (null) :
                     <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
                         <InkEndPtHandles
@@ -287,15 +299,11 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             false, downHdlr);
         // Set of points rendered upon the ink that can be added if a user clicks on one.
 
-        return <div className="inkStroke-wrapper" style={{ display: "flex", alignItems: "center", height: "100%" }}>
+        return <div className="inkStroke-wrapper">
             <svg className="inkStroke"
                 style={{
-                    width: "100%",
-                    height: "100%",
-                    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",
                     cursor: this.props.isSelected() ? "default" : undefined
                 }}
                 onPointerLeave={action(e => this._nearestScrPt = undefined)}
@@ -305,7 +313,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     const cm = ContextMenu.Instance;
                     !Doc.UserDoc().noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
                     cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
-                    cm?.addItem({ description: "Edit Points", event: action(() => { if (this._properties) { this._properties._controlButton = !this._properties._controlButton; } }), icon: "paint-brush" });
+                    cm?.addItem({ description: "Edit Points", event: action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" });
                 }}
             >
                 {clickableLine(this.onPointerDown)}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 546b0e360..a9fea4a78 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -116,7 +116,6 @@ export class MainView extends React.Component {
             }, 0);
             setTimeout(() => ele.outerHTML = '', 1000);
         }
-        new InkStrokeProperties();
         this._sidebarContent.proto = undefined;
         if (!MainView.Live) {
             DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition",
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 1083e0075..18d5f1642 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -535,16 +535,17 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @computed
     get controlPointsButton() {
-        const formatInstance = InkStrokeProperties.Instance;
-        return !formatInstance ? (null) : <div className="inking-button">
+        return <div className="inking-button">
             <Tooltip title={<div className="dash-tooltip">{"Edit points"}</div>}>
-                <div className="inking-button-points" onPointerDown={action(() => formatInstance._controlButton = !formatInstance._controlButton)} style={{ backgroundColor: formatInstance._controlButton ? "black" : "" }}>
+                <div className="inking-button-points"
+                    style={{ backgroundColor: InkStrokeProperties.Instance._controlButton ? "black" : "" }}
+                    onPointerDown={action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton)} >
                     <FontAwesomeIcon icon="bezier-curve" color="white" size="lg" />
                 </div>
             </Tooltip>
-            <Tooltip title={<div className="dash-tooltip">{formatInstance._lock ? "Unlock ratio" : "Lock ratio"}</div>}>
-                <div className="inking-button-lock" onPointerDown={action(() => formatInstance._lock = !formatInstance._lock)} >
-                    <FontAwesomeIcon icon={formatInstance._lock ? "lock" : "unlock"} color="white" size="lg" />
+            <Tooltip title={<div className="dash-tooltip">{InkStrokeProperties.Instance._lock ? "Unlock ratio" : "Lock ratio"}</div>}>
+                <div className="inking-button-lock" onPointerDown={action(() => InkStrokeProperties.Instance._lock = !InkStrokeProperties.Instance._lock)} >
+                    <FontAwesomeIcon icon={InkStrokeProperties.Instance._lock ? "lock" : "unlock"} color="white" size="lg" />
                 </div>
             </Tooltip>
             <Tooltip title={<div className="dash-tooltip">{"Rotate 90˚"}</div>}>
@@ -603,7 +604,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 const oldX = NumCast(this.selectedDoc?.x);
                 const oldY = NumCast(this.selectedDoc?.y);
                 this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === "up" ? 10 : - 10));
-                InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height)));
+                InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height)));
                 const doc = this.selectedDoc;
                 if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
                     const ink = Cast(doc.data, InkField)?.inkData;
@@ -625,7 +626,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 const oX = NumCast(this.selectedDoc?.x);
                 const oY = NumCast(this.selectedDoc?.y);
                 this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === "up" ? 10 : - 10));
-                InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width)));
+                InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width)));
                 const docu = this.selectedDoc;
                 if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) {
                     const ink = Cast(docu.data, InkField)?.inkData;
@@ -663,12 +664,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     set shapeWid(value) {
         const oldWidth = NumCast(this.selectedDoc?._width);
         this.selectedDoc && (this.selectedDoc._width = Number(value));
-        InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth);
+        InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth);
     }
     set shapeHgt(value) {
         const oldHeight = NumCast(this.selectedDoc?._height);
         this.selectedDoc && (this.selectedDoc._height = Number(value));
-        InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight);
+        InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight);
     }
 
     @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; }, "H:", "wid", this.shapeWid, (val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; }, "W:"); }
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 8d8630907..f09d532ad 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -93,7 +93,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
         case StyleProp.HideLinkButton: return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
         case StyleProp.FontSize: return StrCast(doc?.[fieldKey + "fontSize"], StrCast(doc?.fontSize, StrCast(Doc.UserDoc().fontSize)));
-        case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + "fontFamily"], StrCast(doc?.["fontFamily"], StrCast(Doc.UserDoc().fontFamily)));
+        case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + "fontFamily"], StrCast(doc?.fontFamily, StrCast(Doc.UserDoc().fontFamily)));
         case StyleProp.ShowTitle: return (doc && !doc.presentationTargetDoc &&
             StrCast(doc._showTitle,
                 props?.showTitle?.() ||
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9d0402075..72e84c141 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1146,7 +1146,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 if (startupText) {
                     dispatch(state.tr.insertText(startupText));
                 } else if (!FormattedTextBox.LiveTextUndo) {
-                    selectAll(this._editorView!.state, (tr) => {
+                    selectAll(this._editorView.state, (tr) => {
                         this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: "center" })));
                     });
                 }
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index fe297782c..09cfb2077 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -251,7 +251,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
         this._results.forEach((_, doc) => {
             this._pageRanks.set(doc, 1.0 / this._results.size);
 
-            if (Doc.GetProto(doc)[DirectLinksSym].size == 0) {
+            if (Doc.GetProto(doc)[DirectLinksSym].size === 0) {
                 this._linkedDocsOut.set(doc, new Set(this._results.keys()));
 
                 this._results.forEach((_, linkedDoc) => {
@@ -259,20 +259,20 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
                 });
             }
             else {
-                let linkedDocSet: Set<Doc> = new Set();
+                const linkedDocSet = new Set<Doc>();
 
                 Doc.GetProto(doc)[DirectLinksSym].forEach((link) => {
-                    let d1 = link?.anchor1 as Doc;
-                    let d2 = link?.anchor2 as Doc;
-                    if (doc == d1 && this._results.has(d2)) {
+                    const d1 = link?.anchor1 as Doc;
+                    const d2 = link?.anchor2 as Doc;
+                    if (doc === d1 && this._results.has(d2)) {
                         linkedDocSet.add(d2);
                         this._linkedDocsIn.get(d2)?.add(doc);
                     }
-                    else if (doc == d2 && this._results.has(d1)) {
+                    else if (doc === d2 && this._results.has(d1)) {
                         linkedDocSet.add(d1);
                         this._linkedDocsIn.get(d1)?.add(doc);
                     }
-                })
+                });
 
                 this._linkedDocsOut.set(doc, linkedDocSet);
             }
@@ -291,7 +291,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
         let converged = true;
         const pageRankFromAll = (1 - DAMPENING_FACTOR) / this._results.size;
 
-        let nextPageRanks: Map<Doc, number> = new Map<Doc, number>();
+        const nextPageRanks = new Map<Doc, number>();
 
         this._results.forEach((_, doc) => {
             let nextPageRank = pageRankFromAll;
@@ -397,7 +397,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
 
         const isLinkSearch: boolean = this.props.linkSearch;
 
-        const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0))  // sorted by page rank
+        const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0));  // sorted by page rank
 
         const resultsJSX = Array();
 
-- 
cgit v1.2.3-70-g09d2

