Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

draw.tsx 4.5KB

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