{"version":3,"file":"PencilBrush.min.mjs","sources":["../../../src/brushes/PencilBrush.ts"],"sourcesContent":["import type { ModifierKey, TEvent } from '../EventTypeDefs';\nimport type { Point } from '../Point';\nimport { Shadow } from '../Shadow';\nimport { Path } from '../shapes/Path';\nimport { getSmoothPathFromPoints, joinPath } from '../util/path';\nimport type { Canvas } from '../canvas/Canvas';\nimport { BaseBrush } from './BaseBrush';\nimport type { TSimplePathData } from '../util/path/typedefs';\n\n/**\n * @private\n * @param {TSimplePathData} pathData SVG path commands\n * @returns {boolean}\n */\nfunction isEmptySVGPath(pathData: TSimplePathData): boolean {\n  return joinPath(pathData) === 'M 0 0 Q 0 0 0 0 L 0 0';\n}\n\nexport class PencilBrush extends BaseBrush {\n  /**\n   * Discard points that are less than `decimate` pixel distant from each other\n   * @type Number\n   * @default 0.4\n   */\n  decimate = 0.4;\n\n  /**\n   * Draws a straight line between last recorded point to current pointer\n   * Used for `shift` functionality\n   *\n   * @type boolean\n   * @default false\n   */\n  drawStraightLine = false;\n\n  /**\n   * The event modifier key that makes the brush draw a straight line.\n   * If `null` or 'none' or any other string that is not a modifier key the feature is disabled.\n   * @type {ModifierKey | undefined | null}\n   */\n  straightLineKey: ModifierKey | undefined | null = 'shiftKey';\n\n  private declare _points: Point[];\n  private declare _hasStraightLine: boolean;\n  private declare oldEnd?: Point;\n\n  constructor(canvas: Canvas) {\n    super(canvas);\n    this._points = [];\n    this._hasStraightLine = false;\n  }\n\n  needsFullRender() {\n    return super.needsFullRender() || this._hasStraightLine;\n  }\n\n  static drawSegment(ctx: CanvasRenderingContext2D, p1: Point, p2: Point) {\n    const midPoint = p1.midPointFrom(p2);\n    ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);\n    return midPoint;\n  }\n\n  /**\n   * Invoked on mouse down\n   * @param {Point} pointer\n   */\n  onMouseDown(pointer: Point, { e }: TEvent) {\n    if (!this.canvas._isMainEvent(e)) {\n      return;\n    }\n    this.drawStraightLine = !!this.straightLineKey && e[this.straightLineKey];\n    this._prepareForDrawing(pointer);\n    // capture coordinates immediately\n    // this allows to draw dots (when movement never occurs)\n    this._addPoint(pointer);\n    this._render();\n  }\n\n  /**\n   * Invoked on mouse move\n   * @param {Point} pointer\n   */\n  onMouseMove(pointer: Point, { e }: TEvent) {\n    if (!this.canvas._isMainEvent(e)) {\n      return;\n    }\n    this.drawStraightLine = !!this.straightLineKey && e[this.straightLineKey];\n    if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) {\n      return;\n    }\n    if (this._addPoint(pointer) && this._points.length > 1) {\n      if (this.needsFullRender()) {\n        // redraw curve\n        // clear top canvas\n        this.canvas.clearContext(this.canvas.contextTop);\n        this._render();\n      } else {\n        const points = this._points,\n          length = points.length,\n          ctx = this.canvas.contextTop;\n        // draw the curve update\n        this._saveAndTransform(ctx);\n        if (this.oldEnd) {\n          ctx.beginPath();\n          ctx.moveTo(this.oldEnd.x, this.oldEnd.y);\n        }\n        this.oldEnd = PencilBrush.drawSegment(\n          ctx,\n          points[length - 2],\n          points[length - 1],\n        );\n        ctx.stroke();\n        ctx.restore();\n      }\n    }\n  }\n\n  /**\n   * Invoked on mouse up\n   */\n  onMouseUp({ e }: TEvent) {\n    if (!this.canvas._isMainEvent(e)) {\n      return true;\n    }\n    this.drawStraightLine = false;\n    this.oldEnd = undefined;\n    this._finalizeAndAddPath();\n    return false;\n  }\n\n  /**\n   * @private\n   * @param {Point} pointer Actual mouse position related to the canvas.\n   */\n  _prepareForDrawing(pointer: Point) {\n    this._reset();\n    this._addPoint(pointer);\n    this.canvas.contextTop.moveTo(pointer.x, pointer.y);\n  }\n\n  /**\n   * @private\n   * @param {Point} point Point to be added to points array\n   */\n  _addPoint(point: Point) {\n    if (\n      this._points.length > 1 &&\n      point.eq(this._points[this._points.length - 1])\n    ) {\n      return false;\n    }\n    if (this.drawStraightLine && this._points.length > 1) {\n      this._hasStraightLine = true;\n      this._points.pop();\n    }\n    this._points.push(point);\n    return true;\n  }\n\n  /**\n   * Clear points array and set contextTop canvas style.\n   * @private\n   */\n  _reset() {\n    this._points = [];\n    this._setBrushStyles(this.canvas.contextTop);\n    this._setShadow();\n    this._hasStraightLine = false;\n  }\n\n  /**\n   * Draw a smooth path on the topCanvas using quadraticCurveTo\n   * @private\n   * @param {CanvasRenderingContext2D} [ctx]\n   */\n  _render(ctx: CanvasRenderingContext2D = this.canvas.contextTop) {\n    let p1 = this._points[0],\n      p2 = this._points[1];\n    this._saveAndTransform(ctx);\n    ctx.beginPath();\n    //if we only have 2 points in the path and they are the same\n    //it means that the user only clicked the canvas without moving the mouse\n    //then we should be drawing a dot. A path isn't drawn between two identical dots\n    //that's why we set them apart a bit\n    if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) {\n      const width = this.width / 1000;\n      p1.x -= width;\n      p2.x += width;\n    }\n    ctx.moveTo(p1.x, p1.y);\n\n    for (let i = 1; i < this._points.length; i++) {\n      // we pick the point between pi + 1 & pi + 2 as the\n      // end point and p1 as our control point.\n      PencilBrush.drawSegment(ctx, p1, p2);\n      p1 = this._points[i];\n      p2 = this._points[i + 1];\n    }\n    // Draw last line as a straight line while\n    // we wait for the next point to be able to calculate\n    // the bezier control point\n    ctx.lineTo(p1.x, p1.y);\n    ctx.stroke();\n    ctx.restore();\n  }\n\n  /**\n   * Converts points to SVG path\n   * @param {Point[]} points Array of points\n   * @return {TSimplePathData} SVG path commands\n   */\n  convertPointsToSVGPath(points: Point[]): TSimplePathData {\n    const correction = this.width / 1000;\n    return getSmoothPathFromPoints(points, correction);\n  }\n\n  /**\n   * Creates a Path object to add on canvas\n   * @param {TSimplePathData} pathData Path data\n   * @return {Path} Path to add on canvas\n   */\n  createPath(pathData: TSimplePathData): Path {\n    const path = new Path(pathData, {\n      fill: null,\n      stroke: this.color,\n      strokeWidth: this.width,\n      strokeLineCap: this.strokeLineCap,\n      strokeMiterLimit: this.strokeMiterLimit,\n      strokeLineJoin: this.strokeLineJoin,\n      strokeDashArray: this.strokeDashArray,\n    });\n    if (this.shadow) {\n      this.shadow.affectStroke = true;\n      path.shadow = new Shadow(this.shadow);\n    }\n\n    return path;\n  }\n\n  /**\n   * Decimate points array with the decimate value\n   */\n  decimatePoints(points: Point[], distance: number) {\n    if (points.length <= 2) {\n      return points;\n    }\n    let lastPoint = points[0],\n      cDistance;\n    const zoom = this.canvas.getZoom(),\n      adjustedDistance = Math.pow(distance / zoom, 2),\n      l = points.length - 1,\n      newPoints = [lastPoint];\n    for (let i = 1; i < l - 1; i++) {\n      cDistance =\n        Math.pow(lastPoint.x - points[i].x, 2) +\n        Math.pow(lastPoint.y - points[i].y, 2);\n      if (cDistance >= adjustedDistance) {\n        lastPoint = points[i];\n        newPoints.push(lastPoint);\n      }\n    }\n    // Add the last point from the original line to the end of the array.\n    // This ensures decimate doesn't delete the last point on the line, and ensures the line is > 1 point.\n    newPoints.push(points[l]);\n    return newPoints;\n  }\n\n  /**\n   * On mouseup after drawing the path on contextTop canvas\n   * we use the points captured to create an new Path object\n   * and add it to the canvas.\n   */\n  _finalizeAndAddPath() {\n    const ctx = this.canvas.contextTop;\n    ctx.closePath();\n    if (this.decimate) {\n      this._points = this.decimatePoints(this._points, this.decimate);\n    }\n    const pathData = this.convertPointsToSVGPath(this._points);\n    if (isEmptySVGPath(pathData)) {\n      // do not create 0 width/height paths, as they are\n      // rendered inconsistently across browsers\n      // Firefox 4, for example, renders a dot,\n      // whereas Chrome 10 renders nothing\n      this.canvas.requestRenderAll();\n      return;\n    }\n\n    const path = this.createPath(pathData);\n    this.canvas.clearContext(this.canvas.contextTop);\n    this.canvas.fire('before:path:created', { path: path });\n    this.canvas.add(path);\n    this.canvas.requestRenderAll();\n    path.setCoords();\n    this._resetShadow();\n\n    // fire event 'path' created\n    this.canvas.fire('path:created', { path: path });\n  }\n}\n"],"names":["PencilBrush","BaseBrush","constructor","canvas","super","_defineProperty","this","_points","_hasStraightLine","needsFullRender","drawSegment","ctx","p1","p2","midPoint","midPointFrom","quadraticCurveTo","x","y","onMouseDown","pointer","_ref","e","_isMainEvent","drawStraightLine","straightLineKey","_prepareForDrawing","_addPoint","_render","onMouseMove","_ref2","limitedToCanvasSize","_isOutSideCanvas","length","clearContext","contextTop","points","_saveAndTransform","oldEnd","beginPath","moveTo","stroke","restore","onMouseUp","_ref3","undefined","_finalizeAndAddPath","_reset","point","eq","pop","push","_setBrushStyles","_setShadow","arguments","width","i","lineTo","convertPointsToSVGPath","correction","getSmoothPathFromPoints","createPath","pathData","path","Path","fill","color","strokeWidth","strokeLineCap","strokeMiterLimit","strokeLineJoin","strokeDashArray","shadow","affectStroke","Shadow","decimatePoints","distance","cDistance","lastPoint","zoom","getZoom","adjustedDistance","Math","pow","l","newPoints","closePath","decimate","joinPath","isEmptySVGPath","requestRenderAll","fire","add","setCoords","_resetShadow"],"mappings":"8SAkBO,MAAMA,UAAoBC,EA4B/BC,WAAAA,CAAYC,GACVC,MAAMD,GA5BRE,kBAKW,IAEXA,2BAOmB,GAEnBA,yBAKkD,YAQhDC,KAAKC,QAAU,GACfD,KAAKE,kBAAmB,CAC1B,CAEAC,eAAAA,GACE,OAAOL,MAAMK,mBAAqBH,KAAKE,gBACzC,CAEA,kBAAOE,CAAYC,EAA+BC,EAAWC,GAC3D,MAAMC,EAAWF,EAAGG,aAAaF,GAEjC,OADAF,EAAIK,iBAAiBJ,EAAGK,EAAGL,EAAGM,EAAGJ,EAASG,EAAGH,EAASI,GAC/CJ,CACT,CAMAK,WAAAA,CAAYC,EAAcC,GAAiB,IAAfC,EAAEA,GAAWD,EAClCf,KAAKH,OAAOoB,aAAaD,KAG9BhB,KAAKkB,mBAAqBlB,KAAKmB,iBAAmBH,EAAEhB,KAAKmB,iBACzDnB,KAAKoB,mBAAmBN,GAGxBd,KAAKqB,UAAUP,GACfd,KAAKsB,UACP,CAMAC,WAAAA,CAAYT,EAAcU,GAAiB,IAAfR,EAAEA,GAAWQ,EACvC,GAAKxB,KAAKH,OAAOoB,aAAaD,KAG9BhB,KAAKkB,mBAAqBlB,KAAKmB,iBAAmBH,EAAEhB,KAAKmB,mBACxB,IAA7BnB,KAAKyB,sBAAgCzB,KAAK0B,iBAAiBZ,KAG3Dd,KAAKqB,UAAUP,IAAYd,KAAKC,QAAQ0B,OAAS,GACnD,GAAI3B,KAAKG,kBAGPH,KAAKH,OAAO+B,aAAa5B,KAAKH,OAAOgC,YACrC7B,KAAKsB,cACA,CACL,MAAMQ,EAAS9B,KAAKC,QAClB0B,EAASG,EAAOH,OAChBtB,EAAML,KAAKH,OAAOgC,WAEpB7B,KAAK+B,kBAAkB1B,GACnBL,KAAKgC,SACP3B,EAAI4B,YACJ5B,EAAI6B,OAAOlC,KAAKgC,OAAOrB,EAAGX,KAAKgC,OAAOpB,IAExCZ,KAAKgC,OAAStC,EAAYU,YACxBC,EACAyB,EAAOH,EAAS,GAChBG,EAAOH,EAAS,IAElBtB,EAAI8B,SACJ9B,EAAI+B,SACN,CAEJ,CAKAC,SAAAA,CAASC,GAAgB,IAAftB,EAAEA,GAAWsB,EACrB,OAAKtC,KAAKH,OAAOoB,aAAaD,KAG9BhB,KAAKkB,kBAAmB,EACxBlB,KAAKgC,YAASO,EACdvC,KAAKwC,uBACE,EACT,CAMApB,kBAAAA,CAAmBN,GACjBd,KAAKyC,SACLzC,KAAKqB,UAAUP,GACfd,KAAKH,OAAOgC,WAAWK,OAAOpB,EAAQH,EAAGG,EAAQF,EACnD,CAMAS,SAAAA,CAAUqB,GACR,QACE1C,KAAKC,QAAQ0B,OAAS,GACtBe,EAAMC,GAAG3C,KAAKC,QAAQD,KAAKC,QAAQ0B,OAAS,OAI1C3B,KAAKkB,kBAAoBlB,KAAKC,QAAQ0B,OAAS,IACjD3B,KAAKE,kBAAmB,EACxBF,KAAKC,QAAQ2C,OAEf5C,KAAKC,QAAQ4C,KAAKH,IACX,EACT,CAMAD,MAAAA,GACEzC,KAAKC,QAAU,GACfD,KAAK8C,gBAAgB9C,KAAKH,OAAOgC,YACjC7B,KAAK+C,aACL/C,KAAKE,kBAAmB,CAC1B,CAOAoB,OAAAA,GAAgE,IAAxDjB,EAA6B2C,UAAArB,eAAAY,IAAAS,UAAA,GAAAA,UAAG,GAAAhD,KAAKH,OAAOgC,WAC9CvB,EAAKN,KAAKC,QAAQ,GACpBM,EAAKP,KAAKC,QAAQ,GAOpB,GANAD,KAAK+B,kBAAkB1B,GACvBA,EAAI4B,YAKwB,IAAxBjC,KAAKC,QAAQ0B,QAAgBrB,EAAGK,IAAMJ,EAAGI,GAAKL,EAAGM,IAAML,EAAGK,EAAG,CAC/D,MAAMqC,EAAQjD,KAAKiD,MAAQ,IAC3B3C,EAAGK,GAAKsC,EACR1C,EAAGI,GAAKsC,CACV,CACA5C,EAAI6B,OAAO5B,EAAGK,EAAGL,EAAGM,GAEpB,IAAK,IAAIsC,EAAI,EAAGA,EAAIlD,KAAKC,QAAQ0B,OAAQuB,IAGvCxD,EAAYU,YAAYC,EAAKC,EAAIC,GACjCD,EAAKN,KAAKC,QAAQiD,GAClB3C,EAAKP,KAAKC,QAAQiD,EAAI,GAKxB7C,EAAI8C,OAAO7C,EAAGK,EAAGL,EAAGM,GACpBP,EAAI8B,SACJ9B,EAAI+B,SACN,CAOAgB,sBAAAA,CAAuBtB,GACrB,MAAMuB,EAAarD,KAAKiD,MAAQ,IAChC,OAAOK,EAAwBxB,EAAQuB,EACzC,CAOAE,UAAAA,CAAWC,GACT,MAAMC,EAAO,IAAIC,EAAKF,EAAU,CAC9BG,KAAM,KACNxB,OAAQnC,KAAK4D,MACbC,YAAa7D,KAAKiD,MAClBa,cAAe9D,KAAK8D,cACpBC,iBAAkB/D,KAAK+D,iBACvBC,eAAgBhE,KAAKgE,eACrBC,gBAAiBjE,KAAKiE,kBAOxB,OALIjE,KAAKkE,SACPlE,KAAKkE,OAAOC,cAAe,EAC3BV,EAAKS,OAAS,IAAIE,EAAOpE,KAAKkE,SAGzBT,CACT,CAKAY,cAAAA,CAAevC,EAAiBwC,GAC9B,GAAIxC,EAAOH,QAAU,EACnB,OAAOG,EAET,IACEyC,EADEC,EAAY1C,EAAO,GAEvB,MAAM2C,EAAOzE,KAAKH,OAAO6E,UACvBC,EAAmBC,KAAKC,IAAIP,EAAWG,EAAM,GAC7CK,EAAIhD,EAAOH,OAAS,EACpBoD,EAAY,CAACP,GACf,IAAK,IAAItB,EAAI,EAAGA,EAAI4B,EAAI,EAAG5B,IACzBqB,EACEK,KAAKC,IAAIL,EAAU7D,EAAImB,EAAOoB,GAAGvC,EAAG,GACpCiE,KAAKC,IAAIL,EAAU5D,EAAIkB,EAAOoB,GAAGtC,EAAG,GAClC2D,GAAaI,IACfH,EAAY1C,EAAOoB,GACnB6B,EAAUlC,KAAK2B,IAMnB,OADAO,EAAUlC,KAAKf,EAAOgD,IACfC,CACT,CAOAvC,mBAAAA,GACcxC,KAAKH,OAAOgC,WACpBmD,YACAhF,KAAKiF,WACPjF,KAAKC,QAAUD,KAAKqE,eAAerE,KAAKC,QAASD,KAAKiF,WAExD,MAAMzB,EAAWxD,KAAKoD,uBAAuBpD,KAAKC,SAClD,GAzQJ,SAAwBuD,GACtB,MAA8B,0BAAvB0B,EAAS1B,EAClB,CAuQQ2B,CAAe3B,GAMjB,YADAxD,KAAKH,OAAOuF,mBAId,MAAM3B,EAAOzD,KAAKuD,WAAWC,GAC7BxD,KAAKH,OAAO+B,aAAa5B,KAAKH,OAAOgC,YACrC7B,KAAKH,OAAOwF,KAAK,sBAAuB,CAAE5B,KAAMA,IAChDzD,KAAKH,OAAOyF,IAAI7B,GAChBzD,KAAKH,OAAOuF,mBACZ3B,EAAK8B,YACLvF,KAAKwF,eAGLxF,KAAKH,OAAOwF,KAAK,eAAgB,CAAE5B,KAAMA,GAC3C"}