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', Pen = 'pen', Highlighter = 'highlighter', StrokeEraser = 'strokeeraser', SegmentEraser = 'segmenteraser', RadiusEraser = 'radiuseraser', Stamp = 'stamp', Write = 'write', PresentationPin = 'presentationpin', SmartDraw = 'smartdraw', } export type Segment = Array; // Defines an ink as an array of points. export type InkData = Array; 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 }; } } ScriptingGlobals.add('InkField', InkField);