From 1a444532d026a208817c6997f00022a3e09ead23 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 29 Oct 2024 13:39:16 -0400
Subject: fixes to allow typing and drawing without a mode switch.

---
 src/client/views/GestureOverlay.tsx | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index afeecaa63..402a7c20a 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -95,6 +95,8 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     }
     @action
     onPointerDown = (e: React.PointerEvent) => {
+        DocumentView.DeselectAll();
+        (document.activeElement as HTMLElement)?.blur();
         if (!(e.target as HTMLElement)?.className?.toString().startsWith('lm_')) {
             if ([InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
                 this._points.push({ X: e.clientX, Y: e.clientY });
@@ -244,8 +246,9 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         this.dispatchGesture(Gestures.Stroke);
     };
     @action
-    onPointerUp = () => {
+    onPointerUp = (e: PointerEvent) => {
         const ffView = DocumentView.DownDocView?.ComponentView instanceof CollectionFreeFormView && DocumentView.DownDocView.ComponentView;
+        const downView = DocumentView.DownDocView;
         DocumentView.DownDocView = undefined;
         if (this._points.length > 1) {
             const B = this.svgBounds;
@@ -286,6 +289,9 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                     this.dryInk();
                 }
             }
+        } else {
+            (downView?.ComponentView as CollectionFreeFormView)?._marqueeViewRef?.current?.setPreviewCursor?.(this._points[0].X, this._points[0].Y, false, false, undefined);
+            e.preventDefault();
         }
         this._points.length = 0;
     };
-- 
cgit v1.2.3-70-g09d2


From 41575f2f6121c402fef5fa49deb314f3226dec01 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 29 Oct 2024 14:21:47 -0400
Subject: improved rectangle/triangle gesture recognition

---
 src/client/views/GestureOverlay.tsx |  1 -
 src/pen-gestures/ndollar.ts         | 96 +++++++++++--------------------------
 2 files changed, 29 insertions(+), 68 deletions(-)

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

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 402a7c20a..59d37df91 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -95,7 +95,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     }
     @action
     onPointerDown = (e: React.PointerEvent) => {
-        DocumentView.DeselectAll();
         (document.activeElement as HTMLElement)?.blur();
         if (!(e.target as HTMLElement)?.className?.toString().startsWith('lm_')) {
             if ([InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 04262b61f..f6e1c87fa 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -1,4 +1,5 @@
 /* eslint-disable no-use-before-define */
+import { numberRange } from '../Utils';
 import { Gestures } from './GestureTypes';
 
 /**
@@ -193,77 +194,38 @@ export class NDollarRecognizer {
     constructor(
         useBoundedRotationInvariance: boolean // constructor
     ) {
+        const rectMaker = (width: number, height1: number, height2: number) => [
+            new Point(0, 0), //
+            new Point(0, height1),
+            new Point(width, height2),
+            new Point(width, 0),
+            new Point(0, 0),
+        ];
+
+        const arect = rectMaker(100, 100, 50);
+        const aorect = rectMaker(300, 100, 50);
+        const brect = rectMaker(100, 100, 200);
+        const borect = rectMaker(300, 100, 200);
+        const rect = rectMaker(100, 100, 100);
+        const orect = rectMaker(300, 100, 100);
+        const equilateral = [new Point(50, 100), new Point(100, 0), new Point(0, 0), new Point(50, 100)];
+        const aequilateral = [new Point(20, 100), new Point(200, 0), new Point(0, 0), new Point(20, 100)];
+        const bequilateral = [new Point(180, 100), new Point(200, 0), new Point(0, 0), new Point(180, 100)];
+        const circle = numberRange(11).map(i => new Point(100 + 100 * Math.cos((i / 10) * Math.PI * 2), 100 + 100 * Math.sin((i / 10) * Math.PI * 2)));
+        const rightAngle = [new Point(0, 0), new Point(0, 100), new Point(200, 100)];
         //
-        // one predefined multistroke for each multistroke type
+        // one predefined multistroke (plus its counterclockwise reversal for closed shapes) for each multistroke type
         //
         this.Multistrokes.push(
-            new Multistroke(
-                Gestures.Rectangle,
-                useBoundedRotationInvariance,
-                new Array([
-                    new Point(30, 146), // new Point(29, 160), new Point(30, 180), new Point(31, 200),
-                    new Point(30, 222), // new Point(50, 219), new Point(70, 225), new Point(90, 230),
-                    new Point(106, 225), // new Point(100, 200), new Point(106, 180), new Point(110, 160),
-                    new Point(106, 146), // new Point(80, 150), new Point(50, 146),
-                    new Point(30, 143),
-                ])
-            )
-        );
-        this.Multistrokes.push(new Multistroke(Gestures.Rectangle, useBoundedRotationInvariance, new Array([new Point(30, 143), new Point(106, 146), new Point(106, 225), new Point(30, 222), new Point(30, 146)])));
-        this.Multistrokes.push(new Multistroke(Gestures.Line, useBoundedRotationInvariance, [[new Point(12, 347), new Point(119, 347)]]));
-        this.Multistrokes.push(
-            new Multistroke(
-                Gestures.Triangle, // equilateral
-                useBoundedRotationInvariance,
-                new Array([new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100)])
-            )
-        );
-        this.Multistrokes.push(
-            new Multistroke(
-                Gestures.Triangle, // equilateral
-                useBoundedRotationInvariance,
-                new Array([new Point(42, 100), new Point(140, 102), new Point(100, 200), new Point(40, 100)])
-            )
-        );
-        this.Multistrokes.push(
-            new Multistroke(
-                Gestures.Circle,
-                useBoundedRotationInvariance,
-                new Array([
-                    new Point(200, 250),
-                    new Point(240, 230),
-                    new Point(248, 210),
-                    new Point(248, 190),
-                    new Point(240, 170),
-                    new Point(200, 150),
-                    new Point(160, 170),
-                    new Point(151, 190),
-                    new Point(151, 210),
-                    new Point(160, 230),
-                    new Point(201, 250),
-                ])
-            )
-        );
-        this.Multistrokes.push(
-            new Multistroke(
-                Gestures.Circle,
-                useBoundedRotationInvariance,
-                new Array([
-                    new Point(201, 250),
-                    new Point(160, 230),
-                    new Point(151, 210),
-                    new Point(151, 190),
-                    new Point(160, 170),
-                    new Point(200, 150),
-                    new Point(240, 170),
-                    new Point(248, 190),
-                    new Point(248, 210),
-                    new Point(240, 230),
-                    new Point(200, 250),
-                ])
-            )
+            ...[arect, aorect, brect, borect, rect, orect].map(s => new Multistroke(Gestures.Rectangle, useBoundedRotationInvariance, [s])),
+            ...[arect, aorect, brect, borect, rect, orect].map(s => new Multistroke(Gestures.Rectangle, useBoundedRotationInvariance, [s.reverse()])),
+            ...[aequilateral, bequilateral, equilateral].map(s => new Multistroke(Gestures.Triangle, useBoundedRotationInvariance, [s])),
+            ...[aequilateral, bequilateral, equilateral].map(s => new Multistroke(Gestures.Triangle, useBoundedRotationInvariance, [s.reverse()])),
+            new Multistroke(Gestures.Circle, useBoundedRotationInvariance, [circle]),
+            new Multistroke(Gestures.Circle, useBoundedRotationInvariance, [circle.reverse()]),
+            new Multistroke(Gestures.RightAngle, useBoundedRotationInvariance, [rightAngle]),
+            new Multistroke(Gestures.Line, useBoundedRotationInvariance, [[new Point(12, 347), new Point(119, 347)]])
         );
-        this.Multistrokes.push(new Multistroke(Gestures.RightAngle, useBoundedRotationInvariance, new Array([new Point(0, 0), new Point(0, 100), new Point(200, 100)])));
         NumMultistrokes = this.Multistrokes.length; // NumMultistrokes flags the end of the non user-defined gstures strokes
         //
         // PREDEFINED STROKES
-- 
cgit v1.2.3-70-g09d2


From 4c768162e0436115a05b9c8b0e4d837d626d45ba Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 30 Oct 2024 18:54:52 -0400
Subject: reworked how context menu buttons for ink and text work. added
 disableMixBlend for making transparent docs not use 'multiply'.

---
 src/ClientUtils.ts                                 |  11 +-
 src/client/documents/DocUtils.ts                   |  16 +-
 src/client/documents/Documents.ts                  |   1 -
 src/client/util/CurrentUserUtils.ts                |  78 +++++----
 src/client/util/InteractionUtils.tsx               |   6 +-
 src/client/util/SettingsManager.tsx                |   4 +-
 src/client/util/SnappingManager.ts                 |   7 +
 src/client/views/FilterPanel.tsx                   |   2 +-
 src/client/views/GestureOverlay.tsx                | 164 ++++++------------
 src/client/views/GlobalKeyHandler.ts               |   9 +-
 src/client/views/InkingStroke.tsx                  |   3 +-
 src/client/views/LightboxView.tsx                  |   4 +-
 src/client/views/StyleProp.ts                      |   4 +-
 src/client/views/StyleProvider.tsx                 |   5 +-
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 .../CollectionFreeFormInfoUI.tsx                   |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  71 ++++----
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +-
 src/client/views/global/globalScripts.ts           | 112 +++++++------
 .../views/newlightbox/ButtonMenu/ButtonMenu.tsx    |   6 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   5 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   4 +-
 src/client/views/nodes/DocumentView.tsx            |  72 ++++----
 .../views/nodes/FontIconBox/FontIconBox.scss       |   3 +
 src/client/views/nodes/FontIconBox/FontIconBox.tsx | 186 ++++++++++++---------
 src/client/views/nodes/ImageBox.tsx                |  10 +-
 src/client/views/nodes/VideoBox.tsx                |   2 +-
 src/client/views/nodes/WebBox.tsx                  |  13 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  12 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |  22 +--
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 src/client/views/smartdraw/SmartDrawHandler.tsx    |  12 +-
 src/fields/Doc.ts                                  |   6 +-
 src/fields/InkField.ts                             |  27 +--
 src/pen-gestures/GestureTypes.ts                   |   1 -
 35 files changed, 444 insertions(+), 446 deletions(-)

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

diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index e7aee1c2a..baad9a06c 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -107,12 +107,12 @@ export namespace ClientUtils {
             default:                 return type.charAt(0).toUpperCase() + type.substring(1,3);
         } // prettier-ignore
     }
-    export function cleanDocumentType(type: DocumentType, colType: CollectionViewType) {
+    export function cleanDocumentType(type: DocumentType, colType?: CollectionViewType) {
         switch (type) {
             case DocumentType.PDF:   return 'PDF';
             case DocumentType.IMG:   return 'Image';
             case DocumentType.AUDIO: return 'Audio';
-            case DocumentType.COL:   return 'Collection:'+colType;
+            case DocumentType.COL:   return 'Collection:'+ (colType ?? "");
             case DocumentType.RTF:   return 'Text';
             default:                 return type.charAt(0).toUpperCase() + type.slice(1);
         } // prettier-ignore
@@ -212,7 +212,7 @@ export namespace ClientUtils {
         return { r: r, g: g, b: b, a: a };
     }
 
-    const isTransparentFunctionHack = 'isTransparent(__value__)';
+    export const isTransparentFunctionHack = 'isTransparent(__value__)';
     export const noRecursionHack = '__noRecursion';
 
     // special case filters
@@ -223,11 +223,6 @@ export namespace ClientUtils {
     export function IsRecursiveFilter(val: string) {
         return !val.includes(noRecursionHack);
     }
-    export function HasFunctionFilter(val: string) {
-        if (val.includes(isTransparentFunctionHack)) return (color: string) => color !== '' && DashColor(color).alpha() !== 1;
-        // add other function filters here...
-        return undefined;
-    }
 
     export function toRGBAstr(col: { r: number; g: number; b: number; a?: number }) {
         return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')';
diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index dae3e48ad..067b9c5e0 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -3,7 +3,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { saveAs } from 'file-saver';
 import * as JSZip from 'jszip';
 import { action, runInAction } from 'mobx';
-import { ClientUtils } from '../../ClientUtils';
+import { ClientUtils, DashColor } from '../../ClientUtils';
 import * as JSZipUtils from '../../JSZipUtils';
 import { decycle } from '../../decycler/decycler';
 import { DateField } from '../../fields/DateField';
@@ -36,11 +36,16 @@ import { DocumentView } from '../views/nodes/DocumentView';
 import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
 
 export namespace DocUtils {
+    function HasFunctionFilter(val: string) {
+        if (val.includes(ClientUtils.isTransparentFunctionHack)) return (d: Doc, color: string) => !d.disableMixBlend && color !== '' && DashColor(color).alpha() !== 1;
+        // add other function filters here...
+        return undefined;
+    }
     function matchFieldValue(doc: Doc, key: string, valueIn: unknown): boolean {
         let value = valueIn;
-        const hasFunctionFilter = ClientUtils.HasFunctionFilter(value as string);
+        const hasFunctionFilter = HasFunctionFilter(value as string);
         if (hasFunctionFilter) {
-            return hasFunctionFilter(StrCast(doc[key]));
+            return hasFunctionFilter(doc, StrCast(doc[key]));
         }
         if (key === LinkedTo) {
             // links are not a field value, so handled here.  value is an expression of form ([field=]idToDoc("..."))
@@ -683,9 +688,14 @@ export namespace DocUtils {
                       _layout_centered: BoolCast(Doc.UserDoc()._layout_centered),
                       _layout_fitWidth: true,
                       _layout_autoHeight: true,
+                      backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor),
                       text_fitBox: BoolCast(Doc.UserDoc().fitBox),
                       text_align: StrCast(Doc.UserDoc().textAlign),
                       text_fontColor: StrCast(Doc.UserDoc().fontColor),
+                      text_fontFamily: StrCast(Doc.UserDoc().fontFamily),
+                      text_fontWeight: StrCast(Doc.UserDoc().fontWeight),
+                      text_fontStyle: StrCast(Doc.UserDoc().fontStyle),
+                      text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration),
                   }),
         });
 
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b0e1a7545..efd6ce3c9 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -927,7 +927,6 @@ export namespace Docs {
             I.text_align = 'center';
             I.rotation = 0;
             I.defaultDoubleClick = 'ignore';
-            I.keepZWhenDragged = true;
             I.author_date = new DateField();
             I.acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View;
             // I.acl_Override = SharingPermissions.Unset;
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b3aa5e145..98a3e8914 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -4,7 +4,7 @@ import * as rp from 'request-promise';
 import { ClientUtils, OmitKeys } from "../../ClientUtils";
 import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc";
 import { DocData } from "../../fields/DocSymbols";
-import { InkTool } from "../../fields/InkField";
+import { InkEraserTool, InkInkTool, InkTool } from "../../fields/InkField";
 import { List } from "../../fields/List";
 import { PrefetchProxy } from "../../fields/Proxy";
 import { RichTextField } from "../../fields/RichTextField";
@@ -39,6 +39,7 @@ import { SelectionManager } from "./SelectionManager";
 import { ColorScheme } from "./SettingsManager";
 import { SnappingManager } from "./SnappingManager";
 import { UndoManager } from "./UndoManager";
+import { DocumentView } from "../views/nodes/DocumentView";
 
 export interface Button {
     // DocumentOptions fields a button can set
@@ -731,11 +732,11 @@ pie title Minerals in my tap water
         return [
             { title: "Font",     toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'},
               btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
-            { title: "Font Size",toolTip: "Font size (%size)",    btnType: ButtonType.NumberDropdownButton,              toolType:"fontSize", ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 9 },
+            { title: " Size",    toolTip: "Font size (%size)",    btnType: ButtonType.NumberDropdownButton,              toolType:"fontSize", ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 9 },
             { title: "Color",    toolTip: "Font color (%color)",  btnType: ButtonType.ColorButton,  icon: "font",        toolType:"fontColor",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'} },
             { title: "Highlight",toolTip: "Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'} },
             { title: "Bold",     toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",        toolType:"bold",     ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
-            { title: "Italic",   toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",      toolType:"italics",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+            { title: "Italic",   toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",      toolType:"italic",   ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             { title: "Under",    toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",   toolType:"underline",ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             { title: "Bullets",  toolTip: "Bullet List",          btnType: ButtonType.ToggleButton, icon: "list",        toolType:"bullet",   ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             { title: "#",        toolTip: "Number List",          btnType: ButtonType.ToggleButton, icon: "list-ol",     toolType:"decimal",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
@@ -758,24 +759,27 @@ pie title Minerals in my tap water
 
     static inkTools():Button[] {
         return [
-            { title: "Pen",      toolTip: "Pen (Ctrl+P)",                   btnType: ButtonType.ToggleButton, icon: "pen-nib",    toolType: "pen",                                   scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }},
-            { title: "Highlight",toolTip: "Highlight (Ctrl+H)",             btnType: ButtonType.ToggleButton, icon: "highlighter",toolType: "highlighter",                           scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }},
-            { title: "Write",    toolTip: "Write (Ctrl+Shift+P)",           btnType: ButtonType.ToggleButton, icon: "pen",        toolType: "write",                                 scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
-            { title: "Eraser",   toolTip: "Eraser (Ctrl+E)",                btnType: ButtonType.MultiToggleButton,                toolType: InkTool.Eraser,                          scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' },
+            { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",      toolType: Gestures.Circle,                          scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`,   onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+            { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",      toolType: Gestures.Rectangle,                       scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`,   onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+            { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",       toolType: Gestures.Line,                            scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`,   onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+            { title: "Ink",    toolTip: "Ink",                              btnType: ButtonType.MultiToggleButton,                 toolType: InkTool.Ink,                              scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, 
+                subMenu: [
+                    { title: "Pen",      toolTip: "Pen (Ctrl+P)",           btnType: ButtonType.ToggleButton, icon: "pen-nib",     toolType: InkInkTool.Pen,        ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }},
+                    { title: "Highlight",toolTip: "Highlight (Ctrl+H)",     btnType: ButtonType.ToggleButton, icon: "highlighter", toolType: InkInkTool.Highlight,  ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }},
+                    { title: "Write",    toolTip: "Write (Ctrl+Shift+P)",   btnType: ButtonType.ToggleButton, icon: "pen",         toolType: InkInkTool.Write,      ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' },  funcs: {hidden:"IsNoviceMode()" }},      
+                ]},
+            { title: "Width",      toolTip: "Stroke width",                 btnType: ButtonType.NumberSliderButton,                toolType: "strokeWidth",         ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}, numBtnMin: 1, linearBtnWidth:40},
+            { title: "Color",      toolTip: "Stroke color",                 btnType: ButtonType.ColorButton,  icon: "pen",         toolType: "strokeColor",         ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}},
+            { title: "Eraser",     toolTip: "Eraser (Ctrl+E)",              btnType: ButtonType.MultiToggleButton,                 toolType: InkTool.Eraser,                           scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' },
               subMenu: [
-                    { title: "Stroke",   toolTip: "Stroke Erase",           btnType: ButtonType.ToggleButton, icon: "eraser",      toolType:InkTool.StrokeEraser, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} },
-                    { title: "Segment",  toolTip: "Segment Erase",          btnType: ButtonType.ToggleButton, icon: "xmark",       toolType:InkTool.SegmentEraser,ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} },
-                    { title: "Radius",   toolTip: "Radius Erase",           btnType: ButtonType.ToggleButton, icon: "circle-xmark",toolType:InkTool.RadiusEraser, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} },
+                    { title: "Stroke",   toolTip: "Eraser complete strokes",btnType: ButtonType.ToggleButton, icon: "eraser",      toolType:InkEraserTool.Stroke,   ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}},
+                    { title: "Segment",  toolTip: "Erase segments up to intersections",btnType: ButtonType.ToggleButton,icon: "xmark",toolType:InkEraserTool.Segment,ignoreClick:true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}},
+                    { title: "Area",     toolTip: "Erase like a pencil",    btnType: ButtonType.ToggleButton, icon: "circle-xmark",toolType:InkEraserTool.Radius,   ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}},
             ]},
-            { title: "Eraser Width",  toolTip: "Eraser Width",              btnType: ButtonType.NumberSliderButton,               toolType: "eraserWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1, funcs: {hidden:"NotRadiusEraser()"}},
-            { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",     toolType: Gestures.Circle,      scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
-            { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",     toolType: Gestures.Rectangle,   scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
-            { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",      toolType: Gestures.Line,        scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
-            { title: "Mask",   toolTip: "Mask",                             btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask",                        scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
-            { title: "Labels", toolTip: "Labels",                           btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels",                         scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, },
-            { title: "Width",  toolTip: "Stroke width",                     btnType: ButtonType.NumberSliderButton,               toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1},
-            { title: "Ink",    toolTip: "Stroke color",                     btnType: ButtonType.ColorButton,  icon: "pen",        toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'} },
-            { title: "Smart Draw", toolTip: "Draw with GPT",                btnType: ButtonType.ToggleButton, icon: "user-pen",   toolType: "smartdraw",                      scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'}, funcs: {hidden: "IsNoviceMode()"}},
+            { title: " Size",      toolTip: "Size of area pencil eraser",   btnType: ButtonType.NumberSliderButton,               toolType: "eraserWidth",          ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"NotRadiusEraser()"}, numBtnMin: 1, linearBtnWidth:40},
+            { title: "Mask",       toolTip: "Make Stroke a Stencil Mask",   btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask",                                 scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
+            { title: "Labels",     toolTip: "Show Labels Inside Shapes",    btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels",                                  scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}},
+            { title: "Smart Draw", toolTip: "Draw with GPT",                btnType: ButtonType.ToggleButton, icon: "user-pen",   toolType: "smartdraw",                               scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'},  funcs: {hidden: "IsNoviceMode()"}},
         ];
     }
 
@@ -814,11 +818,11 @@ pie title Minerals in my tap water
             { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,         expertMode: false,                                                                                                                             width: 30,  scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
             { title: "Header",  icon: "heading",       toolTip: "Doc Titlebar Color",   btnType: ButtonType.ColorButton,         expertMode: false,                                                                                                          ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'} },
             { title: "Template",icon: "scroll",        toolTip: "Default Note Template",btnType: ButtonType.ToggleButton,        expertMode: false, toolType:DocumentType.RTF,                                                                                                              scripts: { onClick: '{ return setDefaultTemplate(_readOnly_); }'} },
-            { title: "Fill",    icon: "fill-drip",     toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton,         expertMode: false,                                                                                                          ignoreClick: true, width: 30,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected
-            { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton,        expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode, true)'},       scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
-            { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+            { title: "Fill",    icon: "fill-drip",     toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton,         expertMode: false,                                                                                                          ignoreClick: true, width: 30,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'} }, // Only when a document is selected
+            { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton,        expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode, true)'},                        scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
+            { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'},                  width: 30,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
             { title: "Num",     icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton,          expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
-            { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
+            { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'},                  width: 30,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
             
             { title: "Filter",  icon: "=",      toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.tagGroupTools(),ignoreClick:true, toolType:DocumentType.COL,            funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 30, backgroundColor: doc.userVariantColor as string},
             { title: "Text",    icon: "Text",   toolTip: "Text functions",       subMenu: CurrentUserUtils.textTools(),   expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                                             linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
@@ -863,7 +867,7 @@ pie title Minerals in my tap water
         }  
         // linear view
         const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
-            childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: !params.scripts?.onClick,
+            childDontRegisterViews: true, flexGap: 0, _height: 30, _width: 30, ignoreClick: !params.scripts?.onClick,
             linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc};
         
         const items = (menutBtn?:Doc) => !menutBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menutBtn) );
@@ -987,15 +991,23 @@ pie title Minerals in my tap water
         Doc.noviceMode ?? (Doc.noviceMode = true);
         doc._showLabel ?? (doc._showLabel = true);
         doc.textAlign ?? (doc.textAlign = "left");
+        doc.textBackgroundColor ?? (doc.textBackgroundColor = Colors.LIGHT_GRAY);
         doc.activeTool = InkTool.None;
         doc.openInkInLightbox ?? (doc.openInkInLightbox = false);
-        doc.activeInkHideTextLabels ?? (doc.activeInkHideTextLabels = false);
-        doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");
-        doc.activeInkWidth ?? (doc.activeInkWidth = 1);
-        doc.activeInkBezier ?? (doc.activeInkBezier = "0");
-        doc.activeFillColor ?? (doc.activeFillColor = "");
-        doc.activeArrowStart ?? (doc.activeArrowStart = "");
-        doc.activeArrowEnd ?? (doc.activeArrowEnd = "");
+        doc.activeHideTextLabels ?? (doc.activeHideTextLabels = false);
+        doc[`active${InkInkTool.Pen}Color`]        ?? (doc[`active${InkInkTool.Pen}Color`] = "rgb(0, 0, 0)");
+        doc[`active${InkInkTool.Pen}Width`]        ?? (doc[`active${InkInkTool.Pen}Width`] = 2);
+        doc[`active${InkInkTool.Pen}Bezier`]       ?? (doc[`active${InkInkTool.Pen}Bezier`] = "0");
+        doc[`active${InkInkTool.Write}Color`]      ?? (doc[`active${InkInkTool.Write}Color`] = "rgb(255, 0, 0)");
+        doc[`active${InkInkTool.Write}Width`]      ?? (doc[`active${InkInkTool.Write}Width`] = 1);
+        doc[`active${InkInkTool.Write}Bezier`]     ?? (doc[`active${InkInkTool.Write}Bezier`] = "0");
+        doc[`active${InkInkTool.Highlight}Color`]  ?? (doc[`active${InkInkTool.Highlight}Color`] = 'transparent');
+        doc[`active${InkInkTool.Highlight}Width`]  ?? (doc[`active${InkInkTool.Highlight}Width`] = 20);
+        doc[`active${InkInkTool.Highlight}Bezier`] ?? (doc[`active${InkInkTool.Highlight}Bezier`] = "0");
+        doc[`active${InkInkTool.Highlight}Fill`]   ?? (doc[`active${InkInkTool.Highlight}Fill`] = "rgba(0, 255, 255, 0.4)");
+        doc.activeInkTool ?? (doc.activeInkTool = InkInkTool.Pen);
+        doc.activeEraserTool ?? (doc.activeEraserTool = InkEraserTool.Stroke);
+        doc.activeEraserWidth ?? (doc.activeEraserWidth = 20);
         doc.activeDash ?? (doc.activeDash === "0");
         doc.fontSize ?? (doc.fontSize = "12px");
         doc.fontFamily ?? (doc.fontFamily = "Arial");
@@ -1130,7 +1142,9 @@ pie title Minerals in my tap water
 }
 
 // eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function NotRadiusEraser() { return Doc.ActiveTool !== InkTool.RadiusEraser; }, "is the active tool anything but the radius eraser");
+ScriptingGlobals.add(function activeInkTool() { return Doc.ActiveTool=== InkTool.Ink || DocumentView.Selected().some(dv => dv.layoutDoc.layout_isSvg); }, "is a pen tool or an ink stroke active");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function NotRadiusEraser() { return Doc.ActiveTool !== InkTool.Eraser || Doc.ActiveEraser !== InkEraserTool.Radius; }, "is the active tool anything but the radius eraser");
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function MySharedDocs()    { return Doc.MySharedDocs; }, "document containing all shared Docs");
 // eslint-disable-next-line prefer-arrow-callback
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 4231c2ca8..ca1cb8014 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -109,7 +109,7 @@ export namespace InteractionUtils {
         dash: string | undefined,
         scalexIn: number,
         scaleyIn: number,
-        shape: Gestures,
+        shape: Gestures | undefined,
         pevents: Property.PointerEvents,
         opacity: number,
         nodefs: boolean,
@@ -136,7 +136,7 @@ export namespace InteractionUtils {
         const arrowLengthFactor = 5 * (markerScale || 0.5);
         const arrowNotchFactor = 2 * (markerScale || 0.5);
         return (
-            <svg fill={color} style={{ transition: 'inherit' }} onPointerDown={downHdlr}>
+            <svg fill={color || 'transparent'} style={{ transition: 'inherit' }} onPointerDown={downHdlr}>
                 {' '}
                 {/* setting the svg fill sets the arrowStart fill */}
                 {nodefs ? null : (
@@ -186,7 +186,7 @@ export namespace InteractionUtils {
                         opacity: 1.0,
                         // opacity: strokeWidth !== width ? 0.5 : undefined,
                         pointerEvents: pevents === 'all' ? 'visiblePainted' : pevents,
-                        stroke: color ?? 'rgb(0, 0, 0)',
+                        stroke: (color ?? 'rgb(0, 0, 0)') || 'transparent',
                         strokeWidth,
                         strokeLinecap: strokeLineCap,
                         strokeDasharray: dashArray,
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 33d7d90a8..628097ca8 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -268,9 +268,9 @@ export class SettingsManager extends React.Component<object> {
                     formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
                     onClick={() => {
-                        Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels;
+                        Doc.UserDoc().activeHideTextLabels = !Doc.UserDoc().activeHideTextLabels;
                     }}
-                    toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)}
+                    toggleStatus={BoolCast(Doc.UserDoc().activeHideTextLabels)}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                 />
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 5f6c7d9ac..2a150dc5a 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,4 +1,5 @@
 import { observable, action, runInAction, makeObservable } from 'mobx';
+import { Gestures } from '../../pen-gestures/GestureTypes';
 
 export enum freeformScrollMode {
     Pan = 'pan',
@@ -29,6 +30,8 @@ export class SnappingManager {
     @observable _propertyWid: number = 0;
     @observable _printToConsole: boolean = false;
     @observable _hideDecorations: boolean = false;
+    @observable _keepGestureMode: boolean = false; // for whether primitive selection enters a one-shot or persistent mode
+    @observable _inkShape: Gestures | undefined = undefined;
 
     private constructor() {
         SnappingManager._manager = this;
@@ -61,6 +64,8 @@ export class SnappingManager {
     public static get PropertiesWidth(){ return this.Instance._propertyWid; } // prettier-ignore
     public static get PrintToConsole() { return this.Instance._printToConsole; } // prettier-ignore
     public static get HideDecorations(){ return this.Instance._hideDecorations; } // prettier-ignore
+    public static get KeepGestureMode(){ return this.Instance._keepGestureMode; } // prettier-ignore
+    public static get InkShape()       { return this.Instance._inkShape; } // prettier-ignore
 
     public static SetLongPress      = (press: boolean) => runInAction(() => {this.Instance._longPress = press}); // prettier-ignore
     public static SetShiftKey       = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore
@@ -78,6 +83,8 @@ export class SnappingManager {
     public static SetPropertiesWidth= (wid:number)     =>runInAction(() => {this.Instance._propertyWid = wid}); // prettier-ignore
     public static SetPrintToConsole = (state:boolean)  =>runInAction(() => {this.Instance._printToConsole = state}); // prettier-ignore
     public static SetHideDecorations= (state:boolean)  =>runInAction(() => {this.Instance._hideDecorations = state}); // prettier-ignore
+    public static SetKeepGestureMode= (state:boolean)  =>runInAction(() => {this.Instance._keepGestureMode = state}); // prettier-ignore
+    public static SetInkShape       = (shape?:Gestures)=>runInAction(() => {this.Instance._inkShape = shape}); // prettier-ignore
 
     public static userColor: string | undefined;
     public static userVariantColor: string | undefined;
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 11425e477..99738052d 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -102,7 +102,7 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
 
     return (
         <div
-            className={`filterHotKey-button`}
+            className="filterHotKey-button"
             onClick={e => {
                 e.stopPropagation();
                 state.startEditing();
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 59d37df91..2d6cd03e0 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -2,9 +2,9 @@ import * as fitCurve from 'fit-curve';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
+import { setupMoveUpEvents } from '../../ClientUtils';
 import { emptyFunction, intersectRect } from '../../Utils';
-import { Doc, Opt, returnEmptyDoclist } from '../../fields/Doc';
+import { Doc } from '../../fields/Doc';
 import { InkData, InkField, InkTool } from '../../fields/InkField';
 import { NumCast } from '../../fields/Types';
 import { Gestures } from '../../pen-gestures/GestureTypes';
@@ -14,27 +14,25 @@ import { DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { Transform } from '../util/Transform';
+import { SnappingManager } from '../util/SnappingManager';
 import { undoable } from '../util/UndoManager';
 import './GestureOverlay.scss';
 import { InkingStroke } from './InkingStroke';
 import { ObservableReactComponent } from './ObservableReactComponent';
-import { returnEmptyDocViewList } from './StyleProvider';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import {
-    ActiveArrowEnd,
-    ActiveArrowScale,
-    ActiveArrowStart,
-    ActiveDash,
-    ActiveFillColor,
-    ActiveInkBezierApprox,
+    ActiveInkArrowEnd,
+    ActiveInkArrowScale,
+    ActiveInkArrowStart,
     ActiveInkColor,
+    ActiveInkDash,
+    ActiveInkFillColor,
     ActiveInkWidth,
     DocumentView,
-    SetActiveArrowStart,
-    SetActiveDash,
-    SetActiveFillColor,
+    SetActiveInkArrowStart,
     SetActiveInkColor,
+    SetActiveInkDash,
+    SetActiveInkFillColor,
     SetActiveInkWidth,
 } from './nodes/DocumentView';
 export enum ToolglassTools {
@@ -57,18 +55,14 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     // eslint-disable-next-line no-use-before-define
     static Instances: GestureOverlay[] = [];
 
-    @observable public InkShape: Opt<Gestures> = undefined;
     @observable public SavedColor?: string = undefined;
     @observable public SavedWidth?: number = undefined;
     @observable public Tool: ToolglassTools = ToolglassTools.None;
-    @observable public KeepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode
 
     @observable private _thumbX?: number = undefined;
     @observable private _thumbY?: number = undefined;
     @observable private _pointerY?: number = undefined;
     @observable private _points: { X: number; Y: number }[] = [];
-    @observable private _strokes: InkData[] = [];
-    @observable private _palette?: JSX.Element = undefined;
     @observable private _clipboardDoc?: JSX.Element = undefined;
 
     @computed private get height(): number {
@@ -97,7 +91,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     onPointerDown = (e: React.PointerEvent) => {
         (document.activeElement as HTMLElement)?.blur();
         if (!(e.target as HTMLElement)?.className?.toString().startsWith('lm_')) {
-            if ([InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+            if (Doc.ActiveTool === InkTool.Ink) {
                 this._points.push({ X: e.clientX, Y: e.clientY });
                 setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
             }
@@ -123,13 +117,9 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     };
 
     @action primCreated() {
-        if (!this.KeepPrimitiveMode) {
-            this.InkShape = undefined;
-            // get out of ink mode after each stroke=
-            // if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
+        if (!SnappingManager.KeepGestureMode) {
+            SnappingManager.SetInkShape(undefined);
             Doc.ActiveTool = InkTool.None;
-            // SetActiveArrowStart('none');
-            // SetActiveArrowEnd('none');
         }
     }
     /**
@@ -253,8 +243,8 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
             const B = this.svgBounds;
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
             const { Name, Score } =
-                (this.InkShape
-                    ? new Result(this.InkShape, 1, Date.now) 
+                (SnappingManager.InkShape
+                    ? new Result(SnappingManager.InkShape, 1, Date.now) 
                     : Doc.UserDoc().recognizeGestures && points.length > 2
                       ? GestureUtils.GestureRecognizer.Recognize([points])
                       : undefined) ?? 
@@ -292,6 +282,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
             (downView?.ComponentView as CollectionFreeFormView)?._marqueeViewRef?.current?.setPreviewCursor?.(this._points[0].X, this._points[0].Y, false, false, undefined);
             e.preventDefault();
         }
+        this.primCreated();
         this._points.length = 0;
     };
     /**
@@ -557,97 +548,38 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         B.bottom += width / 2;
         B.width += width;
         B.height += width;
-        const fillColor = ActiveFillColor();
+        const fillColor = ActiveInkFillColor();
         const strokeColor = fillColor && fillColor !== 'transparent' ? fillColor : ActiveInkColor();
         return [
             this.props.children,
-            this._palette,
-            [
-                this._strokes.map((l, i) => {
-                    const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(l, true);
-                    return (
-                        <svg key={i} width={b.width} height={b.height} style={{ top: 0, left: 0, transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
-                            {InteractionUtils.CreatePolyline(
-                                l,
-                                b.left,
-                                b.top,
-                                strokeColor,
-                                width,
-                                width,
-                                'miter',
-                                'round',
-                                ActiveInkBezierApprox(),
-                                'none' /* ActiveFillColor() */,
-                                ActiveArrowStart(),
-                                ActiveArrowEnd(),
-                                ActiveArrowScale(),
-                                ActiveDash(),
-                                1,
-                                1,
-                                this.InkShape as Gestures,
-                                'none',
-                                1.0,
-                                false
-                            )}
-                        </svg>
-                    );
-                }),
-                this._points.length <= 1 ? null : (
-                    <svg key="svg" width={B.width} height={B.height} style={{ top: 0, left: 0, transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
-                        {InteractionUtils.CreatePolyline(
-                            this._points.map(p => ({ X: p.X - (rect?.x || 0), Y: p.Y - (rect?.y || 0) })),
-                            B.left,
-                            B.top,
-                            ActiveInkColor(),
-                            width,
-                            width,
-                            'miter',
-                            'round',
-                            '',
-                            'none' /* ActiveFillColor() */,
-                            ActiveArrowStart(),
-                            ActiveArrowEnd(),
-                            ActiveArrowScale(),
-                            ActiveDash(),
-                            1,
-                            1,
-                            this.InkShape as Gestures,
-                            'none',
-                            1.0,
-                            false
-                        )}
-                    </svg>
-                ),
-            ],
+            this._points.length <= 1 ? null : (
+                <svg key="svg" width={B.width} height={B.height} style={{ top: 0, left: 0, transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
+                    {InteractionUtils.CreatePolyline(
+                        this._points.map(p => ({ X: p.X - (rect?.x || 0), Y: p.Y - (rect?.y || 0) })),
+                        B.left,
+                        B.top,
+                        strokeColor,
+                        width,
+                        width,
+                        'miter',
+                        'round',
+                        '',
+                        'none' /* ActiveFillColor() */,
+                        ActiveInkArrowStart(),
+                        ActiveInkArrowEnd(),
+                        ActiveInkArrowScale(),
+                        ActiveInkDash(),
+                        1,
+                        1,
+                        SnappingManager.InkShape,
+                        'none',
+                        1.0,
+                        false
+                    )}
+                </svg>
+            ),
         ];
     }
-    screenToLocalTransform = () => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1);
-    return300 = () => 300;
-    @action
-    public openFloatingDoc = (doc: Doc) => {
-        this._clipboardDoc = (
-            <DocumentView
-                Document={doc}
-                addDocument={undefined}
-                addDocTab={returnFalse}
-                pinToPres={emptyFunction}
-                removeDocument={undefined}
-                ScreenToLocalTransform={this.screenToLocalTransform}
-                PanelWidth={this.return300}
-                PanelHeight={this.return300}
-                isDocumentActive={returnFalse}
-                isContentActive={returnFalse}
-                renderDepth={0}
-                styleProvider={returnEmptyString}
-                containerViewPath={returnEmptyDocViewList}
-                focus={emptyFunction}
-                whenChildContentsActiveChanged={emptyFunction}
-                childFiltersByRanges={returnEmptyFilter}
-                childFilters={returnEmptyFilter}
-                searchFilterDocs={returnEmptyDoclist}
-            />
-        );
-    };
 
     @action
     public closeFloatingDoc = () => {
@@ -694,10 +626,10 @@ ScriptingGlobals.add(function setPen(width: string, color: string, fill: string,
         SetActiveInkColor(color);
         GestureOverlay.Instance.SavedWidth = ActiveInkWidth();
         SetActiveInkWidth(width);
-        SetActiveFillColor(fill);
-        SetActiveArrowStart(arrowStart);
-        SetActiveArrowStart(arrowEnd);
-        SetActiveDash(dash);
+        SetActiveInkFillColor(fill);
+        SetActiveInkArrowStart(arrowStart);
+        SetActiveInkArrowStart(arrowEnd);
+        SetActiveInkDash(dash);
     });
 });
 // eslint-disable-next-line prefer-arrow-callback
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index d7d8e9506..f16338361 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -2,7 +2,7 @@ import { random } from 'lodash';
 import { action } from 'mobx';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
-import { InkTool } from '../../fields/InkField';
+import { InkInkTool, InkTool } from '../../fields/InkField';
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, PromiseValue } from '../../fields/Types';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
@@ -280,10 +280,10 @@ export class KeyManager {
                 }
                 break;
             case 'e':
-                Doc.ActiveTool = [InkTool.StrokeEraser, InkTool.SegmentEraser, InkTool.RadiusEraser].includes(Doc.ActiveTool) ? InkTool.None : InkTool.StrokeEraser;
+                Doc.ActiveTool = Doc.ActiveTool === InkTool.Eraser ? InkTool.None : InkTool.Eraser;
                 break;
             case 'p':
-                Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
+                Doc.ActiveTool = Doc.ActiveTool === InkTool.Ink ? InkTool.None : InkTool.Ink;
                 break;
             case 'r':
                 preventDefault = false;
@@ -378,7 +378,8 @@ export class KeyManager {
                 UndoManager.Redo();
                 break;
             case 'p':
-                Doc.ActiveTool = InkTool.Write;
+                Doc.ActiveInk = InkInkTool.Write;
+                Doc.ActiveTool = InkTool.Ink;
                 break;
             default:
         }
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 270266a94..5199eb02b 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -470,14 +470,13 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
                     className="inkStroke"
                     style={{
                         transform: isInkMask ? `rotate(-${NumCast(this._props.LocalRotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
-                        // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
                         cursor: this._props.isSelected() ? 'default' : undefined,
                     }}
                     {...interactions}>
                     {clickableLine(this.onPointerDown, isInkMask)}
                     {isInkMask ? null : inkLine}
                 </svg>
-                {!closed || this.dataDoc[this.fieldKey + '_showLabel'] === false || (!RTFCast(this.dataDoc.text)?.Text && !this.dataDoc[this.fieldKey + '_showLabel'] && (!this._props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
+                {!closed || this.dataDoc[this.fieldKey + '_showLabel'] === false || (!RTFCast(this.dataDoc.text)?.Text && !this.dataDoc[this.fieldKey + '_showLabel'] && (!this._props.isSelected() || Doc.UserDoc().activeHideTextLabels)) ? null : (
                     <div
                         className="inkStroke-text"
                         style={{
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index a543b4875..3a8a38073 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -214,7 +214,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
         // if (this._showPalette === false) AnnotationPalette.Instance.resetPalette(true);
     };
     togglePen = () => {
-        Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
+        Doc.ActiveTool = Doc.ActiveTool === InkTool.Ink ? InkTool.None : InkTool.Ink;
     };
     toggleExplore = () => SnappingManager.SetExploreMode(!SnappingManager.ExploreMode);
 
@@ -332,7 +332,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                 {toggleBtn('lightboxView-navBtn', 'toggle reading view', BoolCast(this._doc?._layout_fitWidth), 'book-open', 'book', this.toggleFitWidth)}
                 {toggleBtn('lightboxView-tabBtn', 'open document in a tab', false, 'file-export', '', this.downloadDoc)}
                 {toggleBtn('lightboxView-paletteBtn', 'toggle annotation palette', this._showPalette === true, 'palette', '', this.togglePalette)}
-                {toggleBtn('lightboxView-penBtn', 'toggle pen annotation', Doc.ActiveTool === InkTool.Pen, 'pen', '', this.togglePen)}
+                {toggleBtn('lightboxView-penBtn', 'toggle pen annotation', Doc.ActiveTool === InkTool.Ink, 'pen', '', this.togglePen)}
                 {toggleBtn('lightboxView-exploreBtn', 'toggle navigate only mode', SnappingManager.ExploreMode, 'globe-americas', '', this.toggleExplore)}
             </div>
         );
diff --git a/src/client/views/StyleProp.ts b/src/client/views/StyleProp.ts
index 44d3bf757..56367e70b 100644
--- a/src/client/views/StyleProp.ts
+++ b/src/client/views/StyleProp.ts
@@ -19,7 +19,9 @@ export enum StyleProp {
     FontColor = 'fontColor', // color o tet
     FontSize = 'fontSize', // size of text font
     FontFamily = 'fontFamily', // font family of text
-    FontWeight = 'fontWeight', // font weight of text
+    FontWeight = 'fontWeight', // font weight of text (eg bold)
+    FontStyle = 'fontStyle', // font style of text (eg italic)
+    FontDecoration = 'fontDecoration', // text decoration of text (eg underline)
     Highlighting = 'highlighting', // border highlighting
     ContextMenuItems = 'contextMenuItems', // menu items to add to context menu
     AnchorMenuItems = 'anchorMenuItems',
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 12c032964..3a5f57908 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -25,6 +25,7 @@ import { styleProviderQuiz } from './StyleProviderQuiz';
 import { StyleProp } from './StyleProp';
 import './StyleProvider.scss';
 import { TagsView } from './TagsView';
+import { InkInkTool } from '../../fields/InkField';
 
 function toggleLockedPosition(doc: Doc) {
     UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
@@ -171,7 +172,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         case StyleProp.FontSize:     return StrCast(doc?.[fieldKey + 'fontSize'],   StrCast(Doc.UserDoc().fontSize));
         case StyleProp.FontFamily:   return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
         case StyleProp.FontWeight:   return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight));
-        case StyleProp.FillColor:    return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
+        case StyleProp.FontStyle:    return StrCast(doc?.[fieldKey + 'fontStyle'], StrCast(Doc.UserDoc().fontStyle));
+        case StyleProp.FontDecoration:return StrCast(doc?.[fieldKey + 'fontDecoration'], StrCast(Doc.UserDoc().fontDecoration));
+        case StyleProp.FillColor:    return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, StrCast(Doc.UserDoc()[Doc.ActiveInk === InkInkTool.Highlight ? "inkHighlighterColor": "inkFillColor"], 'transparent'))));
         case StyleProp.ShowCaption:  return hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
         case StyleProp.TitleHeight:  return Math.min(4,(docView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
         case StyleProp.ShowTitle:    return (
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index e1786d2c9..3e8138390 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -453,7 +453,7 @@ export class CollectionDockingView extends CollectionSubView() {
                 }
             }
         }
-        if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && Doc.ActiveTool !== InkTool.Ink) {
             e.stopPropagation();
         }
     };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 5d8373fc7..8b9a3e0ec 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -29,7 +29,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
     }
     _firstDocPos = { x: 0, y: 0 };
 
-    constructor(props: any) {
+    constructor(props: CollectionFreeFormInfoUIProps) {
         super(props);
         makeObservable(this);
         this._currState = this.setupStates();
@@ -163,7 +163,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
                     return presentDocs;
                 }],
                 // eslint-disable-next-line no-use-before-define
-                activePen: [() => activeTool() === InkTool.Pen, () => penMode],
+                activePen: [() => activeTool() === InkTool.Ink, () => penMode],
             },
             'documentation.png',
             () => TopBar.Instance.FlipDocumentationIcon()
@@ -187,7 +187,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
 
         const penMode = InfoState('You\'re in pen mode. Click and drag to draw your first masterpiece.', {
             // activePen: [() => activeTool() === InkTool.Eraser, () => eraserMode],
-            activePen: [() => activeTool() !== InkTool.Pen, () => viewedLink],
+            activePen: [() => activeTool() !== InkTool.Ink, () => viewedLink],
         }); // prettier-ignore
 
         // const eraserMode = InfoState('You\'re in eraser mode. Say goodbye to your first masterpiece.', {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ff711314b..f2a5780c5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -10,7 +10,7 @@ import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
 import { DocData, Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
-import { InkData, InkField, InkTool, Segment } from '../../../../fields/InkField';
+import { InkData, InkEraserTool, InkField, InkInkTool, InkTool, Segment } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { listSpec } from '../../../../fields/Schema';
@@ -36,7 +36,20 @@ import { ContextMenu } from '../../ContextMenu';
 import { InkingStroke } from '../../InkingStroke';
 import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
 import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveEraserWidth, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView, SetActiveInkColor, SetActiveInkWidth } from '../../nodes/DocumentView';
+import {
+    ActiveInkArrowEnd,
+    ActiveInkArrowStart,
+    ActiveInkDash,
+    ActiveEraserWidth,
+    ActiveInkFillColor,
+    ActiveInkBezierApprox,
+    ActiveInkColor,
+    ActiveInkWidth,
+    ActiveIsInkMask,
+    DocumentView,
+    SetActiveInkColor,
+    SetActiveInkWidth,
+} from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { FocusViewOptions } from '../../nodes/FocusViewOptions';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
@@ -497,13 +510,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 // prettier-ignore
                 const hit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
                 switch (Doc.ActiveTool) {
-                    case InkTool.Highlighter:
-                    case InkTool.Write:
-                    case InkTool.Pen:
+                    case InkTool.Ink:
                         break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
-                    case InkTool.StrokeEraser:
-                    case InkTool.SegmentEraser:
-                    case InkTool.RadiusEraser:
+                    case InkTool.Eraser:
                         this._batch = UndoManager.StartBatch('collectionErase');
                         this._eraserPts.length = 0;
                         setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, this.onEraserClick, hit !== -1);
@@ -543,7 +552,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const { points } = ge;
                 const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
                 const inkDoc = this.createInkDoc(points, B);
-                if (Doc.ActiveTool === InkTool.Write) {
+                if (Doc.ActiveInk === InkInkTool.Highlight) inkDoc[DocData].backgroundColor = 'transparent';
+                if (Doc.ActiveInk === InkInkTool.Write) {
                     this.unprocessedDocs.push(inkDoc);
                     CollectionFreeFormView.collectionsWithUnprocessedInk.add(this);
                 }
@@ -606,7 +616,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const currPoint = { X: e.clientX, Y: e.clientY };
         this._eraserPts.push([currPoint.X, currPoint.Y]);
         this._eraserPts = this._eraserPts.slice(Math.max(0, this._eraserPts.length - 5));
-        if (Doc.ActiveTool === InkTool.RadiusEraser) {
+        if (Doc.ActiveEraser === InkEraserTool.Radius) {
             const strokeMap: Map<DocumentView, number[]> = this.getRadiusEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint);
             strokeMap.forEach((intersects, stroke) => {
                 if (!this._deleteList.includes(stroke)) {
@@ -614,13 +624,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     SetActiveInkWidth(StrCast(stroke.Document.stroke_width?.toString()) || '1');
                     SetActiveInkColor(StrCast(stroke.Document.color?.toString()) || 'black');
                     const segments = this.radiusErase(stroke, intersects.sort());
-                    segments?.forEach(segment =>
-                        this.forceStrokeGesture(
-                            e,
-                            Gestures.Stroke,
-                            segment.reduce((data, curve) => [...data, ...curve.points.map(p => stroke.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
-                        )
-                    );
+                    segments?.forEach(segment => {
+                        const points = segment.reduce((data, curve) => [...data, ...curve.points.map(p => stroke.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]);
+                        const bounds = InkField.getBounds(points);
+                        const B = this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height);
+                        const inkDoc = this.createInkDoc(points, B);
+                        ['color', 'fillColor', 'stroke_width', 'stroke_dash', 'stroke_bezier'].forEach(field => {
+                            inkDoc[DocData][field] = stroke.dataDoc[field];
+                        });
+                        this.addDocument(inkDoc);
+                    });
                 }
                 stroke.layoutDoc.opacity = 0;
                 stroke.layoutDoc.dontIntersect = true;
@@ -632,7 +645,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     SetActiveInkWidth(StrCast(intersect.inkView.Document.stroke_width?.toString()) || '1');
                     SetActiveInkColor(StrCast(intersect.inkView.Document.color?.toString()) || 'black');
                     // create a new curve by appending all curves of the current segment together in order to render a single new stroke.
-                    if (Doc.ActiveTool !== InkTool.StrokeEraser) {
+                    if (Doc.ActiveEraser !== InkEraserTool.Stroke) {
                         // this._eraserLock++;
                         const segments = this.segmentErase(intersect.inkView, intersect.t); // intersect.t is where the eraser intersected the ink stroke - want to remove the segment that starts at the intersection just before this t value and goes to the one just after it
                         const newStrokes = segments?.map(segment => {
@@ -1195,14 +1208,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 y: B.y - inkWidth / 2,
                 _width: B.width + inkWidth,
                 _height: B.height + inkWidth,
-                stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore
+                stroke_showLabel: !BoolCast(Doc.UserDoc().activeHideTextLabels)}, // prettier-ignore
             inkWidth,
             ActiveInkColor(),
             ActiveInkBezierApprox(),
-            ActiveFillColor(),
-            ActiveArrowStart(),
-            ActiveArrowEnd(),
-            ActiveDash(),
+            ActiveInkFillColor(),
+            ActiveInkArrowStart(),
+            ActiveInkArrowEnd(),
+            ActiveInkDash(),
             ActiveIsInkMask()
         );
     };
@@ -1235,14 +1248,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     y: B.y - inkWidth / 2,
                     _width: B.width + inkWidth,
                     _height: B.height + inkWidth,
-                    stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore
+                    stroke_showLabel: BoolCast(Doc.UserDoc().activeHideTextLabels)}, // prettier-ignore
                 inkWidth,
                 opts.autoColor ? stroke[1] : ActiveInkColor(),
                 ActiveInkBezierApprox(),
-                stroke[2] === 'none' ? ActiveFillColor() : stroke[2],
-                ActiveArrowStart(),
-                ActiveArrowEnd(),
-                ActiveDash(),
+                stroke[2] === 'none' ? ActiveInkFillColor() : stroke[2],
+                ActiveInkArrowStart(),
+                ActiveInkArrowEnd(),
+                ActiveInkDash(),
                 ActiveIsInkMask()
             );
             this._drawing.push(inkDoc);
@@ -2195,7 +2208,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     width: `${100 / this.nativeDimScaling}%`,
                     height: this._props.getScrollHeight?.() ?? `${100 / this.nativeDimScaling}%`,
                 }}>
-                {Doc.ActiveTool === InkTool.RadiusEraser && this._showEraserCircle && (
+                {Doc.ActiveTool === InkTool.Eraser && Doc.ActiveEraser === InkEraserTool.Radius && this._showEraserCircle && (
                     <div
                         onPointerMove={this.onCursorMove}
                         style={{
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c865c681d..ddc50871d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -690,7 +690,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
                 }}
                 style={{
                     overflow: StrCast(this._props.Document._overflow),
-                    cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer',
+                    cursor: Doc.ActiveTool === InkTool.Ink || this._visible ? 'crosshair' : 'pointer',
                 }}
                 onDragOver={e => e.preventDefault()}
                 onScroll={e => {
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 013b18a97..40144c4ce 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -3,7 +3,7 @@ import { Colors } from 'browndash-components';
 import { runInAction } from 'mobx';
 import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
-import { InkTool } from '../../../fields/InkField';
+import { InkEraserTool, InkInkTool, InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
@@ -16,21 +16,20 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { UndoManager, undoable } from '../../util/UndoManager';
 import { GestureOverlay } from '../GestureOverlay';
 import { InkTranscription } from '../InkTranscription';
-import { InkingStroke } from '../InkingStroke';
 import { PropertiesView } from '../PropertiesView';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
 import {
     ActiveEraserWidth,
-    ActiveFillColor,
+    ActiveInkFillColor,
     ActiveInkColor,
-    ActiveInkHideTextLabels,
+    ActiveHideTextLabels,
     ActiveInkWidth,
     ActiveIsInkMask,
     DocumentView,
-    SetActiveFillColor,
+    SetActiveInkFillColor,
     SetActiveInkColor,
-    SetActiveInkHideTextLabels,
+    SetactiveHideTextLabels,
     SetActiveInkWidth,
     SetActiveIsInkMask,
     SetEraserWidth,
@@ -60,20 +59,21 @@ ScriptingGlobals.add(function setView(view: string, getSelected: boolean) {
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
     const selectedViews = DocumentView.Selected();
+    const selectedDoc = selectedViews.lastElement()?.Document;
+    const defaultFill = selectedDoc?._layout_isSvg ? () => StrCast(selectedDoc[DocData].fillColor) : !Doc.ActiveTool || Doc.ActiveTool === InkTool.None ? () => StrCast(Doc.UserDoc().textBackgroundColor, 'transparent') : () => ActiveInkFillColor();
+    const setDefaultFill = !Doc.ActiveTool || Doc.ActiveTool === InkTool.None  ? (c: string) => { Doc.UserDoc().textBackgroundColor = c; }: SetActiveInkFillColor; // prettier-ignore
     if (Doc.ActiveTool !== InkTool.None && !selectedViews.lastElement()?.Document._layout_isSvg) {
-        if (checkResult) {
-            return ActiveFillColor();
-        }
-        SetActiveFillColor(color ?? 'transparent');
+        if (checkResult) return defaultFill();
+        setDefaultFill(color ?? 'transparent');
     } else if (selectedViews.length) {
         if (checkResult) {
             const selView = selectedViews.lastElement();
             const fieldKey = selView.Document._layout_isSvg ? 'fillColor' : 'backgroundColor';
             const layoutFrameNumber = Cast(selView.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
-            return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent';
+            return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] || defaultFill();
         }
-        selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent');
+        setDefaultFill(color ?? 'transparent');
         selectedViews.forEach(dv => {
             const fieldKey = dv.Document._layout_isSvg ? 'fillColor' : 'backgroundColor';
             const layoutFrameNumber = Cast(dv.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
@@ -92,10 +92,13 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
     } else {
         const selected = DocumentView.SelectedDocs().length ? DocumentView.SelectedDocs() : LinkManager.Instance.currentLink ? [LinkManager.Instance.currentLink] : [];
         if (checkResult) {
-            return selected.lastElement()?._backgroundColor ?? 'transparent';
+            return selected.lastElement()?._backgroundColor ?? defaultFill();
         }
-        SetActiveFillColor(color ?? 'transparent');
-        selected.forEach(doc => { doc[DocData].backgroundColor = color; }); // prettier-ignore
+        if (!selected.length) setDefaultFill(color ?? 'transparent');
+        else
+            selected.forEach(doc => {
+                doc[DocData][doc._layout_isSvg ? 'fillColor' : 'backgroundColor'] = color;
+            });
     }
     return '';
 });
@@ -330,7 +333,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
     return undefined;
 });
 
-type attrname = 'noAutoLink' | 'dictation' | 'fitBox' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
+type attrname = 'noAutoLink' | 'dictation' | 'fitBox' | 'bold' | 'italic' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
 type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => unknown }];
 
 // eslint-disable-next-line prefer-arrow-callback
@@ -360,10 +363,10 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?:
                         toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
         ['bold',      { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold')),
                         toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => { Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold'; }}],
-        ['italics',   { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics')),
-                        toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => { Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics'; }}],
-        ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline')),
-                        toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => { Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline'; } }]]
+        ['italic',    { checkResult: () => (editorView ? RichTextMenu.Instance?.italic ?? false : (Doc.UserDoc().fontStyle === 'italic')),
+                        toggle: editorView ? RichTextMenu.Instance?.toggleItalic : () => { Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italic' ? undefined : 'italic'; }}],
+        ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().fontDecoration === 'underline')),
+                        toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => { Doc.UserDoc().fontDecoration = Doc.UserDoc().fontDecoration === 'underline' ? undefined : 'underline'; } }]]
 
     const map = new Map(attrs.concat(alignments).concat(listings));
     if (checkResult) {
@@ -373,43 +376,46 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?:
     return undefined;
 });
 
-function setActiveTool(toolIn: InkTool | Gestures, keepPrim: boolean, checkResult?: boolean) {
+function setActiveTool(tool: InkTool | InkEraserTool | InkInkTool | Gestures, keepPrim: boolean, checkResult?: boolean) {
     InkTranscription.Instance?.createInkGroup();
-    const tool = toolIn === InkTool.Eraser ? Doc.UserDoc().activeEraserTool : toolIn;
     if (checkResult) {
-        return ((Doc.ActiveTool === tool || (Doc.UserDoc().activeEraserTool === tool && (tool === toolIn || Doc.ActiveTool === tool))) && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool
-            ? GestureOverlay.Instance?.KeepPrimitiveMode || ![Gestures.Circle, Gestures.Line, Gestures.Rectangle].includes(tool as Gestures)
+        return Doc.ActiveTool === tool || Doc.ActiveEraser === tool || Doc.ActiveInk === tool || SnappingManager.InkShape === tool
+            ? true //SnappingManager.KeepGestureMode || ![Gestures.Circle, Gestures.Line, Gestures.Rectangle].includes(tool as Gestures)
             : false;
     }
     runInAction(() => {
+        const eraserTool = tool === InkTool.Eraser ? Doc.ActiveEraser : [InkEraserTool.Stroke, InkEraserTool.Radius, InkEraserTool.Segment].includes(tool as InkEraserTool) ? (tool as InkEraserTool) : undefined;
+        const inkTool = tool === InkTool.Ink ? Doc.ActiveInk : [InkInkTool.Pen, InkInkTool.Write, InkInkTool.Highlight].includes(tool as InkInkTool) ? (tool as InkInkTool) : undefined;
         if (GestureOverlay.Instance) {
-            GestureOverlay.Instance.KeepPrimitiveMode = keepPrim;
+            SnappingManager.SetKeepGestureMode(keepPrim);
         }
         if (Object.values(Gestures).includes(tool as Gestures)) {
-            if (GestureOverlay.Instance.InkShape === tool && !keepPrim) {
+            if (SnappingManager.InkShape === tool && !keepPrim) {
                 Doc.ActiveTool = InkTool.None;
-                GestureOverlay.Instance.InkShape = undefined;
+                SnappingManager.SetInkShape(undefined);
             } else {
-                Doc.ActiveTool = InkTool.Pen;
-                GestureOverlay.Instance.InkShape = tool as Gestures;
+                Doc.ActiveTool = InkTool.Ink;
+                SnappingManager.SetInkShape(tool as Gestures);
             }
-        } else if (tool) {
-            if (Doc.UserDoc().ActiveTool === tool) {
+        } else if (eraserTool) {
+            if (Doc.ActiveTool === InkTool.Eraser && Doc.ActiveTool === tool) {
                 Doc.ActiveTool = InkTool.None;
             } else {
-                if ([InkTool.StrokeEraser, InkTool.RadiusEraser, InkTool.SegmentEraser].includes(tool as InkTool)) {
-                    Doc.UserDoc().activeEraserTool = tool;
-                }
-                // pen or eraser
-                if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) {
-                    Doc.ActiveTool = InkTool.None;
-                } else {
-                    Doc.ActiveTool = tool as InkTool;
-                    GestureOverlay.Instance.InkShape = undefined;
-                }
+                Doc.ActiveEraser = eraserTool;
+                Doc.ActiveTool = InkTool.Eraser;
+                SnappingManager.SetInkShape(undefined);
+            }
+        } else if (inkTool) {
+            if (Doc.ActiveTool === InkTool.Ink && Doc.ActiveTool === tool) {
+                Doc.ActiveTool = InkTool.None;
+            } else {
+                Doc.ActiveInk = inkTool;
+                Doc.ActiveTool = InkTool.Ink;
+                SnappingManager.SetInkShape(undefined);
             }
         } else {
-            Doc.ActiveTool = InkTool.None;
+            if ((Doc.ActiveTool === tool || !tool) && !keepPrim) Doc.ActiveTool = InkTool.None;
+            else Doc.ActiveTool = tool as InkTool;
         }
     });
     return undefined;
@@ -419,44 +425,44 @@ ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');
 
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function activeEraserTool() {
-    return StrCast(Doc.UserDoc().activeEraserTool, InkTool.StrokeEraser);
+    return StrCast(Doc.UserDoc().activeEraserTool, InkEraserTool.Stroke);
 }, 'returns the current eraser tool');
 
 // toggle: Set overlay status of selected document
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor' | 'eraserWidth', value: string | number, checkResult?: boolean) {
-    const selected = DocumentView.SelectedDocs().lastElement() ?? Doc.UserDoc();
+    const selected = DocumentView.SelectedDocs().lastElement();
     // prettier-ignore
     const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor' | 'eraserWidth', { checkResult: () => number|boolean|string|undefined; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
         ['inkMask', {
             checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())),
             setInk: (doc: Doc) => { doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask; },
-            setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
+            setMode: () => SetActiveIsInkMask(value ? true : false)
         }],
         ['labels', {
-            checkResult: () => ((selected?._stroke_showLabel ? BoolCast(selected[DocData].stroke_showLabel) : ActiveInkHideTextLabels())),
-            setInk: (doc: Doc) => { doc[DocData].stroke_showLabel = !doc.stroke_showLabel; },
-            setMode: () => selected?.type !== DocumentType.INK && SetActiveInkHideTextLabels(!ActiveInkHideTextLabels()),
+            checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_showLabel) : !ActiveHideTextLabels())),
+            setInk: (doc: Doc) => { doc[DocData].stroke_showLabel = value; },
+            setMode: () => SetactiveHideTextLabels(value? false : true),
         }],
         ['fillColor', {
-            checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"),
+            checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveInkFillColor() ?? "transparent"),
             setInk: (doc: Doc) => { doc[DocData].fillColor = StrCast(value); },
-            setMode: () => SetActiveFillColor(StrCast(value)),
+            setMode: () => SetActiveInkFillColor(StrCast(value)),
         }],
         [ 'strokeWidth', {
             checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()),
             setInk: (doc: Doc) => { doc[DocData].stroke_width = NumCast(value); },
-            setMode: () => { SetActiveInkWidth(value.toString()); selected?.type === DocumentType.INK && setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
+            setMode: () => SetActiveInkWidth(value.toString()),
         }],
         ['strokeColor', {
             checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()),
             setInk: (doc: Doc) => { doc[DocData].color = String(value); },
-            setMode: () => { SetActiveInkColor(StrCast(value)); selected?.type === DocumentType.INK &&  setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
+            setMode: () => SetActiveInkColor(StrCast(value))
         }],
         [ 'eraserWidth', {
             checkResult: () => ActiveEraserWidth() === 0 ? 1 : ActiveEraserWidth(),
             setInk: (doc: Doc) => { },
-            setMode: () => { SetEraserWidth(+value);},
+            setMode: () => SetEraserWidth(+value),
         }]
     ]);
 
diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
index 3eb99f47a..8115bafbf 100644
--- a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
+++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable jsx-a11y/no-static-element-interactions */
-/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { action } from 'mobx';
 import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
@@ -35,10 +33,10 @@ export function ButtonMenu() {
             <div
                 className="newLightboxView-penBtn"
                 title="toggle pen annotation"
-                style={{ background: Doc.ActiveTool === InkTool.Pen ? 'white' : undefined }}
+                style={{ background: Doc.ActiveTool === InkTool.Ink ? 'white' : undefined }}
                 onClick={e => {
                     e.stopPropagation();
-                    Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
+                    Doc.ActiveTool = Doc.ActiveTool === InkTool.Ink ? InkTool.None : InkTool.Ink;
                 }}
             />
             <div
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index d51b1cd3a..a9edc90e7 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,7 +1,8 @@
+import { Colors } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { OmitKeys } from '../../../ClientUtils';
+import { DashColor, OmitKeys } from '../../../ClientUtils';
 import { numberRange } from '../../../Utils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { TransitionTimer } from '../../../fields/DocSymbols';
@@ -296,12 +297,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     transition: this.DataTransition(),
                     zIndex: this.ZIndex,
                     display: this.Width ? undefined : 'none',
+                    mixBlendMode: DashColor(StrCast(this.layoutDoc[this.layoutDoc._layout_isSvg ? 'fillColor' : 'backgroundColor'], Colors.WHITE)).alpha() !== 1 ? 'multiply' : undefined,
                 }}>
                 {this.RenderCutoffProvider(this.Document) ? (
                     <div style={{ position: 'absolute', width: this.PanelWidth(), height: this.PanelHeight(), background: 'lightGreen' }} />
                 ) : (
                     <DocumentView
-                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore
                         Document={this._props.Document}
                         renderDepth={this._props.renderDepth}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index d79a37181..7925410e2 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -73,7 +73,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return <div className="dataVizBox-annotationLayer" style={{ height: this._props.PanelHeight(), width: this._props.PanelWidth() }} ref={this._annotationLayer} />;
     }
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -453,7 +453,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         if ((this.Document._freeform_scale || 1) !== 1) return;
-        if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
             this._props.select(false);
             MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
             this._marqueeing = [e.clientX, e.clientY];
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index eb7f333b9..30f9e6363 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -380,7 +380,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     };
 
     onPointerMove = (e: PointerEvent): void => {
-        if (e.buttons !== 1 || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
+        if (e.buttons !== 1 || Doc.ActiveTool === InkTool.Ink) return;
 
         if (!ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
             this.cleanupPointerEvents();
@@ -1560,55 +1560,45 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         } else func();
     }
 }
-
-export function ActiveFillColor(): string { 
-    const dv = DocumentView.Selected().lastElement() ?.Document._layout_isSvg ? DocumentView.Selected().lastElement() : undefined;
-    return StrCast(dv?.Document.fillColor, StrCast(ActiveInkPen()?.activeFillColor, "")); 
-} // prettier-ignore
-export function ActiveInkPen(): Doc { return Doc.UserDoc(); } // prettier-ignore
-export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, 'black'); } // prettier-ignore
-export function ActiveIsInkMask(): boolean { return BoolCast(ActiveInkPen()?.activeIsInkMask, false); } // prettier-ignore
-export function ActiveInkHideTextLabels(): boolean { return BoolCast(ActiveInkPen().activeInkHideTextLabels, false); } // prettier-ignore
-export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ''); } // prettier-ignore
-export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ''); } // prettier-ignore
-export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.activeArrowScale, 1); } // prettier-ignore
-export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, '0'); } // prettier-ignore
-export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); } // prettier-ignore
-export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } // prettier-ignore
-export function ActiveEraserWidth(): number { return Number(ActiveInkPen()?.eraserWidth ?? 25); } // prettier-ignore
-
+export function ActiveHideTextLabels(): boolean { return BoolCast(Doc.UserDoc().activeHideTextLabels, false); } // prettier-ignore
+export function ActiveIsInkMask(): boolean { return BoolCast(Doc.UserDoc()?.activeIsInkMask, false); } // prettier-ignore
+export function ActiveEraserWidth(): number { return Number(Doc.UserDoc()?.activeEraserWidth ?? 25); } // prettier-ignore
+
+export function ActiveInkFillColor(): string { return StrCast(Doc.UserDoc()?.[`active${Doc.ActiveInk}Fill`]); } // prettier-ignore
+export function ActiveInkColor(): string { return StrCast(Doc.UserDoc()?.[`active${Doc.ActiveInk}Color`], 'black'); } // prettier-ignore
+export function ActiveInkArrowStart(): string { return StrCast(Doc.UserDoc()?.[`active${Doc.ActiveInk}ArrowStart`], ''); } // prettier-ignore
+export function ActiveInkArrowEnd(): string { return StrCast(Doc.UserDoc()?.[`active${Doc.ActiveInk}ArrowEnd`], ''); } // prettier-ignore
+export function ActiveInkArrowScale(): number { return NumCast(Doc.UserDoc()?.[`active${Doc.ActiveInk}ArrowScale`], 1); } // prettier-ignore
+export function ActiveInkDash(): string { return StrCast(Doc.UserDoc()?.[`active${Doc.ActiveInk}Dash`], '0'); } // prettier-ignore
+export function ActiveInkWidth(): number { return Number(Doc.UserDoc()?.[`active${Doc.ActiveInk}Width`]); } // prettier-ignore
+export function ActiveInkBezierApprox(): string { return StrCast(Doc.UserDoc()[`active${Doc.ActiveInk}Bezier`]); } // prettier-ignore
+
+export function SetActiveIsInkMask(value: boolean) { Doc.UserDoc() && (Doc.UserDoc().activeIsInkMask = value); } // prettier-ignore
+export function SetactiveHideTextLabels(value: boolean) { Doc.UserDoc() && (Doc.UserDoc().activeHideTextLabels = value); } // prettier-ignore
+export function SetEraserWidth(width: number): void { Doc.UserDoc() && (Doc.UserDoc().activeEraserWidth = width); } // prettier-ignore
 export function SetActiveInkWidth(width: string): void {
-    !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
+    !isNaN(parseInt(width)) && Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}Width`] = width);
 }
-export function SetActiveBezierApprox(bezier: string): void {
-    ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier);
+export function SetActiveInkBezierApprox(bezier: string): void {
+    Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}Bezier`] = isNaN(parseInt(bezier)) ? '' : bezier);
 }
 export function SetActiveInkColor(value: string) {
-    ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
-}
-export function SetActiveIsInkMask(value: boolean) {
-    ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
-}
-export function SetActiveInkHideTextLabels(value: boolean) {
-    ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value);
-}
-export function SetActiveFillColor(value: string) {
-    ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
+    Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}Color`] = value);
 }
-export function SetActiveArrowStart(value: string) {
-    ActiveInkPen() && (ActiveInkPen().activeArrowStart = value);
+export function SetActiveInkFillColor(value: string) {
+    Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}Fill`] = value);
 }
-export function SetActiveArrowEnd(value: string) {
-    ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value);
+export function SetActiveInkArrowStart(value: string) {
+    Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}ArrowStart`] = value);
 }
-export function SetActiveArrowScale(value: number) {
-    ActiveInkPen() && (ActiveInkPen().activeArrowScale = value);
+export function SetActiveInkArrowEnd(value: string) {
+    Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}ArrowEnd`] = value);
 }
-export function SetActiveDash(dash: string): void {
-    !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash);
+export function SetActiveInkArrowScale(value: number) {
+    Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}ArrowScale`] = value);
 }
-export function SetEraserWidth(width: number): void {
-    ActiveInkPen() && (ActiveInkPen().eraserWidth = width);
+export function SetActiveInkDash(dash: string): void {
+    !isNaN(parseInt(dash)) && Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}`] = dash);
 }
 
 // eslint-disable-next-line prefer-arrow-callback
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss
index ab03a2318..2405889cf 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss
@@ -13,6 +13,9 @@
 .fonticonbox {
     margin: auto;
     width: 100%;
+    .formLabel {
+        height: 5px;
+    }
 }
 .menuButton {
     height: 100%;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index b45774a75..8c138c2ee 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -4,7 +4,7 @@ import { Button, ColorPicker, Dropdown, DropdownType, IconButton, IListItemProps
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { ClientUtils, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
+import { ClientUtils, DashColor, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
 import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction } from '../../../../Utils';
@@ -21,6 +21,8 @@ import { FieldView, FieldViewProps } from '../FieldView';
 import { OpenWhere } from '../OpenWhere';
 import './FontIconBox.scss';
 import TrailsIcon from './TrailsIcon';
+import { InkTool } from '../../../../fields/InkField';
+import { ScriptField } from '../../../../fields/ScriptField';
 
 export enum ButtonType {
     TextButton = 'textBtn',
@@ -126,7 +128,8 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                 background={SnappingManager.userBackgroundColor}
                 numberDropdownType={type}
                 showPlusMinus={false}
-                tooltip={this.label}
+                formLabel={(StrCast(this.Document.title).startsWith(' ') ? '\u00A0' : '') + StrCast(this.Document.title)}
+                tooltip={StrCast(this.Document.toolTip, this.label)}
                 type={Type.PRIM}
                 min={NumCast(this.dataDoc.numBtnMin, 0)}
                 max={NumCast(this.dataDoc.numBtnMax, 100)}
@@ -148,65 +151,80 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         return false;
     };
 
+    /**
+     * Displays custom dropdown menu for fonts -- this is a HACK -- fix for generality, don't copy
+     */
+    handleFontDropdown = (script: () => string, buttonList: string[]) => {
+        // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
+        return {
+            buttonList,
+            jsx: undefined,
+            selectedVal: script(),
+            getStyle: (val: string) => ({ fontFamily: val }),
+        };
+    };
+    /**
+     * Displays custom dropdown menu for view selection -- this is a HACK -- fix for generality, don't copy
+     */
+    handleViewDropdown = (script: ScriptField, buttonList: string[]) => {
+        const selected = Array.from(script?.script.run({ _readOnly_: true }).result as Doc[]);
+        const noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Card, CollectionViewType.Carousel3D, CollectionViewType.Carousel, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
+        return selected.length === 1 && selected[0].type === DocumentType.COL
+            ? {
+                  buttonList: buttonList.filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value as CollectionViewType)),
+                  getStyle: undefined,
+                  selectedVal: StrCast(selected[0]._type_collection),
+              }
+            : {
+                  jsx: selected.length ? (
+                      <Popup
+                          icon={<FontAwesomeIcon size="1x" icon={selected.length > 1 ? 'caret-down' : (Doc.toIcon(selected.lastElement()) as IconProp)} />}
+                          text={selected.length === 1 ? ClientUtils.cleanDocumentType(StrCast(selected[0].type) as DocumentType) : selected.length + ' selected'}
+                          type={Type.TERT}
+                          color={SnappingManager.userColor}
+                          background={SnappingManager.userVariantColor}
+                          popup={<SelectedDocView selectedDocs={selected} />}
+                          fillWidth
+                      />
+                  ) : (
+                      <Button
+                          text={`${Doc.ActiveTool === InkTool.None ? 'Text box' : Doc.ActiveInk} defaults`} //
+                          type={Type.TERT}
+                          color={SnappingManager.userColor}
+                          background={SnappingManager.userVariantColor}
+                          fillWidth
+                          inactive
+                      />
+                  ),
+              };
+    };
+
     /**
      * Dropdown list
      */
     @computed get dropdownListButton() {
         const script = ScriptCast(this.Document.script);
-
-        let noviceList: string[] = [];
-        let text: string | undefined;
-        let getStyle: (val: string) => { [key: string]: string } = () => ({});
-        let icon: IconProp = 'caret-down';
-        const isViewDropdown = script?.script.originalScript.startsWith('{ return setView');
-        if (isViewDropdown) {
-            const selected = Array.from(script?.script.run({ _readOnly_: true }).result as Doc[]);
-            // const selected = DocumentView.SelectedDocs();
-            if (selected.lastElement()) {
-                if (StrCast(selected.lastElement().type) === DocumentType.COL) {
-                    text = StrCast(selected.lastElement()._type_collection);
-                } else {
-                    if (selected.length > 1) {
-                        text = selected.length + ' selected';
-                    } else {
-                        text = ClientUtils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType, '' as CollectionViewType);
-                        icon = Doc.toIcon(selected.lastElement());
-                    }
-                    return (
-                        <Popup
-                            icon={<FontAwesomeIcon size="1x" icon={icon} />}
-                            text={text}
-                            type={Type.TERT}
-                            color={SnappingManager.userColor}
-                            background={SnappingManager.userVariantColor}
-                            popup={<SelectedDocView selectedDocs={selected} />}
-                            fillWidth
-                        />
-                    );
-                }
-            } else {
-                return <Button text="None Selected" type={Type.TERT} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} fillWidth inactive />;
-            }
-            noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Card, CollectionViewType.Carousel3D, CollectionViewType.Carousel, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
-        } else {
-            text = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result as string;
-            // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
-            if (this.Document.title === 'Font') getStyle = (val: string) => ({ fontFamily: val }); // bcz: major hack to style the font dropdown items --- needs to become part of the dropdown's metadata
-        }
+        const selectedFunc = () => script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result as string;
+        const { buttonList, selectedVal, getStyle, jsx } = (() => {
+            switch (this.Document.title) {
+                case 'Font':         return this.handleFontDropdown(selectedFunc, this.buttonList);
+                case 'Perspective':  return this.handleViewDropdown(script, this.buttonList);
+                default:             return { buttonList: this.buttonList, selectedVal: selectedFunc(), jsx: undefined, getStyle: undefined };
+            } // prettier-ignore
+        })();
+        if (jsx) return jsx;
 
         // Get items to place into the list
-        const list: IListItemProps[] = this.buttonList
-            .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value))
-            .map(value => ({
-                text: typeof value === 'string' ? value.charAt(0).toUpperCase() + value.slice(1) : StrCast(DocCast(value)?.title),
-                val: value,
-                style: getStyle(value),
-                // shortcut: '#',
-            }));
+        const list: IListItemProps[] = buttonList.map(value => ({
+            text: typeof value === 'string' ? value.charAt(0).toUpperCase() + value.slice(1) : StrCast(DocCast(value)?.title),
+            val: value,
+            style: getStyle?.(value),
+            // shortcut: '#',
+        }));
 
         return (
             <Dropdown
-                selectedVal={text}
+                selectedVal={selectedVal}
                 setSelectedVal={undoable(value => script.script.run({ this: this.Document, value }), `dropdown select ${this.label}`)}
                 color={SnappingManager.userColor}
                 background={SnappingManager.userVariantColor}
@@ -215,7 +233,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                 dropdownType={DropdownType.SELECT}
                 onItemDown={this.dropdownItemDown}
                 items={list}
-                tooltip={this.label}
+                tooltip={StrCast(this.Document.toolTip, this.label)}
                 fillWidth
             />
         );
@@ -235,51 +253,53 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         const tooltip: string = StrCast(this.Document.toolTip);
 
         return (
-            <ColorPicker
-                setSelectedColor={value => {
-                    if (!this.colorBatch) this.colorBatch = UndoManager.StartBatch(`Set ${tooltip} color`);
-                    this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false });
-                }}
-                setFinalColor={value => {
-                    this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false });
-                    this.colorBatch?.end();
-                    this.colorBatch = undefined;
-                }}
-                defaultPickerType="Classic"
-                selectedColor={curColor}
-                type={Type.PRIM}
-                color={color}
-                background={SnappingManager.userBackgroundColor}
-                icon={this.Icon(color) ?? undefined}
-                tooltip={tooltip}
-                label={this.label}
-            />
+            <div
+                onPointerDown={e => {
+                    e.stopPropagation();
+                }}>
+                <ColorPicker
+                    setSelectedColor={value => {
+                        if (!this.colorBatch) this.colorBatch = UndoManager.StartBatch(`Set ${tooltip} color`);
+                        this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false });
+                    }}
+                    setFinalColor={value => {
+                        this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false });
+                        this.colorBatch?.end();
+                        this.colorBatch = undefined;
+                    }}
+                    defaultPickerType="Classic"
+                    selectedColor={curColor}
+                    type={Type.PRIM}
+                    color={color}
+                    background={SnappingManager.userBackgroundColor}
+                    icon={this.Icon(color) ?? undefined}
+                    tooltip={tooltip}
+                    label={this.label}
+                />
+            </div>
         );
     }
     @computed get multiToggleButton() {
-        // Determine the type of toggle button
-        const tooltip: string = StrCast(this.Document.toolTip);
+        const tooltip = StrCast(this.Document.toolTip);
 
         const script = ScriptCast(this.Document.onClick)?.script;
         const toggleStatus = script?.run({ this: this.Document, value: undefined, _readOnly_: true }).result as boolean;
 
-        // Colors
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
-        const background = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string;
         const items = DocListCast(this.dataDoc.data);
         const selectedItems = items.filter(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result).map(item => StrCast(item.toolType));
 
         return (
             <MultiToggle
-                tooltip={`Toggle ${tooltip}`}
+                tooltip={`Click to Toggle ${tooltip} or select new option`}
                 type={Type.PRIM}
                 color={color}
-                background={background === SnappingManager.userBackgroundColor ? undefined : background}
+                background={undefined}
                 multiSelect={true}
                 onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: this.Document, value: undefined, _readOnly_: false }))}
                 isToggle={false}
                 toggleStatus={toggleStatus}
-                label={this.label}
+                label={selectedItems.length === 1 ? selectedItems[0] : this.label}
                 items={items.map(item => ({
                     icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as IconProp} color={color} />,
                     tooltip: StrCast(item.toolTip),
@@ -292,7 +312,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                     //   it would be better to pas the 'added' flag to the callback script, but our script generator from currentUserUtils makes it hard to define
                     //   arbitrary parameter variables (but it could be done as a special case or with additional effort when creating the sript)
                     const itemsChanged = items.filter(item => (val instanceof Array ? val.includes(item.toolType as string | number) : item.toolType === val));
-                    itemsChanged.forEach(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, _added_: added, itemDoc, _readOnly_: false }));
+                    itemsChanged.forEach(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, _added_: added, value: toggleStatus, itemDoc, _readOnly_: false }));
                 }}
             />
         );
@@ -310,17 +330,19 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         const toggleStatus = (script?.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result as boolean) ?? false;
         // Colors
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
-        // const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
 
+        // bcz: ink shapes are tri-state - off, one-shot, and on.  Need to update Toggle buttons to allow this and update currentUserUtils to set the tri-state on the Doc
+        // in the meantime, if the button matches a tool type that is not locked, we want to set the background color to something distinct.
+        const inkShapeHack = ((this.Document.toolType && this.Document.toolType === SnappingManager.InkShape) || this.Document.toolType === Doc.ActiveTool) && !SnappingManager.KeepGestureMode;
         return (
             <Toggle
                 tooltip={`Toggle ${tooltip}`}
                 toggleType={ToggleType.BUTTON}
-                type={Type.PRIM}
+                type={inkShapeHack ? Type.TERT : Type.PRIM}
                 toggleStatus={toggleStatus}
                 text={buttonText}
                 color={color}
-                // background={SnappingManager.userBackgroundColor}
+                background={inkShapeHack ? DashColor(SnappingManager.userBackgroundColor).darken(0.05).toString() : undefined}
                 icon={this.Icon(color)!}
                 label={this.label}
                 onPointerDown={e =>
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 0dfc0ec28..ff879a2ab 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -338,7 +338,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @computed get nativeSize() {
         TraceMobx();
-        if (this.paths.length && this.paths[0].includes('icon-hi')) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0}
+        if (this.paths.length && this.paths[0].includes('icon-hi')) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0 };
         const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500));
         const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500));
         const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1);
@@ -465,13 +465,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     marqueeDown = (e: React.PointerEvent) => {
         if (!this.dataDoc[this.fieldKey]) {
             this.chooseImage();
-        } else if (
-            !e.altKey &&
-            e.button === 0 &&
-            NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) &&
-            this._props.isContentActive() &&
-            ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)
-        ) {
+        } else if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
             setupMoveUpEvents(
                 this,
                 e,
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index de51f6447..3bf7de2fe 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -776,7 +776,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     // starts marquee selection
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
             setupMoveUpEvents(
                 this,
                 e,
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index a5788d02a..9ba4f8ead 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -44,6 +44,7 @@ import { LinkInfo } from './LinkDocPreview';
 import { OpenWhere } from './OpenWhere';
 import './WebBox.scss';
 
+// eslint-disable-next-line @typescript-eslint/no-require-imports
 const { CreateImage } = require('./WebBoxRenderer');
 
 @observer
@@ -335,7 +336,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 ele = document.createElement('div');
                 ele.append(contents);
             }
-        } catch (e) {
+        } catch {
             /* empty */
         }
         const visibleAnchor = this._getAnchor(this._savedAnnotations, true);
@@ -506,7 +507,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         let href: Opt<string>;
         try {
             href = iframe?.contentWindow?.location.href;
-        } catch (e) {
+        } catch {
             runInAction(() => this._warning++);
             href = undefined;
         }
@@ -713,7 +714,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     this._webUrl = this._url;
                 }
             }
-        } catch (e) {
+        } catch {
             console.log('WebBox URL error:' + this._url);
         }
         return true;
@@ -805,7 +806,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             sel.empty(); // Chrome
         else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
         this.marqueeing = [e.clientX, e.clientY];
-        if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -855,7 +856,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     })}
                     contentEditable
                     onPointerDown={this.webClipDown}
-                    // eslint-disable-next-line react/no-danger
                     dangerouslySetInnerHTML={{ __html: field.html }}
                 />
             );
@@ -1031,7 +1031,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 {this.inlineTextAnnotations
                     .sort((a, b) => NumCast(a.y) - NumCast(b.y))
                     .map(anno => (
-                        // eslint-disable-next-line react/jsx-props-no-spreading
                         <Annotation {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} containerDataDoc={this.dataDoc} annoDoc={anno} key={`${anno[Id]}-annotation`} />
                     ))}
             </div>
@@ -1042,7 +1041,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
     renderAnnotations = (childFilters: () => string[]) => (
         <CollectionFreeFormView
-            // eslint-disable-next-line react/jsx-props-no-spreading
             {...this._props}
             setContentViewBox={this.setInnerContent}
             NativeWidth={returnZero}
@@ -1217,7 +1215,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this._props.PanelWidth()}%` }}>
                     <SidebarAnnos
                         ref={this._sidebarRef}
-                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                         fieldKey={this.fieldKey + '_' + this._urlHash}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 659cda9dd..905c69bb8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -135,10 +135,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     @observable _showSidebar = false;
 
-    @computed get fontColor() {  return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor) as string; } // prettier-ignore
-    @computed get fontSize()  {  return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) as string; } // prettier-ignore
-    @computed get fontFamily() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) as string; } // prettier-ignore
-    @computed get fontWeight() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontWeight) as string;  } // prettier-ignore
+    @computed get fontColor()      { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor) as string; } // prettier-ignore
+    @computed get fontSize()       { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) as string; } // prettier-ignore
+    @computed get fontFamily()     { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) as string; } // prettier-ignore
+    @computed get fontWeight()     { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontWeight) as string;  } // prettier-ignore
+    @computed get fontStyle()      { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontStyle) as string;  } // prettier-ignore
+    @computed get fontDecoration() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontDecoration) as string;  } // prettier-ignore
 
     set _recordingDictation(value) {
         !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? mediaState.Recording : undefined);
@@ -2119,6 +2121,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     fontSize: this.fontSize,
                     fontFamily: this.fontFamily,
                     fontWeight: this.fontWeight,
+                    fontStyle: this.fontStyle,
+                    textDecoration: this.fontDecoration,
                     ...styleFromLayout,
                 }}>
                 <div
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 33cfbb510..62fbd23ea 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -41,7 +41,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     @observable private collapsed: boolean = false;
     @observable private _noLinkActive: boolean = false;
     @observable private _boldActive: boolean = false;
-    @observable private _italicsActive: boolean = false;
+    @observable private _italicActive: boolean = false;
     @observable private _underlineActive: boolean = false;
     @observable private _strikethroughActive: boolean = false;
     @observable private _subscriptActive: boolean = false;
@@ -89,8 +89,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     @computed get underline() {
         return this._underlineActive;
     }
-    @computed get italics() {
-        return this._italicsActive;
+    @computed get italic() {
+        return this._italicActive;
     }
     @computed get strikeThrough() {
         return this._strikethroughActive;
@@ -280,7 +280,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
         this._noLinkActive = false;
         this._boldActive = false;
-        this._italicsActive = false;
+        this._italicActive = false;
         this._underlineActive = false;
         this._strikethroughActive = false;
         this._subscriptActive = false;
@@ -290,7 +290,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             switch (mark.name) {
                 case 'noAutoLinkAnchor': this._noLinkActive = true; break;
                 case 'strong':           this._boldActive = true; break;
-                case 'em':               this._italicsActive = true; break;
+                case 'em':               this._italicActive = true; break;
                 case 'underline':        this._underlineActive = true; break;
                 case 'strikethrough':    this._strikethroughActive = true; break;
                 case 'subscript':        this._subscriptActive = true; break;
@@ -352,7 +352,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         }
     };
 
-    toggleItalics = () => {
+    toggleItalic = () => {
         if (this.view) {
             const mark = this.view.state.schema.mark(this.view.state.schema.marks.em);
             this.setMark(mark, this.view.state, this.view.dispatch, false);
@@ -361,13 +361,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     };
 
     setFontField = (value: string, fontField: 'fitBox' | 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => {
-        if (this.dataDoc) {
-            this.dataDoc[`text_${fontField}`] = value;
-            this.updateMenu(undefined, undefined, undefined, this.dataDoc);
-        } else if (this.TextView && this.view) {
+        if (this.TextView && this.view && fontField !== 'fitBox') {
             const { text, paragraph } = this.view.state.schema.nodes;
             const selNode = this.view.state.selection.$anchor.node();
-            if ((fontField === 'fontSize' && value === '0px') || (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type))) {
+            if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
                 this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value;
                 this.view.focus();
             }
@@ -376,6 +373,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
             this.setMark(fmark, this.view.state, (tx: Transaction) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
             this.view.focus();
+        } else if (this.dataDoc) {
+            this.dataDoc[`text_${fontField}`] = value;
+            this.updateMenu(undefined, undefined, undefined, this.dataDoc);
         } else {
             Doc.UserDoc()[fontField] = value;
             this.updateMenu(undefined, undefined, undefined, this.dataDoc);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 358557ad7..920c9ea8b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -377,7 +377,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         if ((e.button !== 0 || e.altKey) && this._props.isContentActive()) {
             this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Document);
         }
-        if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
             this._props.select(false);
             MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
             this.isAnnotating = true;
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index b4635673c..d0f6566a5 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -21,7 +21,7 @@ import { SVGToBezier, SVGType } from '../../util/bezierFit';
 import { InkingStroke } from '../InkingStroke';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { MarqueeView } from '../collections/collectionFreeForm';
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../nodes/DocumentView';
+import { ActiveInkArrowEnd, ActiveInkArrowStart, ActiveInkDash, ActiveInkFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../nodes/DocumentView';
 import './SmartDrawHandler.scss';
 
 export interface DrawingOptions {
@@ -105,14 +105,14 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
                         y: bounds.top - inkWidth / 2,
                         _width: bounds.width + inkWidth,
                         _height: bounds.height + inkWidth,
-                        stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore
+                        stroke_showLabel: !BoolCast(Doc.UserDoc().activeHideTextLabels)}, // prettier-ignore
                 inkWidth,
                 opts.autoColor ? stroke[1] : ActiveInkColor(),
                 ActiveInkBezierApprox(),
-                stroke[2] === 'none' ? ActiveFillColor() : stroke[2],
-                ActiveArrowStart(),
-                ActiveArrowEnd(),
-                ActiveDash(),
+                stroke[2] === 'none' ? ActiveInkFillColor() : stroke[2],
+                ActiveInkArrowStart(),
+                ActiveInkArrowEnd(),
+                ActiveInkDash(),
                 ActiveIsInkMask()
             );
             drawing.push(inkDoc);
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 6ec195910..aef7bf330 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -14,7 +14,7 @@ import {
     Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width
 } from './DocSymbols'; // prettier-ignore
 import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
-import { InkTool } from './InkField';
+import { InkEraserTool, InkInkTool, InkTool } from './InkField';
 import { List } from './List';
 import { ObjectField, serverOpType } from './ObjectField';
 import { PrefetchProxy, ProxyField } from './Proxy';
@@ -253,6 +253,10 @@ export class Doc extends RefField {
     public static set ActivePage(val)         { Doc.UserDoc().activePage = val; } // prettier-ignore
     public static get ActiveTool(): InkTool   { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool;  } // prettier-ignore
     public static set ActiveTool(tool:InkTool){ Doc.UserDoc().activeTool = tool; } // prettier-ignore
+    public static get ActiveInk(): InkInkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkInkTool;  } // prettier-ignore
+    public static set ActiveInk(tool:InkInkTool){ Doc.UserDoc().activeInkTool = tool; } // prettier-ignore
+    public static get ActiveEraser(): InkEraserTool { return StrCast(Doc.UserDoc().activeEraserTool, InkTool.None) as InkEraserTool;  } // prettier-ignore
+    public static set ActiveEraser(tool:InkEraserTool){ Doc.UserDoc().activeEraserTool = tool; } // prettier-ignore
     public static get ActivePresentation()    { return DocCast(Doc.ActiveDashboard?.activePresentation) as Opt<Doc>;  } // prettier-ignore
     public static set ActivePresentation(val) { Doc.ActiveDashboard && (Doc.ActiveDashboard.activePresentation = val) } // prettier-ignore
     public static get ActiveDashboard()       { return DocCast(Doc.UserDoc().activeDashboard); } // prettier-ignore
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 17b99b033..f3ff5f11f 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -8,17 +8,22 @@ import { ObjectField } from './ObjectField';
 
 // Helps keep track of the current ink tool in use.
 export enum InkTool {
-    None = 'none',
-    Pen = 'pen',
-    Highlighter = 'highlighter',
-    StrokeEraser = 'strokeeraser',
-    SegmentEraser = 'segmenteraser',
-    RadiusEraser = 'radiuseraser',
-    Eraser = 'eraser', // not a real tool, but a class of tools
-    Stamp = 'stamp',
-    Write = 'write',
-    PresentationPin = 'presentationpin',
-    SmartDraw = 'smartdraw',
+    None = 'None',
+    Ink = 'Ink',
+    Eraser = 'Eraser', // not a real tool, but a class of tools
+    SmartDraw = 'Smartdraw',
+}
+
+export enum InkInkTool {
+    Pen = 'Pen',
+    Highlight = 'Highlight',
+    Write = 'Write',
+}
+
+export enum InkEraserTool {
+    Stroke = 'Stroke',
+    Segment = 'Segment',
+    Radius = 'Radius',
 }
 
 export type Segment = Array<Bezier>;
diff --git a/src/pen-gestures/GestureTypes.ts b/src/pen-gestures/GestureTypes.ts
index 5a8e9bd97..10f9ba6d0 100644
--- a/src/pen-gestures/GestureTypes.ts
+++ b/src/pen-gestures/GestureTypes.ts
@@ -8,7 +8,6 @@ export enum Gestures {
     Arrow = 'arrow',
     RightAngle = 'rightangle',
 }
-
 // Defines a point in an ink as a pair of x- and y-coordinates.
 export interface PointData {
     X: number;
-- 
cgit v1.2.3-70-g09d2

