aboutsummaryrefslogtreecommitdiff
path: root/src/fields/InkField.ts
blob: d1dda106a72891301b042ab2814fc5593157ee78 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { Bezier } from 'bezier-js';
import { alias, createSimpleSchema, list, object, serializable } from 'serializr';
import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { Deserializable } from '../client/util/SerializationHelper';
import { PointData } from '../pen-gestures/GestureTypes';
import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
import { ObjectField } from './ObjectField';

// Helps keep track of the current ink tool in use.
export enum InkTool {
    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 enum InkProperty {
    Mask = 'inkMask',
    Labels = 'labels',
    StrokeWidth = 'strokeWidth',
    StrokeColor = 'strokeColor',
    EraserWidth = ' eraserWidth',
}

export type Segment = Array<Bezier>;

// Defines an ink as an array of points.
export type InkData = Array<PointData>;

export interface ControlPoint {
    X: number;
    Y: number;
    I: number;
}

export interface HandlePoint {
    X: number;
    Y: number;
    I: number;
    dot1: number;
    dot2: number;
}

export interface HandleLine {
    X1: number;
    Y1: number;
    X2: number;
    Y2: number;
    X3: number;
    Y3: number;
    dot1: number;
    dot2: number;
}

const pointSchema = createSimpleSchema({
    X: true,
    Y: true,
});

const strokeDataSchema = createSimpleSchema({
    pathData: list(object(pointSchema)),
    '*': true,
});

export const InkDataFieldName = '__inkData';
@Deserializable('ink')
export class InkField extends ObjectField {
    @serializable(alias(InkDataFieldName, list(object(strokeDataSchema))))
    readonly inkData: InkData;

    constructor(data: InkData) {
        super();
        this.inkData = data;
    }

    /**
     * Extacts a simple segment from a compound Bezier curve
     * @param segIndex the start index of the simple bezier segment to extact (eg., 0, 4, 8, ...)
     */
    public static Segment(inkData: InkData, segIndex: number) {
        return new Bezier(inkData.slice(segIndex, segIndex + 4).map(pt => ({ x: pt.X, y: pt.Y })));
    }

    [Copy]() {
        return new InkField(this.inkData);
    }

    [ToJavascriptString]() {
        return '[' + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}}`) + ']';
    }
    [ToScriptString]() {
        return 'new InkField([' + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}}`) + '])';
    }
    [ToString]() {
        return 'InkField';
    }

    public static getBounds(stroke: InkData, pad?: boolean) {
        const padding = pad ? [-20000, 20000] : [];
        const xs = [...padding, ...stroke.map(p => p.X)];
        const ys = [...padding, ...stroke.map(p => p.Y)];
        const right = Math.max(...xs);
        const left = Math.min(...xs);
        const bottom = Math.max(...ys);
        const top = Math.min(...ys);
        return { right, left, bottom, top, width: right - left, height: bottom - top };
    }

    // for some reason bezier.js doesn't handle the case of intersecting a linear curve, so we wrap the intersection
    // call in a test for linearity
    public static bintersects(curve: Bezier, otherCurve: Bezier) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        if ((curve as any)._linear) {
            // bezier.js doesn't intersect properly if the curve is actually a line -- so get intersect other curve against this line, then figure out the t coordinates of the intersection on this line
            const intersections = otherCurve.lineIntersects({ p1: curve.points[0], p2: curve.points[3] });
            if (intersections.length) {
                const intPt = otherCurve.get(intersections[0]);
                const intT = curve.project(intPt).t;
                return intT ? [intT] : [];
            }
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        if ((otherCurve as any)._linear) {
            return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] });
        }
        return curve.intersects(otherCurve);
    }
}

ScriptingGlobals.add('InkField', InkField);