From 687b51a3de67eca11fcc1bd11964d93acc1f56d9 Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Thu, 7 Oct 2021 15:34:25 -0400
Subject: ink rotation based on angle change

---
 src/client/views/DocumentDecorations.tsx | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index bd9c3509b..b1c147747 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -197,9 +197,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         this._rotateUndo = UndoManager.StartBatch("rotatedown");
         setupMoveUpEvents(this, e,
             (e: PointerEvent, down: number[], delta: number[]) => {
-                const movement = { X: delta[0], Y: e.clientY - down[1] };
-                const angle = Math.max(1, Math.abs(movement.Y / 10));
-                InkStrokeProperties.Instance?.rotateInk(2 * movement.X / angle * (Math.PI / 180));
+                let origin;
+                SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
+                .map(doc => {
+                    const inkData = Cast(doc.rootDoc.data, InkField)?.inkData ?? [];
+                    const inkStrokeWidth = NumCast(doc.rootDoc.strokeWidth, 1);
+                    const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2;
+                    const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2;
+                    const inkLeft = Math.min(...inkData.map(p => p.X)) - inkStrokeWidth / 2;
+                    const inkRight = Math.max(...inkData.map(p => p.X)) + inkStrokeWidth / 2;
+                    origin = { X: (inkLeft + inkRight) / 2, Y: (inkTop + inkBottom) / 2 };
+                });
+                if (origin) {
+                    const previousPoint = { X: e.clientX, Y: e.clientY };
+                    const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] };
+                    const angle = InkStrokeProperties.Instance?.angleChange(previousPoint, movedPoint, origin);
+                    if (angle) InkStrokeProperties.Instance?.rotateInk(-angle);
+                }
                 return false;
             },
             () => {
-- 
cgit v1.2.3-70-g09d2


From bf896f7f7c287c9a78cca91d7ceab88f66613b61 Mon Sep 17 00:00:00 2001
From: vkalev <vjk1883@gmail.com>
Date: Thu, 21 Oct 2021 16:44:12 -0400
Subject: changing rotation

---
 src/client/views/DocumentDecorations.tsx | 24 +++++++-----------------
 src/client/views/InkStrokeProperties.ts  |  6 ++++++
 2 files changed, 13 insertions(+), 17 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b1c147747..207c7fcac 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -197,23 +197,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         this._rotateUndo = UndoManager.StartBatch("rotatedown");
         setupMoveUpEvents(this, e,
             (e: PointerEvent, down: number[], delta: number[]) => {
-                let origin;
-                SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
-                .map(doc => {
-                    const inkData = Cast(doc.rootDoc.data, InkField)?.inkData ?? [];
-                    const inkStrokeWidth = NumCast(doc.rootDoc.strokeWidth, 1);
-                    const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2;
-                    const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2;
-                    const inkLeft = Math.min(...inkData.map(p => p.X)) - inkStrokeWidth / 2;
-                    const inkRight = Math.max(...inkData.map(p => p.X)) + inkStrokeWidth / 2;
-                    origin = { X: (inkLeft + inkRight) / 2, Y: (inkTop + inkBottom) / 2 };
-                });
-                if (origin) {
-                    const previousPoint = { X: e.clientX, Y: e.clientY };
-                    const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] };
-                    const angle = InkStrokeProperties.Instance?.angleChange(previousPoint, movedPoint, origin);
-                    if (angle) InkStrokeProperties.Instance?.rotateInk(-angle);
-                }
+                const docView = SelectionManager.Views()[0];
+                const { left, top, right, bottom } = docView.getBounds() || { left: 0, top: 0, right: 0, bottom: 0  };
+                const centerPoint = { X: (left + right) / 2, Y: (top + bottom) / 2 };
+                const previousPoint = { X: e.clientX, Y: e.clientY };
+                const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] };
+                const angle = InkStrokeProperties.Instance?.angleChange(previousPoint, movedPoint, centerPoint);
+                if (angle) InkStrokeProperties.Instance?.rotateInk(-angle);
                 return false;
             },
             () => {
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index ee30caa3d..ba85b851c 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -259,11 +259,17 @@ export class InkStrokeProperties {
 
             if (near / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) {
                 return this.moveControlPtHandle((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex);
+            } else {
+                return 
             }
         }
         return false;
     }
 
+    // snapControlBetweenCurves = (ink, controlIndex) => {
+        
+    // }
+
     /**
      * Snaps a control point with broken tangency back to synced rotation.
      * @param handleIndexA The handle point that retains its current position.
-- 
cgit v1.2.3-70-g09d2


From b30b1cb301ed742a128d5e17eea9c954d1fe0c22 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 26 Oct 2021 20:40:58 -0400
Subject:  fix to ink rotation to preserve rotation center between rotations.

---
 src/client/views/DocumentDecorations.tsx | 11 +++++------
 src/client/views/InkingStroke.tsx        | 17 +++++++++++++++++
 src/client/views/nodes/DocumentView.tsx  |  5 +++--
 3 files changed, 25 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 5b44a0552..3b992e0d1 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,7 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { Tooltip } from '@material-ui/core';
-import { action, computed, observable, reaction, runInAction } from "mobx";
+import { action, computed, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { DateField } from '../../fields/DateField';
 import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from "../../fields/Doc";
@@ -27,8 +27,6 @@ import { LightboxView } from './LightboxView';
 import { DocumentView } from "./nodes/DocumentView";
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import React = require("react");
-import { dark } from '@material-ui/core/styles/createPalette';
-import { color } from 'd3-color';
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number, PanelHeight: number, boundsLeft: number, boundsTop: number }, { value: string }> {
@@ -71,9 +69,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                     x: Math.min(rect.left, bounds.x),
                     y: Math.min(rect.top, bounds.y),
                     r: Math.max(rect.right, bounds.r),
-                    b: Math.max(rect.bottom, bounds.b)
+                    b: Math.max(rect.bottom, bounds.b),
+                    c: rect.center
                 },
-            { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
+            { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as ({ X: number, Y: number } | undefined) });
     }
 
     @action
@@ -195,7 +194,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
     @action
     onRotateDown = (e: React.PointerEvent): void => {
         this._rotateUndo = UndoManager.StartBatch("rotatedown");
-        const pt = { x: (this.Bounds.x + this.Bounds.r) / 2, y: (this.Bounds.y + this.Bounds.b) / 2 };
+        const pt = { x: this.Bounds.c?.X ?? (this.Bounds.x + this.Bounds.r) / 2, y: this.Bounds.c?.Y ?? (this.Bounds.y + this.Bounds.b) / 2 };
         setupMoveUpEvents(this, e,
             (e: PointerEvent, down: number[], delta: number[]) => {
                 const movement = { X: delta[0], Y: e.clientY - down[1] };
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index d312331d0..b039579ce 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -21,6 +21,7 @@ import { InkStrokeProperties } from "./InkStrokeProperties";
 import { InkTangentHandles } from "./InkTangentHandles";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
 import Color = require("color");
+import { Transform } from "../util/Transform";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
@@ -56,6 +57,22 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         this._selDisposer?.();
     }
 
+    getCenter = (xf: Transform) => {
+        const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
+        const angle = -NumCast(this.layoutDoc.rotation);
+        const newPoints = inkData.map(pt => {
+            const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * inkScaleY / inkScaleX;
+            const newY = Math.sin(angle) * pt.X * inkScaleX / inkScaleY + Math.cos(angle) * pt.Y;
+            return { X: newX, Y: newY };
+        });
+        const crx = (Math.max(...newPoints.map(np => np.X)) + Math.min(...newPoints.map(np => np.X))) / 2;
+        const cry = (Math.max(...newPoints.map(np => np.Y)) + Math.min(...newPoints.map(np => np.Y))) / 2;
+        const cx = Math.cos(-angle) * crx - Math.sin(-angle) * cry * inkScaleY / inkScaleX;
+        const cy = Math.sin(-angle) * crx * inkScaleX / inkScaleY + Math.cos(-angle) * cry;
+        const tc = xf.transformPoint((cx - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (cy - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2);
+        return { X: tc[0], Y: tc[1] };
+    }
+
     analyzeStrokes() {
         const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
         CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 949e0e168..cbb77f369 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -97,6 +97,7 @@ export interface DocComponentView {
     annotationKey?: string;
     getTitle?: () => string;
     getScrollHeight?: () => number;
+    getCenter?: (xf: Transform) => { X: number, Y: number };
     search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
 }
 export interface DocumentViewSharedProps {
@@ -1179,9 +1180,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
         if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
             const docuBox = this.docView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
-            if (docuBox.length) return docuBox[0].getBoundingClientRect();
+            if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
         }
-        return { left, top, right, bottom };
+        return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) };
     }
 
     public iconify() {
-- 
cgit v1.2.3-70-g09d2


From c7aea59ca8cd24bf218be581ea241670fee6cc49 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 26 Oct 2021 21:03:46 -0400
Subject: use center of selection bounds for rotation if more than one stroke
 is selected.

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

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3b2661145..522995479 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -63,14 +63,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
 
     @computed
     get Bounds() {
-        return SelectionManager.Views().map(dv => dv.getBounds()).reduce((bounds, rect) =>
+        const views = SelectionManager.Views();
+        return views.map(dv => dv.getBounds()).reduce((bounds, rect) =>
             !rect ? bounds :
                 {
                     x: Math.min(rect.left, bounds.x),
                     y: Math.min(rect.top, bounds.y),
                     r: Math.max(rect.right, bounds.r),
                     b: Math.max(rect.bottom, bounds.b),
-                    c: rect.center
+                    c: views.length === 1 ? rect.center : undefined
                 },
             { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as ({ X: number, Y: number } | undefined) });
     }
-- 
cgit v1.2.3-70-g09d2

