You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

draw.tsx 4.8KB

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