Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import { v4 as uuid } from 'uuid'
  2. import * as vec from 'utils/vec'
  3. import { DrawShape, ShapeType } from 'types'
  4. import { registerShapeUtils } from './index'
  5. import { intersectPolylineBounds } from 'utils/intersections'
  6. import { boundsContainPolygon } from 'utils/bounds'
  7. import getStroke from 'perfect-freehand'
  8. import {
  9. getBoundsCenter,
  10. getBoundsFromPoints,
  11. getSvgPathFromStroke,
  12. translateBounds,
  13. } from 'utils/utils'
  14. const pathCache = new WeakMap<DrawShape['points'], string>([])
  15. const draw = registerShapeUtils<DrawShape>({
  16. boundsCache: new WeakMap([]),
  17. create(props) {
  18. return {
  19. id: uuid(),
  20. type: ShapeType.Draw,
  21. isGenerated: false,
  22. name: 'Draw',
  23. parentId: 'page0',
  24. childIndex: 0,
  25. point: [0, 0],
  26. points: [],
  27. rotation: 0,
  28. isAspectRatioLocked: false,
  29. isLocked: false,
  30. isHidden: false,
  31. ...props,
  32. style: {
  33. strokeWidth: 2,
  34. strokeLinecap: 'round',
  35. strokeLinejoin: 'round',
  36. ...props.style,
  37. fill: props.style.stroke,
  38. },
  39. }
  40. },
  41. render(shape) {
  42. const { id, points, style } = shape
  43. if (!pathCache.has(points)) {
  44. pathCache.set(
  45. points,
  46. getSvgPathFromStroke(
  47. getStroke(points, {
  48. size: +style.strokeWidth * 2,
  49. thinning: 0.9,
  50. end: { taper: 100 },
  51. start: { taper: 100 },
  52. })
  53. )
  54. )
  55. }
  56. if (points.length < 2) {
  57. return <circle id={id} r={+style.strokeWidth * 0.618} />
  58. }
  59. return <path id={id} d={pathCache.get(points)} />
  60. },
  61. applyStyles(shape, style) {
  62. Object.assign(shape.style, style)
  63. shape.style.fill = shape.style.stroke
  64. return this
  65. },
  66. getBounds(shape) {
  67. if (!this.boundsCache.has(shape)) {
  68. const bounds = getBoundsFromPoints(shape.points)
  69. this.boundsCache.set(shape, bounds)
  70. }
  71. return translateBounds(this.boundsCache.get(shape), shape.point)
  72. },
  73. getRotatedBounds(shape) {
  74. const bounds =
  75. this.boundsCache.get(shape) || getBoundsFromPoints(shape.points)
  76. const center = getBoundsCenter(bounds)
  77. const rotatedPts = shape.points.map((pt) =>
  78. vec.rotWith(pt, center, shape.rotation)
  79. )
  80. const rotatedBounds = translateBounds(
  81. getBoundsFromPoints(rotatedPts),
  82. shape.point
  83. )
  84. return rotatedBounds
  85. },
  86. getCenter(shape) {
  87. const bounds = this.getRotatedBounds(shape)
  88. return [bounds.minX + bounds.width / 2, bounds.minY + bounds.height / 2]
  89. },
  90. hitTest(shape, point) {
  91. let pt = vec.sub(point, shape.point)
  92. let prev = shape.points[0]
  93. for (let i = 1; i < shape.points.length; i++) {
  94. let curr = shape.points[i]
  95. if (
  96. vec.distanceToLineSegment(prev, curr, pt) < +shape.style.strokeWidth
  97. ) {
  98. return true
  99. }
  100. prev = curr
  101. }
  102. return false
  103. },
  104. hitTestBounds(this, shape, brushBounds) {
  105. const b = this.getBounds(shape)
  106. const center = [b.minX + b.width / 2, b.minY + b.height / 2]
  107. const rotatedCorners = [
  108. [b.minX, b.minY],
  109. [b.maxX, b.minY],
  110. [b.maxX, b.maxY],
  111. [b.minX, b.maxY],
  112. ].map((point) => vec.rotWith(point, center, shape.rotation))
  113. return (
  114. boundsContainPolygon(brushBounds, rotatedCorners) ||
  115. intersectPolylineBounds(
  116. shape.points.map((point) => vec.add(point, shape.point)),
  117. brushBounds
  118. ).length > 0
  119. )
  120. },
  121. rotateTo(shape, rotation) {
  122. shape.rotation = rotation
  123. // console.log(shape.points.map(([x, y]) => [x, y]))
  124. // const bounds = this.getBounds(shape)
  125. // const center = [bounds.width / 2, bounds.height / 2]
  126. // shape.points = shape.points.map((pt) => vec.rotWith(pt, center, rotation))
  127. return this
  128. },
  129. translateTo(shape, point) {
  130. shape.point = vec.toPrecision(point)
  131. return this
  132. },
  133. transform(shape, bounds, { initialShape, scaleX, scaleY }) {
  134. const initialShapeBounds = this.boundsCache.get(initialShape)
  135. shape.points = initialShape.points.map(([x, y]) => {
  136. return [
  137. bounds.width *
  138. (scaleX < 0
  139. ? 1 - x / initialShapeBounds.width
  140. : x / initialShapeBounds.width),
  141. bounds.height *
  142. (scaleY < 0
  143. ? 1 - y / initialShapeBounds.height
  144. : y / initialShapeBounds.height),
  145. ]
  146. })
  147. const newBounds = getBoundsFromPoints(shape.points)
  148. shape.point = vec.sub(
  149. [bounds.minX, bounds.minY],
  150. [newBounds.minX, newBounds.minY]
  151. )
  152. return this
  153. },
  154. transformSingle(shape, bounds, info) {
  155. this.transform(shape, bounds, info)
  156. return this
  157. },
  158. setProperty(shape, prop, value) {
  159. shape[prop] = value
  160. return this
  161. },
  162. canTransform: true,
  163. canChangeAspectRatio: true,
  164. })
  165. export default draw