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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  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. getBoundsFromPoints,
  10. getRotatedCorners,
  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: [[0, 0]],
  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, { size: +style.strokeWidth * 2, thinning: 0.9 })
  48. )
  49. )
  50. }
  51. if (points.length < 2) {
  52. return <circle id={id} r={+style.strokeWidth * 0.618} />
  53. }
  54. return <path id={id} d={pathCache.get(points)} />
  55. },
  56. applyStyles(shape, style) {
  57. Object.assign(shape.style, style)
  58. shape.style.fill = shape.style.stroke
  59. return this
  60. },
  61. getBounds(shape) {
  62. if (!this.boundsCache.has(shape)) {
  63. const bounds = getBoundsFromPoints(shape.points)
  64. this.boundsCache.set(shape, bounds)
  65. }
  66. return translateBounds(this.boundsCache.get(shape), shape.point)
  67. },
  68. getRotatedBounds(shape) {
  69. return getBoundsFromPoints(
  70. getRotatedCorners(this.getBounds(shape), shape.rotation)
  71. )
  72. },
  73. getCenter(shape) {
  74. const bounds = this.getRotatedBounds(shape)
  75. return [bounds.minX + bounds.width / 2, bounds.minY + bounds.height / 2]
  76. },
  77. hitTest(shape, point) {
  78. let pt = vec.sub(point, shape.point)
  79. let prev = shape.points[0]
  80. for (let i = 1; i < shape.points.length; i++) {
  81. let curr = shape.points[i]
  82. if (
  83. vec.distanceToLineSegment(prev, curr, pt) < +shape.style.strokeWidth
  84. ) {
  85. return true
  86. }
  87. prev = curr
  88. }
  89. return false
  90. },
  91. hitTestBounds(this, shape, brushBounds) {
  92. const b = this.getBounds(shape)
  93. const center = [b.minX + b.width / 2, b.minY + b.height / 2]
  94. const rotatedCorners = [
  95. [b.minX, b.minY],
  96. [b.maxX, b.minY],
  97. [b.maxX, b.maxY],
  98. [b.minX, b.maxY],
  99. ].map((point) => vec.rotWith(point, center, shape.rotation))
  100. return (
  101. boundsContainPolygon(brushBounds, rotatedCorners) ||
  102. intersectPolylineBounds(
  103. shape.points.map((point) => vec.add(point, shape.point)),
  104. brushBounds
  105. ).length > 0
  106. )
  107. },
  108. rotateTo(shape, rotation) {
  109. shape.rotation = rotation
  110. // console.log(shape.points.map(([x, y]) => [x, y]))
  111. // const bounds = this.getBounds(shape)
  112. // const center = [bounds.width / 2, bounds.height / 2]
  113. // shape.points = shape.points.map((pt) => vec.rotWith(pt, center, rotation))
  114. return this
  115. },
  116. translateTo(shape, point) {
  117. shape.point = vec.toPrecision(point)
  118. return this
  119. },
  120. transform(shape, bounds, { initialShape, scaleX, scaleY }) {
  121. const initialShapeBounds = this.boundsCache.get(initialShape)
  122. shape.points = initialShape.points.map(([x, y]) => {
  123. return [
  124. bounds.width *
  125. (scaleX < 0
  126. ? 1 - x / initialShapeBounds.width
  127. : x / initialShapeBounds.width),
  128. bounds.height *
  129. (scaleY < 0
  130. ? 1 - y / initialShapeBounds.height
  131. : y / initialShapeBounds.height),
  132. ]
  133. })
  134. const newBounds = getBoundsFromPoints(shape.points)
  135. shape.point = vec.sub(
  136. [bounds.minX, bounds.minY],
  137. [newBounds.minX, newBounds.minY]
  138. )
  139. return this
  140. },
  141. transformSingle(shape, bounds, info) {
  142. this.transform(shape, bounds, info)
  143. return this
  144. },
  145. setProperty(shape, prop, value) {
  146. shape[prop] = value
  147. return this
  148. },
  149. canTransform: true,
  150. canChangeAspectRatio: true,
  151. })
  152. export default draw