@archireport/react-native-drawing 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/README.md +181 -0
  2. package/lib/commonjs/DrawingEditor.js +815 -0
  3. package/lib/commonjs/DrawingEditor.js.map +1 -0
  4. package/lib/commonjs/assets/toolbar-icons/arrow-disabled.png +0 -0
  5. package/lib/commonjs/assets/toolbar-icons/arrow-enabled.png +0 -0
  6. package/lib/commonjs/assets/toolbar-icons/arrow.png +0 -0
  7. package/lib/commonjs/assets/toolbar-icons/circle-disabled.png +0 -0
  8. package/lib/commonjs/assets/toolbar-icons/circle-enabled.png +0 -0
  9. package/lib/commonjs/assets/toolbar-icons/circle.png +0 -0
  10. package/lib/commonjs/assets/toolbar-icons/freehand-disabled.png +0 -0
  11. package/lib/commonjs/assets/toolbar-icons/freehand-enabled.png +0 -0
  12. package/lib/commonjs/assets/toolbar-icons/freehand.png +0 -0
  13. package/lib/commonjs/assets/toolbar-icons/line-disabled.png +0 -0
  14. package/lib/commonjs/assets/toolbar-icons/line-enabled.png +0 -0
  15. package/lib/commonjs/assets/toolbar-icons/line.png +0 -0
  16. package/lib/commonjs/assets/toolbar-icons/measure-disabled.png +0 -0
  17. package/lib/commonjs/assets/toolbar-icons/measure-enabled.png +0 -0
  18. package/lib/commonjs/assets/toolbar-icons/measure.png +0 -0
  19. package/lib/commonjs/assets/toolbar-icons/move-disabled.png +0 -0
  20. package/lib/commonjs/assets/toolbar-icons/move-enabled.png +0 -0
  21. package/lib/commonjs/assets/toolbar-icons/move.png +0 -0
  22. package/lib/commonjs/assets/toolbar-icons/polygon-disabled.png +0 -0
  23. package/lib/commonjs/assets/toolbar-icons/polygon-enabled.png +0 -0
  24. package/lib/commonjs/assets/toolbar-icons/polygon.png +0 -0
  25. package/lib/commonjs/assets/toolbar-icons/rectangle-disabled.png +0 -0
  26. package/lib/commonjs/assets/toolbar-icons/rectangle-enabled.png +0 -0
  27. package/lib/commonjs/assets/toolbar-icons/rectangle.png +0 -0
  28. package/lib/commonjs/assets/toolbar-icons/text-disabled.png +0 -0
  29. package/lib/commonjs/assets/toolbar-icons/text-enabled.png +0 -0
  30. package/lib/commonjs/assets/toolbar-icons/text.png +0 -0
  31. package/lib/commonjs/components/ColorPalette.js +379 -0
  32. package/lib/commonjs/components/ColorPalette.js.map +1 -0
  33. package/lib/commonjs/components/LineWidthSlider.js +70 -0
  34. package/lib/commonjs/components/LineWidthSlider.js.map +1 -0
  35. package/lib/commonjs/components/MeasurementEditModal.js +153 -0
  36. package/lib/commonjs/components/MeasurementEditModal.js.map +1 -0
  37. package/lib/commonjs/components/MiniMap.js +244 -0
  38. package/lib/commonjs/components/MiniMap.js.map +1 -0
  39. package/lib/commonjs/components/TextAnnotation.js +162 -0
  40. package/lib/commonjs/components/TextAnnotation.js.map +1 -0
  41. package/lib/commonjs/components/TextEditModal.js +133 -0
  42. package/lib/commonjs/components/TextEditModal.js.map +1 -0
  43. package/lib/commonjs/components/Toolbar.js +198 -0
  44. package/lib/commonjs/components/Toolbar.js.map +1 -0
  45. package/lib/commonjs/components/ZoomBadge.js +161 -0
  46. package/lib/commonjs/components/ZoomBadge.js.map +1 -0
  47. package/lib/commonjs/hooks/useFreehandGesture.js +173 -0
  48. package/lib/commonjs/hooks/useFreehandGesture.js.map +1 -0
  49. package/lib/commonjs/hooks/usePolygonGesture.js +109 -0
  50. package/lib/commonjs/hooks/usePolygonGesture.js.map +1 -0
  51. package/lib/commonjs/hooks/useSelectionGesture.js +236 -0
  52. package/lib/commonjs/hooks/useSelectionGesture.js.map +1 -0
  53. package/lib/commonjs/hooks/useShapeGesture.js +181 -0
  54. package/lib/commonjs/hooks/useShapeGesture.js.map +1 -0
  55. package/lib/commonjs/hooks/useViewportGesture.js +238 -0
  56. package/lib/commonjs/hooks/useViewportGesture.js.map +1 -0
  57. package/lib/commonjs/index.js +104 -0
  58. package/lib/commonjs/index.js.map +1 -0
  59. package/lib/commonjs/package.json +1 -0
  60. package/lib/commonjs/renderers/ArrowRenderer.js +118 -0
  61. package/lib/commonjs/renderers/ArrowRenderer.js.map +1 -0
  62. package/lib/commonjs/renderers/CircleRenderer.js +51 -0
  63. package/lib/commonjs/renderers/CircleRenderer.js.map +1 -0
  64. package/lib/commonjs/renderers/FreehandRenderer.js +31 -0
  65. package/lib/commonjs/renderers/FreehandRenderer.js.map +1 -0
  66. package/lib/commonjs/renderers/InProgressRenderer.js +174 -0
  67. package/lib/commonjs/renderers/InProgressRenderer.js.map +1 -0
  68. package/lib/commonjs/renderers/LineRenderer.js +27 -0
  69. package/lib/commonjs/renderers/LineRenderer.js.map +1 -0
  70. package/lib/commonjs/renderers/MeasurementRenderer.js +134 -0
  71. package/lib/commonjs/renderers/MeasurementRenderer.js.map +1 -0
  72. package/lib/commonjs/renderers/ObjectRenderer.js +65 -0
  73. package/lib/commonjs/renderers/ObjectRenderer.js.map +1 -0
  74. package/lib/commonjs/renderers/PolygonRenderer.js +46 -0
  75. package/lib/commonjs/renderers/PolygonRenderer.js.map +1 -0
  76. package/lib/commonjs/renderers/RectRenderer.js +51 -0
  77. package/lib/commonjs/renderers/RectRenderer.js.map +1 -0
  78. package/lib/commonjs/renderers/SelectedObjectRenderer.js +592 -0
  79. package/lib/commonjs/renderers/SelectedObjectRenderer.js.map +1 -0
  80. package/lib/commonjs/renderers/SelectionOverlay.js +120 -0
  81. package/lib/commonjs/renderers/SelectionOverlay.js.map +1 -0
  82. package/lib/commonjs/store/useDrawingStore.js +354 -0
  83. package/lib/commonjs/store/useDrawingStore.js.map +1 -0
  84. package/lib/commonjs/types.js +6 -0
  85. package/lib/commonjs/types.js.map +1 -0
  86. package/lib/commonjs/utils/colors.js +44 -0
  87. package/lib/commonjs/utils/colors.js.map +1 -0
  88. package/lib/commonjs/utils/coordinates.js +81 -0
  89. package/lib/commonjs/utils/coordinates.js.map +1 -0
  90. package/lib/commonjs/utils/hitTesting.js +181 -0
  91. package/lib/commonjs/utils/hitTesting.js.map +1 -0
  92. package/lib/commonjs/utils/serialization.js +42 -0
  93. package/lib/commonjs/utils/serialization.js.map +1 -0
  94. package/lib/commonjs/utils/shapeDetection.js +151 -0
  95. package/lib/commonjs/utils/shapeDetection.js.map +1 -0
  96. package/lib/commonjs/utils/smoothing.js +85 -0
  97. package/lib/commonjs/utils/smoothing.js.map +1 -0
  98. package/lib/module/DrawingEditor.js +811 -0
  99. package/lib/module/DrawingEditor.js.map +1 -0
  100. package/lib/module/assets/toolbar-icons/arrow-disabled.png +0 -0
  101. package/lib/module/assets/toolbar-icons/arrow-enabled.png +0 -0
  102. package/lib/module/assets/toolbar-icons/arrow.png +0 -0
  103. package/lib/module/assets/toolbar-icons/circle-disabled.png +0 -0
  104. package/lib/module/assets/toolbar-icons/circle-enabled.png +0 -0
  105. package/lib/module/assets/toolbar-icons/circle.png +0 -0
  106. package/lib/module/assets/toolbar-icons/freehand-disabled.png +0 -0
  107. package/lib/module/assets/toolbar-icons/freehand-enabled.png +0 -0
  108. package/lib/module/assets/toolbar-icons/freehand.png +0 -0
  109. package/lib/module/assets/toolbar-icons/line-disabled.png +0 -0
  110. package/lib/module/assets/toolbar-icons/line-enabled.png +0 -0
  111. package/lib/module/assets/toolbar-icons/line.png +0 -0
  112. package/lib/module/assets/toolbar-icons/measure-disabled.png +0 -0
  113. package/lib/module/assets/toolbar-icons/measure-enabled.png +0 -0
  114. package/lib/module/assets/toolbar-icons/measure.png +0 -0
  115. package/lib/module/assets/toolbar-icons/move-disabled.png +0 -0
  116. package/lib/module/assets/toolbar-icons/move-enabled.png +0 -0
  117. package/lib/module/assets/toolbar-icons/move.png +0 -0
  118. package/lib/module/assets/toolbar-icons/polygon-disabled.png +0 -0
  119. package/lib/module/assets/toolbar-icons/polygon-enabled.png +0 -0
  120. package/lib/module/assets/toolbar-icons/polygon.png +0 -0
  121. package/lib/module/assets/toolbar-icons/rectangle-disabled.png +0 -0
  122. package/lib/module/assets/toolbar-icons/rectangle-enabled.png +0 -0
  123. package/lib/module/assets/toolbar-icons/rectangle.png +0 -0
  124. package/lib/module/assets/toolbar-icons/text-disabled.png +0 -0
  125. package/lib/module/assets/toolbar-icons/text-enabled.png +0 -0
  126. package/lib/module/assets/toolbar-icons/text.png +0 -0
  127. package/lib/module/components/ColorPalette.js +374 -0
  128. package/lib/module/components/ColorPalette.js.map +1 -0
  129. package/lib/module/components/LineWidthSlider.js +64 -0
  130. package/lib/module/components/LineWidthSlider.js.map +1 -0
  131. package/lib/module/components/MeasurementEditModal.js +148 -0
  132. package/lib/module/components/MeasurementEditModal.js.map +1 -0
  133. package/lib/module/components/MiniMap.js +239 -0
  134. package/lib/module/components/MiniMap.js.map +1 -0
  135. package/lib/module/components/TextAnnotation.js +157 -0
  136. package/lib/module/components/TextAnnotation.js.map +1 -0
  137. package/lib/module/components/TextEditModal.js +128 -0
  138. package/lib/module/components/TextEditModal.js.map +1 -0
  139. package/lib/module/components/Toolbar.js +193 -0
  140. package/lib/module/components/Toolbar.js.map +1 -0
  141. package/lib/module/components/ZoomBadge.js +155 -0
  142. package/lib/module/components/ZoomBadge.js.map +1 -0
  143. package/lib/module/hooks/useFreehandGesture.js +169 -0
  144. package/lib/module/hooks/useFreehandGesture.js.map +1 -0
  145. package/lib/module/hooks/usePolygonGesture.js +106 -0
  146. package/lib/module/hooks/usePolygonGesture.js.map +1 -0
  147. package/lib/module/hooks/useSelectionGesture.js +232 -0
  148. package/lib/module/hooks/useSelectionGesture.js.map +1 -0
  149. package/lib/module/hooks/useShapeGesture.js +177 -0
  150. package/lib/module/hooks/useShapeGesture.js.map +1 -0
  151. package/lib/module/hooks/useViewportGesture.js +234 -0
  152. package/lib/module/hooks/useViewportGesture.js.map +1 -0
  153. package/lib/module/index.js +20 -0
  154. package/lib/module/index.js.map +1 -0
  155. package/lib/module/package.json +1 -0
  156. package/lib/module/renderers/ArrowRenderer.js +113 -0
  157. package/lib/module/renderers/ArrowRenderer.js.map +1 -0
  158. package/lib/module/renderers/CircleRenderer.js +46 -0
  159. package/lib/module/renderers/CircleRenderer.js.map +1 -0
  160. package/lib/module/renderers/FreehandRenderer.js +26 -0
  161. package/lib/module/renderers/FreehandRenderer.js.map +1 -0
  162. package/lib/module/renderers/InProgressRenderer.js +169 -0
  163. package/lib/module/renderers/InProgressRenderer.js.map +1 -0
  164. package/lib/module/renderers/LineRenderer.js +22 -0
  165. package/lib/module/renderers/LineRenderer.js.map +1 -0
  166. package/lib/module/renderers/MeasurementRenderer.js +129 -0
  167. package/lib/module/renderers/MeasurementRenderer.js.map +1 -0
  168. package/lib/module/renderers/ObjectRenderer.js +60 -0
  169. package/lib/module/renderers/ObjectRenderer.js.map +1 -0
  170. package/lib/module/renderers/PolygonRenderer.js +41 -0
  171. package/lib/module/renderers/PolygonRenderer.js.map +1 -0
  172. package/lib/module/renderers/RectRenderer.js +46 -0
  173. package/lib/module/renderers/RectRenderer.js.map +1 -0
  174. package/lib/module/renderers/SelectedObjectRenderer.js +587 -0
  175. package/lib/module/renderers/SelectedObjectRenderer.js.map +1 -0
  176. package/lib/module/renderers/SelectionOverlay.js +116 -0
  177. package/lib/module/renderers/SelectionOverlay.js.map +1 -0
  178. package/lib/module/store/useDrawingStore.js +350 -0
  179. package/lib/module/store/useDrawingStore.js.map +1 -0
  180. package/lib/module/types.js +4 -0
  181. package/lib/module/types.js.map +1 -0
  182. package/lib/module/utils/colors.js +40 -0
  183. package/lib/module/utils/colors.js.map +1 -0
  184. package/lib/module/utils/coordinates.js +71 -0
  185. package/lib/module/utils/coordinates.js.map +1 -0
  186. package/lib/module/utils/hitTesting.js +171 -0
  187. package/lib/module/utils/hitTesting.js.map +1 -0
  188. package/lib/module/utils/serialization.js +36 -0
  189. package/lib/module/utils/serialization.js.map +1 -0
  190. package/lib/module/utils/shapeDetection.js +147 -0
  191. package/lib/module/utils/shapeDetection.js.map +1 -0
  192. package/lib/module/utils/smoothing.js +80 -0
  193. package/lib/module/utils/smoothing.js.map +1 -0
  194. package/lib/typescript/DrawingEditor.d.ts +3 -0
  195. package/lib/typescript/DrawingEditor.d.ts.map +1 -0
  196. package/lib/typescript/components/ColorPalette.d.ts +9 -0
  197. package/lib/typescript/components/ColorPalette.d.ts.map +1 -0
  198. package/lib/typescript/components/LineWidthSlider.d.ts +11 -0
  199. package/lib/typescript/components/LineWidthSlider.d.ts.map +1 -0
  200. package/lib/typescript/components/MeasurementEditModal.d.ts +11 -0
  201. package/lib/typescript/components/MeasurementEditModal.d.ts.map +1 -0
  202. package/lib/typescript/components/MiniMap.d.ts +23 -0
  203. package/lib/typescript/components/MiniMap.d.ts.map +1 -0
  204. package/lib/typescript/components/TextAnnotation.d.ts +22 -0
  205. package/lib/typescript/components/TextAnnotation.d.ts.map +1 -0
  206. package/lib/typescript/components/TextEditModal.d.ts +10 -0
  207. package/lib/typescript/components/TextEditModal.d.ts.map +1 -0
  208. package/lib/typescript/components/Toolbar.d.ts +13 -0
  209. package/lib/typescript/components/Toolbar.d.ts.map +1 -0
  210. package/lib/typescript/components/ZoomBadge.d.ts +9 -0
  211. package/lib/typescript/components/ZoomBadge.d.ts.map +1 -0
  212. package/lib/typescript/hooks/useFreehandGesture.d.ts +47 -0
  213. package/lib/typescript/hooks/useFreehandGesture.d.ts.map +1 -0
  214. package/lib/typescript/hooks/usePolygonGesture.d.ts +47 -0
  215. package/lib/typescript/hooks/usePolygonGesture.d.ts.map +1 -0
  216. package/lib/typescript/hooks/useSelectionGesture.d.ts +32 -0
  217. package/lib/typescript/hooks/useSelectionGesture.d.ts.map +1 -0
  218. package/lib/typescript/hooks/useShapeGesture.d.ts +54 -0
  219. package/lib/typescript/hooks/useShapeGesture.d.ts.map +1 -0
  220. package/lib/typescript/hooks/useViewportGesture.d.ts +37 -0
  221. package/lib/typescript/hooks/useViewportGesture.d.ts.map +1 -0
  222. package/lib/typescript/index.d.ts +11 -0
  223. package/lib/typescript/index.d.ts.map +1 -0
  224. package/lib/typescript/renderers/ArrowRenderer.d.ts +9 -0
  225. package/lib/typescript/renderers/ArrowRenderer.d.ts.map +1 -0
  226. package/lib/typescript/renderers/CircleRenderer.d.ts +9 -0
  227. package/lib/typescript/renderers/CircleRenderer.d.ts.map +1 -0
  228. package/lib/typescript/renderers/FreehandRenderer.d.ts +9 -0
  229. package/lib/typescript/renderers/FreehandRenderer.d.ts.map +1 -0
  230. package/lib/typescript/renderers/InProgressRenderer.d.ts +32 -0
  231. package/lib/typescript/renderers/InProgressRenderer.d.ts.map +1 -0
  232. package/lib/typescript/renderers/LineRenderer.d.ts +9 -0
  233. package/lib/typescript/renderers/LineRenderer.d.ts.map +1 -0
  234. package/lib/typescript/renderers/MeasurementRenderer.d.ts +9 -0
  235. package/lib/typescript/renderers/MeasurementRenderer.d.ts.map +1 -0
  236. package/lib/typescript/renderers/ObjectRenderer.d.ts +12 -0
  237. package/lib/typescript/renderers/ObjectRenderer.d.ts.map +1 -0
  238. package/lib/typescript/renderers/PolygonRenderer.d.ts +13 -0
  239. package/lib/typescript/renderers/PolygonRenderer.d.ts.map +1 -0
  240. package/lib/typescript/renderers/RectRenderer.d.ts +9 -0
  241. package/lib/typescript/renderers/RectRenderer.d.ts.map +1 -0
  242. package/lib/typescript/renderers/SelectedObjectRenderer.d.ts +18 -0
  243. package/lib/typescript/renderers/SelectedObjectRenderer.d.ts.map +1 -0
  244. package/lib/typescript/renderers/SelectionOverlay.d.ts +21 -0
  245. package/lib/typescript/renderers/SelectionOverlay.d.ts.map +1 -0
  246. package/lib/typescript/store/useDrawingStore.d.ts +30 -0
  247. package/lib/typescript/store/useDrawingStore.d.ts.map +1 -0
  248. package/lib/typescript/types.d.ts +130 -0
  249. package/lib/typescript/types.d.ts.map +1 -0
  250. package/lib/typescript/utils/colors.d.ts +11 -0
  251. package/lib/typescript/utils/colors.d.ts.map +1 -0
  252. package/lib/typescript/utils/coordinates.d.ts +34 -0
  253. package/lib/typescript/utils/coordinates.d.ts.map +1 -0
  254. package/lib/typescript/utils/hitTesting.d.ts +18 -0
  255. package/lib/typescript/utils/hitTesting.d.ts.map +1 -0
  256. package/lib/typescript/utils/serialization.d.ts +17 -0
  257. package/lib/typescript/utils/serialization.d.ts.map +1 -0
  258. package/lib/typescript/utils/shapeDetection.d.ts +22 -0
  259. package/lib/typescript/utils/shapeDetection.d.ts.map +1 -0
  260. package/lib/typescript/utils/smoothing.d.ts +16 -0
  261. package/lib/typescript/utils/smoothing.d.ts.map +1 -0
  262. package/package.json +108 -0
  263. package/src/DrawingEditor.tsx +1071 -0
  264. package/src/assets/toolbar-icons/arrow-disabled.png +0 -0
  265. package/src/assets/toolbar-icons/arrow-enabled.png +0 -0
  266. package/src/assets/toolbar-icons/arrow.png +0 -0
  267. package/src/assets/toolbar-icons/circle-disabled.png +0 -0
  268. package/src/assets/toolbar-icons/circle-enabled.png +0 -0
  269. package/src/assets/toolbar-icons/circle.png +0 -0
  270. package/src/assets/toolbar-icons/freehand-disabled.png +0 -0
  271. package/src/assets/toolbar-icons/freehand-enabled.png +0 -0
  272. package/src/assets/toolbar-icons/freehand.png +0 -0
  273. package/src/assets/toolbar-icons/line-disabled.png +0 -0
  274. package/src/assets/toolbar-icons/line-enabled.png +0 -0
  275. package/src/assets/toolbar-icons/line.png +0 -0
  276. package/src/assets/toolbar-icons/measure-disabled.png +0 -0
  277. package/src/assets/toolbar-icons/measure-enabled.png +0 -0
  278. package/src/assets/toolbar-icons/measure.png +0 -0
  279. package/src/assets/toolbar-icons/move-disabled.png +0 -0
  280. package/src/assets/toolbar-icons/move-enabled.png +0 -0
  281. package/src/assets/toolbar-icons/move.png +0 -0
  282. package/src/assets/toolbar-icons/polygon-disabled.png +0 -0
  283. package/src/assets/toolbar-icons/polygon-enabled.png +0 -0
  284. package/src/assets/toolbar-icons/polygon.png +0 -0
  285. package/src/assets/toolbar-icons/rectangle-disabled.png +0 -0
  286. package/src/assets/toolbar-icons/rectangle-enabled.png +0 -0
  287. package/src/assets/toolbar-icons/rectangle.png +0 -0
  288. package/src/assets/toolbar-icons/text-disabled.png +0 -0
  289. package/src/assets/toolbar-icons/text-enabled.png +0 -0
  290. package/src/assets/toolbar-icons/text.png +0 -0
  291. package/src/components/ColorPalette.tsx +497 -0
  292. package/src/components/LineWidthSlider.tsx +87 -0
  293. package/src/components/MeasurementEditModal.tsx +163 -0
  294. package/src/components/MiniMap.tsx +275 -0
  295. package/src/components/TextAnnotation.tsx +198 -0
  296. package/src/components/TextEditModal.tsx +139 -0
  297. package/src/components/Toolbar.tsx +254 -0
  298. package/src/components/ZoomBadge.tsx +166 -0
  299. package/src/hooks/useFreehandGesture.ts +249 -0
  300. package/src/hooks/usePolygonGesture.ts +162 -0
  301. package/src/hooks/useSelectionGesture.ts +293 -0
  302. package/src/hooks/useShapeGesture.ts +256 -0
  303. package/src/hooks/useViewportGesture.ts +337 -0
  304. package/src/index.tsx +51 -0
  305. package/src/renderers/ArrowRenderer.tsx +123 -0
  306. package/src/renderers/CircleRenderer.tsx +60 -0
  307. package/src/renderers/FreehandRenderer.tsx +33 -0
  308. package/src/renderers/InProgressRenderer.tsx +217 -0
  309. package/src/renderers/LineRenderer.tsx +34 -0
  310. package/src/renderers/MeasurementRenderer.tsx +179 -0
  311. package/src/renderers/ObjectRenderer.tsx +42 -0
  312. package/src/renderers/PolygonRenderer.tsx +66 -0
  313. package/src/renderers/RectRenderer.tsx +60 -0
  314. package/src/renderers/SelectedObjectRenderer.tsx +738 -0
  315. package/src/renderers/SelectionOverlay.tsx +170 -0
  316. package/src/store/useDrawingStore.ts +357 -0
  317. package/src/types.ts +186 -0
  318. package/src/utils/colors.ts +98 -0
  319. package/src/utils/coordinates.ts +75 -0
  320. package/src/utils/hitTesting.ts +242 -0
  321. package/src/utils/serialization.ts +45 -0
  322. package/src/utils/shapeDetection.ts +160 -0
  323. package/src/utils/smoothing.ts +84 -0
@@ -0,0 +1,738 @@
1
+ import React, { useEffect, useMemo } from "react";
2
+ import { Platform } from "react-native";
3
+ import {
4
+ Line as SkiaLine,
5
+ Rect,
6
+ Oval,
7
+ Group,
8
+ Path,
9
+ Skia,
10
+ Text,
11
+ matchFont,
12
+ } from "@shopify/react-native-skia";
13
+ import { useDerivedValue } from "react-native-reanimated";
14
+ import type { SharedValue } from "react-native-reanimated";
15
+ import type { DrawingObject, TwoPointObject, FreehandObject, PolygonObject, MeasureObject, Size } from "../types";
16
+ import { denormalize, denormalizePoints } from "../utils/coordinates";
17
+ import { buildSmoothedPath } from "../utils/smoothing";
18
+
19
+ const FONT_FAMILY = Platform.OS === "android" ? "sans-serif" : "System";
20
+ const TEXT_GAP_PADDING = 6;
21
+
22
+ interface SelectedObjectRendererProps {
23
+ object: DrawingObject;
24
+ canvasSize: Size;
25
+ isDragging: SharedValue<boolean>;
26
+ draggingAnchorIndex: SharedValue<number>;
27
+ dragOffsetX: SharedValue<number>;
28
+ dragOffsetY: SharedValue<number>;
29
+ }
30
+
31
+ /**
32
+ * Renders the selected object with animated positions that follow
33
+ * the drag gesture in real-time — both for move and resize.
34
+ */
35
+ export const SelectedObjectRenderer = React.memo(
36
+ function SelectedObjectRenderer({
37
+ object,
38
+ canvasSize,
39
+ isDragging,
40
+ draggingAnchorIndex,
41
+ dragOffsetX,
42
+ dragOffsetY,
43
+ }: SelectedObjectRendererProps) {
44
+ // When the store commits new positions after a drag, the component
45
+ // re-renders with the updated object. Reset drag state at that point
46
+ // so the transition from "base + offset" to "newBase + 0" is atomic.
47
+ useEffect(() => {
48
+ if (isDragging.value) {
49
+ isDragging.value = false;
50
+ dragOffsetX.value = 0;
51
+ dragOffsetY.value = 0;
52
+ draggingAnchorIndex.value = -1;
53
+ }
54
+ }, [object, isDragging, dragOffsetX, dragOffsetY, draggingAnchorIndex]);
55
+
56
+ switch (object.type) {
57
+ case "line":
58
+ return (
59
+ <AnimatedLine
60
+ object={object as TwoPointObject}
61
+ canvasSize={canvasSize}
62
+ isDragging={isDragging}
63
+ draggingAnchorIndex={draggingAnchorIndex}
64
+ dragOffsetX={dragOffsetX}
65
+ dragOffsetY={dragOffsetY}
66
+ />
67
+ );
68
+ case "measure":
69
+ return (
70
+ <AnimatedMeasurement
71
+ object={object as MeasureObject}
72
+ canvasSize={canvasSize}
73
+ isDragging={isDragging}
74
+ draggingAnchorIndex={draggingAnchorIndex}
75
+ dragOffsetX={dragOffsetX}
76
+ dragOffsetY={dragOffsetY}
77
+ />
78
+ );
79
+ case "arrow":
80
+ return (
81
+ <AnimatedArrow
82
+ object={object as TwoPointObject}
83
+ canvasSize={canvasSize}
84
+ isDragging={isDragging}
85
+ draggingAnchorIndex={draggingAnchorIndex}
86
+ dragOffsetX={dragOffsetX}
87
+ dragOffsetY={dragOffsetY}
88
+ />
89
+ );
90
+ case "rectangle":
91
+ return (
92
+ <AnimatedRect
93
+ object={object as TwoPointObject & { backgroundColor?: string; backgroundAlpha?: number }}
94
+ canvasSize={canvasSize}
95
+ isDragging={isDragging}
96
+ draggingAnchorIndex={draggingAnchorIndex}
97
+ dragOffsetX={dragOffsetX}
98
+ dragOffsetY={dragOffsetY}
99
+ />
100
+ );
101
+ case "circle":
102
+ return (
103
+ <AnimatedOval
104
+ object={object as TwoPointObject & { backgroundColor?: string; backgroundAlpha?: number }}
105
+ canvasSize={canvasSize}
106
+ isDragging={isDragging}
107
+ draggingAnchorIndex={draggingAnchorIndex}
108
+ dragOffsetX={dragOffsetX}
109
+ dragOffsetY={dragOffsetY}
110
+ />
111
+ );
112
+ case "freehand":
113
+ return (
114
+ <AnimatedFreehand
115
+ object={object as FreehandObject}
116
+ canvasSize={canvasSize}
117
+ isDragging={isDragging}
118
+ draggingAnchorIndex={draggingAnchorIndex}
119
+ dragOffsetX={dragOffsetX}
120
+ dragOffsetY={dragOffsetY}
121
+ />
122
+ );
123
+ case "polygon":
124
+ return (
125
+ <AnimatedPolygon
126
+ object={object as PolygonObject}
127
+ canvasSize={canvasSize}
128
+ isDragging={isDragging}
129
+ draggingAnchorIndex={draggingAnchorIndex}
130
+ dragOffsetX={dragOffsetX}
131
+ dragOffsetY={dragOffsetY}
132
+ />
133
+ );
134
+ case "text":
135
+ return null;
136
+ }
137
+ },
138
+ );
139
+
140
+ // ─── Shared types ────────────────────────────────────────────────────────────
141
+
142
+ interface AnimatedShapeProps {
143
+ object: TwoPointObject;
144
+ canvasSize: Size;
145
+ isDragging: SharedValue<boolean>;
146
+ draggingAnchorIndex: SharedValue<number>;
147
+ dragOffsetX: SharedValue<number>;
148
+ dragOffsetY: SharedValue<number>;
149
+ }
150
+
151
+ /**
152
+ * Hook: compute animated from/to pixel positions for a two-point object.
153
+ * - anchor -1 (move): both points shift
154
+ * - anchor 0: only `from` shifts
155
+ * - anchor 1: only `to` shifts
156
+ */
157
+ function useAnimatedEndpoints(
158
+ object: TwoPointObject,
159
+ canvasSize: Size,
160
+ isDragging: SharedValue<boolean>,
161
+ draggingAnchorIndex: SharedValue<number>,
162
+ dragOffsetX: SharedValue<number>,
163
+ dragOffsetY: SharedValue<number>,
164
+ ) {
165
+ const baseFrom = useMemo(
166
+ () => denormalize(object.from, canvasSize),
167
+ [object.from, canvasSize],
168
+ );
169
+ const baseTo = useMemo(
170
+ () => denormalize(object.to, canvasSize),
171
+ [object.to, canvasSize],
172
+ );
173
+
174
+ const fromX = useDerivedValue(() => {
175
+ if (!isDragging.value) return baseFrom.x;
176
+ // Move or resize anchor 0 → from moves
177
+ if (draggingAnchorIndex.value <= 0) return baseFrom.x + dragOffsetX.value;
178
+ return baseFrom.x;
179
+ });
180
+ const fromY = useDerivedValue(() => {
181
+ if (!isDragging.value) return baseFrom.y;
182
+ if (draggingAnchorIndex.value <= 0) return baseFrom.y + dragOffsetY.value;
183
+ return baseFrom.y;
184
+ });
185
+ const toX = useDerivedValue(() => {
186
+ if (!isDragging.value) return baseTo.x;
187
+ // Move or resize anchor 1 → to moves
188
+ if (draggingAnchorIndex.value < 0 || draggingAnchorIndex.value === 1)
189
+ return baseTo.x + dragOffsetX.value;
190
+ return baseTo.x;
191
+ });
192
+ const toY = useDerivedValue(() => {
193
+ if (!isDragging.value) return baseTo.y;
194
+ if (draggingAnchorIndex.value < 0 || draggingAnchorIndex.value === 1)
195
+ return baseTo.y + dragOffsetY.value;
196
+ return baseTo.y;
197
+ });
198
+
199
+ return { fromX, fromY, toX, toY };
200
+ }
201
+
202
+ // ─── Line / Measure ──────────────────────────────────────────────────────────
203
+
204
+ function AnimatedLine({
205
+ object,
206
+ canvasSize,
207
+ isDragging,
208
+ draggingAnchorIndex,
209
+ dragOffsetX,
210
+ dragOffsetY,
211
+ }: AnimatedShapeProps) {
212
+ const { fromX, fromY, toX, toY } = useAnimatedEndpoints(
213
+ object,
214
+ canvasSize,
215
+ isDragging,
216
+ draggingAnchorIndex,
217
+ dragOffsetX,
218
+ dragOffsetY,
219
+ );
220
+
221
+ const p1 = useDerivedValue(() => ({ x: fromX.value, y: fromY.value }));
222
+ const p2 = useDerivedValue(() => ({ x: toX.value, y: toY.value }));
223
+
224
+ return (
225
+ <SkiaLine
226
+ p1={p1}
227
+ p2={p2}
228
+ color={object.color}
229
+ style="stroke"
230
+ strokeWidth={object.lineWidth}
231
+ strokeCap="round"
232
+ />
233
+ );
234
+ }
235
+
236
+ // ─── Arrow ───────────────────────────────────────────────────────────────────
237
+
238
+ function AnimatedArrow({
239
+ object,
240
+ canvasSize,
241
+ isDragging,
242
+ draggingAnchorIndex,
243
+ dragOffsetX,
244
+ dragOffsetY,
245
+ }: AnimatedShapeProps) {
246
+ const { fromX, fromY, toX, toY } = useAnimatedEndpoints(
247
+ object,
248
+ canvasSize,
249
+ isDragging,
250
+ draggingAnchorIndex,
251
+ dragOffsetX,
252
+ dragOffsetY,
253
+ );
254
+
255
+ const arrowPath = useDerivedValue(() => {
256
+ const p = Skia.Path.Make();
257
+ const fx = fromX.value;
258
+ const fy = fromY.value;
259
+ const tx = toX.value;
260
+ const ty = toY.value;
261
+
262
+ const ddx = tx - fx;
263
+ const ddy = ty - fy;
264
+ const len = Math.sqrt(ddx * ddx + ddy * ddy);
265
+ if (len === 0) return p;
266
+
267
+ const angle = Math.atan2(ddy, ddx);
268
+ const cos = Math.cos(angle);
269
+ const sin = Math.sin(angle);
270
+ const lw = object.lineWidth;
271
+
272
+ if (lw < 8) {
273
+ const headLen = lw * 2;
274
+ const headW = lw * 2;
275
+ p.moveTo(fx, fy);
276
+ p.lineTo(tx, ty);
277
+ const hx1 = tx - headLen * cos + (headW / 2) * sin;
278
+ const hy1 = ty - headLen * sin - (headW / 2) * cos;
279
+ const hx2 = tx - headLen * cos - (headW / 2) * sin;
280
+ const hy2 = ty - headLen * sin + (headW / 2) * cos;
281
+ p.moveTo(tx, ty);
282
+ p.lineTo(hx1, hy1);
283
+ p.moveTo(tx, ty);
284
+ p.lineTo(hx2, hy2);
285
+ } else {
286
+ const headLen = lw * 3;
287
+ const headW = lw * 3;
288
+ const bodyW = lw;
289
+ const bodyLen = Math.max(0, len - headLen);
290
+ const halfBody = bodyW / 2;
291
+
292
+ const tf = (px: number, py: number) => ({
293
+ x: fx + px * cos - py * sin,
294
+ y: fy + px * sin + py * cos,
295
+ });
296
+
297
+ const tb1 = tf(0, -halfBody);
298
+ const tb2 = tf(bodyLen, -halfBody);
299
+ const tb3 = tf(bodyLen, halfBody);
300
+ const tb4 = tf(0, halfBody);
301
+ const th1 = tf(bodyLen, -headW / 2);
302
+ const th2 = tf(len, 0);
303
+ const th3 = tf(bodyLen, headW / 2);
304
+
305
+ p.moveTo(tb1.x, tb1.y);
306
+ p.lineTo(tb2.x, tb2.y);
307
+ p.lineTo(tb3.x, tb3.y);
308
+ p.lineTo(tb4.x, tb4.y);
309
+ p.close();
310
+ p.moveTo(th1.x, th1.y);
311
+ p.lineTo(th2.x, th2.y);
312
+ p.lineTo(th3.x, th3.y);
313
+ p.close();
314
+ }
315
+
316
+ return p;
317
+ });
318
+
319
+ if (object.lineWidth < 8) {
320
+ return (
321
+ <Path
322
+ path={arrowPath}
323
+ style="stroke"
324
+ strokeWidth={object.lineWidth}
325
+ color={object.color}
326
+ strokeCap="round"
327
+ strokeJoin="round"
328
+ />
329
+ );
330
+ }
331
+
332
+ return <Path path={arrowPath} style="fill" color={object.color} />;
333
+ }
334
+
335
+ // ─── Rectangle ───────────────────────────────────────────────────────────────
336
+
337
+ function AnimatedRect({
338
+ object,
339
+ canvasSize,
340
+ isDragging,
341
+ draggingAnchorIndex,
342
+ dragOffsetX,
343
+ dragOffsetY,
344
+ }: AnimatedShapeProps & { object: TwoPointObject & { backgroundColor?: string; backgroundAlpha?: number } }) {
345
+ const { fromX, fromY, toX, toY } = useAnimatedEndpoints(
346
+ object,
347
+ canvasSize,
348
+ isDragging,
349
+ draggingAnchorIndex,
350
+ dragOffsetX,
351
+ dragOffsetY,
352
+ );
353
+
354
+ const x = useDerivedValue(() => Math.min(fromX.value, toX.value));
355
+ const y = useDerivedValue(() => Math.min(fromY.value, toY.value));
356
+ const width = useDerivedValue(() => Math.abs(toX.value - fromX.value));
357
+ const height = useDerivedValue(() => Math.abs(toY.value - fromY.value));
358
+
359
+ const hasFill = object.backgroundColor && object.backgroundAlpha;
360
+ const fillColor = hasFill
361
+ ? object.backgroundColor!.replace(
362
+ /[\d.]+\)$/,
363
+ `${object.backgroundAlpha})`,
364
+ )
365
+ : undefined;
366
+
367
+ return (
368
+ <Group>
369
+ {fillColor && (
370
+ <Rect
371
+ x={x}
372
+ y={y}
373
+ width={width}
374
+ height={height}
375
+ color={fillColor}
376
+ style="fill"
377
+ />
378
+ )}
379
+ <Rect
380
+ x={x}
381
+ y={y}
382
+ width={width}
383
+ height={height}
384
+ color={object.color}
385
+ style="stroke"
386
+ strokeWidth={object.lineWidth}
387
+ />
388
+ </Group>
389
+ );
390
+ }
391
+
392
+ // ─── Circle / Oval ───────────────────────────────────────────────────────────
393
+
394
+ function AnimatedOval({
395
+ object,
396
+ canvasSize,
397
+ isDragging,
398
+ draggingAnchorIndex,
399
+ dragOffsetX,
400
+ dragOffsetY,
401
+ }: AnimatedShapeProps & { object: TwoPointObject & { backgroundColor?: string; backgroundAlpha?: number } }) {
402
+ const { fromX, fromY, toX, toY } = useAnimatedEndpoints(
403
+ object,
404
+ canvasSize,
405
+ isDragging,
406
+ draggingAnchorIndex,
407
+ dragOffsetX,
408
+ dragOffsetY,
409
+ );
410
+
411
+ const x = useDerivedValue(() => Math.min(fromX.value, toX.value));
412
+ const y = useDerivedValue(() => Math.min(fromY.value, toY.value));
413
+ const width = useDerivedValue(() => Math.abs(toX.value - fromX.value));
414
+ const height = useDerivedValue(() => Math.abs(toY.value - fromY.value));
415
+
416
+ const hasFill = object.backgroundColor && object.backgroundAlpha;
417
+ const fillColor = hasFill
418
+ ? object.backgroundColor!.replace(
419
+ /[\d.]+\)$/,
420
+ `${object.backgroundAlpha})`,
421
+ )
422
+ : undefined;
423
+
424
+ return (
425
+ <Group>
426
+ {fillColor && (
427
+ <Oval
428
+ x={x}
429
+ y={y}
430
+ width={width}
431
+ height={height}
432
+ color={fillColor}
433
+ style="fill"
434
+ />
435
+ )}
436
+ <Oval
437
+ x={x}
438
+ y={y}
439
+ width={width}
440
+ height={height}
441
+ color={object.color}
442
+ style="stroke"
443
+ strokeWidth={object.lineWidth}
444
+ />
445
+ </Group>
446
+ );
447
+ }
448
+
449
+ // ─── Freehand ────────────────────────────────────────────────────────────────
450
+
451
+ function AnimatedFreehand({
452
+ object,
453
+ canvasSize,
454
+ isDragging,
455
+ draggingAnchorIndex,
456
+ dragOffsetX,
457
+ dragOffsetY,
458
+ }: {
459
+ object: FreehandObject;
460
+ canvasSize: Size;
461
+ isDragging: SharedValue<boolean>;
462
+ draggingAnchorIndex: SharedValue<number>;
463
+ dragOffsetX: SharedValue<number>;
464
+ dragOffsetY: SharedValue<number>;
465
+ }) {
466
+ const pathString = useMemo(() => {
467
+ const denormalized = denormalizePoints(object.points, canvasSize);
468
+ return buildSmoothedPath(denormalized);
469
+ }, [object.points, canvasSize]);
470
+
471
+ // For freehand, we use a Group transform for whole-object move.
472
+ // Anchor-specific resize isn't well suited to visual preview for freehand.
473
+ const transform = useDerivedValue(() => {
474
+ if (!isDragging.value) return [{ translateX: 0 }, { translateY: 0 }];
475
+ if (draggingAnchorIndex.value < 0) {
476
+ return [
477
+ { translateX: dragOffsetX.value },
478
+ { translateY: dragOffsetY.value },
479
+ ];
480
+ }
481
+ // For endpoint resize on freehand, no visual preview of the path deformation
482
+ return [{ translateX: 0 }, { translateY: 0 }];
483
+ });
484
+
485
+ if (!pathString) return null;
486
+
487
+ return (
488
+ <Group transform={transform}>
489
+ <Path
490
+ path={pathString}
491
+ style="stroke"
492
+ strokeWidth={object.lineWidth}
493
+ color={object.color}
494
+ strokeCap="round"
495
+ strokeJoin="round"
496
+ />
497
+ </Group>
498
+ );
499
+ }
500
+
501
+ // ─── Polygon ─────────────────────────────────────────────────────────────────
502
+
503
+ function AnimatedPolygon({
504
+ object,
505
+ canvasSize,
506
+ isDragging,
507
+ draggingAnchorIndex,
508
+ dragOffsetX,
509
+ dragOffsetY,
510
+ }: {
511
+ object: PolygonObject;
512
+ canvasSize: Size;
513
+ isDragging: SharedValue<boolean>;
514
+ draggingAnchorIndex: SharedValue<number>;
515
+ dragOffsetX: SharedValue<number>;
516
+ dragOffsetY: SharedValue<number>;
517
+ }) {
518
+ const basePoints = useMemo(
519
+ () => denormalizePoints(object.points, canvasSize),
520
+ [object.points, canvasSize],
521
+ );
522
+
523
+ // Build animated path; move-all translates, vertex-drag moves one point
524
+ const animatedPath = useDerivedValue(() => {
525
+ const p = Skia.Path.Make();
526
+ if (basePoints.length < 2) return p;
527
+
528
+ for (let i = 0; i < basePoints.length; i++) {
529
+ let x = basePoints[i]!.x;
530
+ let y = basePoints[i]!.y;
531
+
532
+ if (isDragging.value) {
533
+ if (draggingAnchorIndex.value < 0) {
534
+ // Move whole object
535
+ x += dragOffsetX.value;
536
+ y += dragOffsetY.value;
537
+ } else if (draggingAnchorIndex.value === i) {
538
+ // Move single vertex
539
+ x += dragOffsetX.value;
540
+ y += dragOffsetY.value;
541
+ }
542
+ }
543
+
544
+ if (i === 0) p.moveTo(x, y);
545
+ else p.lineTo(x, y);
546
+ }
547
+ if (object.closed) p.close();
548
+ return p;
549
+ });
550
+
551
+ const hasFill =
552
+ object.closed && object.backgroundColor && object.backgroundAlpha;
553
+ const fillColor = hasFill
554
+ ? object.backgroundColor!.replace(
555
+ /[\d.]+\)$/,
556
+ `${object.backgroundAlpha})`,
557
+ )
558
+ : undefined;
559
+
560
+ return (
561
+ <Group>
562
+ {fillColor && (
563
+ <Path path={animatedPath} style="fill" color={fillColor} />
564
+ )}
565
+ <Path
566
+ path={animatedPath}
567
+ style="stroke"
568
+ strokeWidth={object.lineWidth}
569
+ color={object.color}
570
+ strokeCap="round"
571
+ strokeJoin="round"
572
+ />
573
+ </Group>
574
+ );
575
+ }
576
+
577
+ // ─── Measurement ─────────────────────────────────────────────────────────────
578
+
579
+ function AnimatedMeasurement({
580
+ object,
581
+ canvasSize,
582
+ isDragging,
583
+ draggingAnchorIndex,
584
+ dragOffsetX,
585
+ dragOffsetY,
586
+ }: {
587
+ object: MeasureObject;
588
+ canvasSize: Size;
589
+ isDragging: SharedValue<boolean>;
590
+ draggingAnchorIndex: SharedValue<number>;
591
+ dragOffsetX: SharedValue<number>;
592
+ dragOffsetY: SharedValue<number>;
593
+ }) {
594
+ const { fromX, fromY, toX, toY } = useAnimatedEndpoints(
595
+ object,
596
+ canvasSize,
597
+ isDragging,
598
+ draggingAnchorIndex,
599
+ dragOffsetX,
600
+ dragOffsetY,
601
+ );
602
+
603
+ const labelText = `${object.text}${object.unit}`;
604
+ const hasLabel = object.text.length > 0;
605
+
606
+ const fontSize = Math.max(14, object.lineWidth * 3);
607
+ const font = useMemo(
608
+ () => matchFont({ fontFamily: FONT_FAMILY, fontSize }),
609
+ [fontSize],
610
+ );
611
+
612
+ const textWidth = useMemo(() => {
613
+ if (!hasLabel) return 0;
614
+ return font.getTextWidth(labelText);
615
+ }, [font, labelText, hasLabel]);
616
+
617
+ const gapHalfWidth = hasLabel ? textWidth / 2 + TEXT_GAP_PADDING : 0;
618
+
619
+ const measurePath = useDerivedValue(() => {
620
+ const p = Skia.Path.Make();
621
+ const fx = fromX.value;
622
+ const fy = fromY.value;
623
+ const tx = toX.value;
624
+ const ty = toY.value;
625
+
626
+ const ddx = tx - fx;
627
+ const ddy = ty - fy;
628
+ const len = Math.sqrt(ddx * ddx + ddy * ddy);
629
+ if (len === 0) return p;
630
+
631
+ const angle = Math.atan2(ddy, ddx);
632
+ const cos = Math.cos(angle);
633
+ const sin = Math.sin(angle);
634
+ const perpCos = Math.cos(angle + Math.PI / 2);
635
+ const perpSin = Math.sin(angle + Math.PI / 2);
636
+ const lw = object.lineWidth;
637
+
638
+ const headLen = Math.min(lw * 3, len / 4);
639
+ const headW = lw * 2;
640
+ const extLen = lw * 4;
641
+
642
+ const mx = (fx + tx) / 2;
643
+ const my = (fy + ty) / 2;
644
+
645
+ // Main line with gap for label
646
+ if (gapHalfWidth > 0 && len > gapHalfWidth * 2) {
647
+ const gsX = mx - gapHalfWidth * cos;
648
+ const gsY = my - gapHalfWidth * sin;
649
+ const geX = mx + gapHalfWidth * cos;
650
+ const geY = my + gapHalfWidth * sin;
651
+ p.moveTo(fx, fy);
652
+ p.lineTo(gsX, gsY);
653
+ p.moveTo(geX, geY);
654
+ p.lineTo(tx, ty);
655
+ } else {
656
+ p.moveTo(fx, fy);
657
+ p.lineTo(tx, ty);
658
+ }
659
+
660
+ // Arrowhead at "from" pointing toward "to"
661
+ const ah1x = fx + headLen * cos + (headW / 2) * perpCos;
662
+ const ah1y = fy + headLen * sin + (headW / 2) * perpSin;
663
+ const ah2x = fx + headLen * cos - (headW / 2) * perpCos;
664
+ const ah2y = fy + headLen * sin - (headW / 2) * perpSin;
665
+ p.moveTo(fx, fy);
666
+ p.lineTo(ah1x, ah1y);
667
+ p.moveTo(fx, fy);
668
+ p.lineTo(ah2x, ah2y);
669
+
670
+ // Arrowhead at "to" pointing toward "from"
671
+ const bh1x = tx - headLen * cos + (headW / 2) * perpCos;
672
+ const bh1y = ty - headLen * sin + (headW / 2) * perpSin;
673
+ const bh2x = tx - headLen * cos - (headW / 2) * perpCos;
674
+ const bh2y = ty - headLen * sin - (headW / 2) * perpSin;
675
+ p.moveTo(tx, ty);
676
+ p.lineTo(bh1x, bh1y);
677
+ p.moveTo(tx, ty);
678
+ p.lineTo(bh2x, bh2y);
679
+
680
+ // Extension lines
681
+ if (object.measureStyle === 0 || object.measureStyle === 2) {
682
+ p.moveTo(fx + extLen * perpCos, fy + extLen * perpSin);
683
+ p.lineTo(fx - extLen * perpCos, fy - extLen * perpSin);
684
+ p.moveTo(tx + extLen * perpCos, ty + extLen * perpSin);
685
+ p.lineTo(tx - extLen * perpCos, ty - extLen * perpSin);
686
+ }
687
+
688
+ return p;
689
+ });
690
+
691
+ // Animated text transform: translate to midpoint + rotate
692
+ const textTransform = useDerivedValue(() => {
693
+ const mx = (fromX.value + toX.value) / 2;
694
+ const my = (fromY.value + toY.value) / 2;
695
+ const rawAngle = Math.atan2(
696
+ toY.value - fromY.value,
697
+ toX.value - fromX.value,
698
+ );
699
+ const finalAngle =
700
+ rawAngle > Math.PI / 2
701
+ ? rawAngle - Math.PI
702
+ : rawAngle < -Math.PI / 2
703
+ ? rawAngle + Math.PI
704
+ : rawAngle;
705
+ return [
706
+ { translateX: mx },
707
+ { translateY: my },
708
+ { rotate: finalAngle },
709
+ ];
710
+ });
711
+
712
+ const textX = -textWidth / 2;
713
+ const textY = fontSize / 3;
714
+
715
+ return (
716
+ <Group>
717
+ <Path
718
+ path={measurePath}
719
+ style="stroke"
720
+ strokeWidth={object.lineWidth}
721
+ color={object.color}
722
+ strokeCap="round"
723
+ strokeJoin="round"
724
+ />
725
+ {hasLabel && (
726
+ <Group transform={textTransform}>
727
+ <Text
728
+ x={textX}
729
+ y={textY}
730
+ text={labelText}
731
+ font={font}
732
+ color={object.color}
733
+ />
734
+ </Group>
735
+ )}
736
+ </Group>
737
+ );
738
+ }