{"version":3,"file":"pathControl.mjs","names":[],"sources":["../../../src/controls/pathControl.ts"],"sourcesContent":["import { Point } from '../Point';\nimport { Control } from './Control';\nimport type { TMat2D } from '../typedefs';\nimport type { Path } from '../shapes/Path';\nimport { multiplyTransformMatrices } from '../util/misc/matrix';\nimport type {\n  TModificationEvents,\n  TPointerEvent,\n  Transform,\n} from '../EventTypeDefs';\nimport { sendPointToPlane } from '../util/misc/planeChange';\nimport type { TSimpleParseCommandType } from '../util/path/typedefs';\nimport type { ControlRenderingStyleOverride } from './controlRendering';\nimport { fireEvent } from './fireEvent';\nimport { commonEventInfo } from './util';\n\nconst ACTION_NAME: TModificationEvents = 'modifyPath' as const;\n\ntype TTransformAnchor = Transform;\n\nexport type PathPointControlStyle = {\n  controlFill?: string;\n  controlStroke?: string;\n  connectionDashArray?: number[];\n};\n\nconst calcPathPointPosition = (\n  pathObject: Path,\n  commandIndex: number,\n  pointIndex: number,\n) => {\n  const { path, pathOffset } = pathObject;\n  const command = path[commandIndex];\n  return new Point(\n    (command[pointIndex] as number) - pathOffset.x,\n    (command[pointIndex + 1] as number) - pathOffset.y,\n  ).transform(\n    multiplyTransformMatrices(\n      pathObject.getViewportTransform(),\n      pathObject.calcTransformMatrix(),\n    ),\n  );\n};\n\nconst movePathPoint = (\n  pathObject: Path,\n  x: number,\n  y: number,\n  commandIndex: number,\n  pointIndex: number,\n) => {\n  const { path, pathOffset } = pathObject;\n\n  const anchorCommand =\n    path[(commandIndex > 0 ? commandIndex : path.length) - 1];\n  const anchorPoint = new Point(\n    anchorCommand[pointIndex] as number,\n    anchorCommand[pointIndex + 1] as number,\n  );\n\n  const anchorPointInParentPlane = anchorPoint\n    .subtract(pathOffset)\n    .transform(pathObject.calcOwnMatrix());\n\n  const mouseLocalPosition = sendPointToPlane(\n    new Point(x, y),\n    undefined,\n    pathObject.calcOwnMatrix(),\n  );\n\n  path[commandIndex][pointIndex] = mouseLocalPosition.x + pathOffset.x;\n  path[commandIndex][pointIndex + 1] = mouseLocalPosition.y + pathOffset.y;\n  pathObject.setDimensions();\n\n  const newAnchorPointInParentPlane = anchorPoint\n    .subtract(pathObject.pathOffset)\n    .transform(pathObject.calcOwnMatrix());\n\n  const diff = newAnchorPointInParentPlane.subtract(anchorPointInParentPlane);\n  pathObject.left -= diff.x;\n  pathObject.top -= diff.y;\n  pathObject.set('dirty', true);\n  return true;\n};\n\n/**\n * This function locates the controls.\n * It'll be used both for drawing and for interaction.\n */\nfunction pathPositionHandler(\n  this: PathPointControl,\n  dim: Point,\n  finalMatrix: TMat2D,\n  pathObject: Path,\n) {\n  const { commandIndex, pointIndex } = this;\n  return calcPathPointPosition(pathObject, commandIndex, pointIndex);\n}\n\n/**\n * This function defines what the control does.\n * It'll be called on every mouse move after a control has been clicked and is being dragged.\n * The function receives as argument the mouse event, the current transform object\n * and the current position in canvas coordinate `transform.target` is a reference to the\n * current object being transformed.\n */\nfunction pathActionHandler(\n  this: PathPointControl,\n  eventData: TPointerEvent,\n  transform: TTransformAnchor,\n  x: number,\n  y: number,\n) {\n  const { target } = transform;\n  const { commandIndex, pointIndex } = this;\n  const actionPerformed = movePathPoint(\n    target as Path,\n    x,\n    y,\n    commandIndex,\n    pointIndex,\n  );\n  if (actionPerformed) {\n    fireEvent(this.actionName as TModificationEvents, {\n      ...commonEventInfo(eventData, transform, x, y),\n      commandIndex,\n      pointIndex,\n    });\n  }\n  return actionPerformed;\n}\n\nconst indexFromPrevCommand = (previousCommandType: TSimpleParseCommandType) =>\n  previousCommandType === 'C' ? 5 : previousCommandType === 'Q' ? 3 : 1;\n\nclass PathPointControl extends Control {\n  declare commandIndex: number;\n  declare pointIndex: number;\n  declare controlFill: string;\n  declare controlStroke: string;\n  constructor(options?: Partial<PathPointControl>) {\n    super(options);\n  }\n\n  render(\n    ctx: CanvasRenderingContext2D,\n    left: number,\n    top: number,\n    styleOverride: ControlRenderingStyleOverride | undefined,\n    fabricObject: Path,\n  ) {\n    const overrides: ControlRenderingStyleOverride = {\n      ...styleOverride,\n      cornerColor: this.controlFill,\n      cornerStrokeColor: this.controlStroke,\n      transparentCorners: !this.controlFill,\n    };\n    super.render(ctx, left, top, overrides, fabricObject);\n  }\n}\n\nclass PathControlPointControl extends PathPointControl {\n  declare connectionDashArray?: number[];\n  declare connectToCommandIndex: number;\n  declare connectToPointIndex: number;\n  constructor(options?: Partial<PathControlPointControl>) {\n    super(options);\n  }\n\n  render(\n    this: PathControlPointControl,\n    ctx: CanvasRenderingContext2D,\n    left: number,\n    top: number,\n    styleOverride: ControlRenderingStyleOverride | undefined,\n    fabricObject: Path,\n  ) {\n    const { path } = fabricObject;\n    const {\n      commandIndex,\n      pointIndex,\n      connectToCommandIndex,\n      connectToPointIndex,\n    } = this;\n    ctx.save();\n    ctx.strokeStyle = this.controlStroke;\n    if (this.connectionDashArray) {\n      ctx.setLineDash(this.connectionDashArray);\n    }\n    const [commandType] = path[commandIndex];\n    const point = calcPathPointPosition(\n      fabricObject,\n      connectToCommandIndex,\n      connectToPointIndex,\n    );\n\n    if (commandType === 'Q') {\n      // one control point connects to 2 points\n      const point2 = calcPathPointPosition(\n        fabricObject,\n        commandIndex,\n        pointIndex + 2,\n      );\n      ctx.moveTo(point2.x, point2.y);\n      ctx.lineTo(left, top);\n    } else {\n      ctx.moveTo(left, top);\n    }\n    ctx.lineTo(point.x, point.y);\n    ctx.stroke();\n    ctx.restore();\n\n    super.render(ctx, left, top, styleOverride, fabricObject);\n  }\n}\n\nconst createControl = (\n  commandIndexPos: number,\n  pointIndexPos: number,\n  isControlPoint: boolean,\n  options: Partial<Control> & {\n    controlPointStyle?: PathPointControlStyle;\n    pointStyle?: PathPointControlStyle;\n  },\n  connectToCommandIndex?: number,\n  connectToPointIndex?: number,\n) =>\n  new (isControlPoint ? PathControlPointControl : PathPointControl)({\n    commandIndex: commandIndexPos,\n    pointIndex: pointIndexPos,\n    actionName: ACTION_NAME,\n    positionHandler: pathPositionHandler,\n    actionHandler: pathActionHandler,\n    connectToCommandIndex,\n    connectToPointIndex,\n    ...options,\n    ...(isControlPoint ? options.controlPointStyle : options.pointStyle),\n  } as Partial<PathControlPointControl>);\n\nexport function createPathControls(\n  path: Path,\n  options: Partial<Control> & {\n    controlPointStyle?: PathPointControlStyle;\n    pointStyle?: PathPointControlStyle;\n  } = {},\n): Record<string, Control> {\n  const controls = {} as Record<string, Control>;\n  let previousCommandType: TSimpleParseCommandType = 'M';\n  path.path.forEach((command, commandIndex) => {\n    const commandType = command[0];\n\n    if (commandType !== 'Z') {\n      controls[`c_${commandIndex}_${commandType}`] = createControl(\n        commandIndex,\n        command.length - 2,\n        false,\n        options,\n      );\n    }\n    switch (commandType) {\n      case 'C':\n        controls[`c_${commandIndex}_C_CP_1`] = createControl(\n          commandIndex,\n          1,\n          true,\n          options,\n          commandIndex - 1,\n          indexFromPrevCommand(previousCommandType),\n        );\n        controls[`c_${commandIndex}_C_CP_2`] = createControl(\n          commandIndex,\n          3,\n          true,\n          options,\n          commandIndex,\n          5,\n        );\n        break;\n      case 'Q':\n        controls[`c_${commandIndex}_Q_CP_1`] = createControl(\n          commandIndex,\n          1,\n          true,\n          options,\n          commandIndex,\n          3,\n        );\n        break;\n    }\n    previousCommandType = commandType;\n  });\n  return controls;\n}\n"],"mappings":"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"}