Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

draw.tsx 4.9KB

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