@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,98 @@
1
+ /** Preset color palette matching the ArchiReport iOS spec */
2
+ export const PRESET_COLORS = [
3
+ "rgba(255, 79, 61, 1)", // Red
4
+ "rgba(94, 112, 255, 1)", // Blue
5
+ "rgba(66, 224, 125, 1)", // Green
6
+ "rgba(255, 163, 69, 1)", // Orange
7
+ "rgba(46, 46, 46, 1)", // Black (dark)
8
+ "rgba(255, 255, 255, 1)", // White
9
+ ] as const;
10
+
11
+ /** Extended color palette grouped by hue families for a more predictable picker layout */
12
+ export const EXTENDED_COLOR_GROUPS: readonly string[][] = [
13
+ [
14
+ // Reds / corals / pinks
15
+ "rgba(255, 121, 107, 1)",
16
+ "rgba(255, 79, 61, 1)",
17
+ "rgba(239, 68, 68, 1)",
18
+ "rgba(220, 38, 38, 1)",
19
+ "rgba(183, 28, 28, 1)",
20
+ "rgba(255, 99, 132, 1)",
21
+ "rgba(244, 63, 94, 1)",
22
+ "rgba(219, 39, 119, 1)",
23
+ ],
24
+ [
25
+ // Oranges / ambers / yellows
26
+ "rgba(255, 190, 92, 1)",
27
+ "rgba(255, 163, 69, 1)",
28
+ "rgba(249, 115, 22, 1)",
29
+ "rgba(234, 88, 12, 1)",
30
+ "rgba(217, 119, 6, 1)",
31
+ "rgba(250, 204, 21, 1)",
32
+ "rgba(234, 179, 8, 1)",
33
+ "rgba(202, 138, 4, 1)",
34
+ ],
35
+ [
36
+ // Greens
37
+ "rgba(134, 239, 172, 1)",
38
+ "rgba(66, 224, 125, 1)",
39
+ "rgba(74, 222, 128, 1)",
40
+ "rgba(34, 197, 94, 1)",
41
+ "rgba(22, 163, 74, 1)",
42
+ "rgba(45, 212, 191, 1)",
43
+ "rgba(16, 185, 129, 1)",
44
+ "rgba(5, 150, 105, 1)",
45
+ ],
46
+ [
47
+ // Teals / cyans
48
+ "rgba(94, 234, 212, 1)",
49
+ "rgba(20, 184, 166, 1)",
50
+ "rgba(13, 148, 136, 1)",
51
+ "rgba(8, 145, 178, 1)",
52
+ "rgba(34, 211, 238, 1)",
53
+ "rgba(6, 182, 212, 1)",
54
+ "rgba(14, 165, 233, 1)",
55
+ "rgba(14, 116, 144, 1)",
56
+ ],
57
+ [
58
+ // Blues / indigos
59
+ "rgba(129, 140, 248, 1)",
60
+ "rgba(94, 112, 255, 1)",
61
+ "rgba(96, 165, 250, 1)",
62
+ "rgba(59, 130, 246, 1)",
63
+ "rgba(37, 99, 235, 1)",
64
+ "rgba(29, 78, 216, 1)",
65
+ "rgba(99, 102, 241, 1)",
66
+ "rgba(49, 46, 129, 1)",
67
+ ],
68
+ [
69
+ // Purples / violets
70
+ "rgba(196, 181, 253, 1)",
71
+ "rgba(139, 92, 246, 1)",
72
+ "rgba(124, 58, 237, 1)",
73
+ "rgba(109, 40, 217, 1)",
74
+ "rgba(168, 85, 247, 1)",
75
+ "rgba(147, 51, 234, 1)",
76
+ "rgba(126, 34, 206, 1)",
77
+ "rgba(107, 33, 168, 1)",
78
+ ],
79
+ [
80
+ // Neutrals
81
+ "rgba(255, 255, 255, 1)",
82
+ "rgba(244, 244, 245, 1)",
83
+ "rgba(212, 212, 216, 1)",
84
+ "rgba(161, 161, 170, 1)",
85
+ "rgba(113, 113, 122, 1)",
86
+ "rgba(63, 63, 70, 1)",
87
+ "rgba(46, 46, 46, 1)",
88
+ "rgba(24, 24, 27, 1)",
89
+ ],
90
+ ] as const;
91
+
92
+ /** Flat version kept for compatibility with existing imports */
93
+ export const EXTENDED_COLORS: string[] = EXTENDED_COLOR_GROUPS.flat();
94
+
95
+ export const DEFAULT_STROKE_COLOR = PRESET_COLORS[0]!;
96
+ export const DEFAULT_FILL_ALPHA = 0.5;
97
+ export const DEFAULT_LINE_WIDTH = 4;
98
+ export const DEFAULT_MEASURE_UNIT = "m";
@@ -0,0 +1,75 @@
1
+ import type { Point, Size } from "../types";
2
+
3
+ /**
4
+ * Convert pixel coordinates to normalized [0,1] range.
5
+ */
6
+ export function normalize(pixel: Point, canvasSize: Size): Point {
7
+ if (canvasSize.width === 0 || canvasSize.height === 0) {
8
+ return { x: 0, y: 0 };
9
+ }
10
+ return {
11
+ x: pixel.x / canvasSize.width,
12
+ y: pixel.y / canvasSize.height,
13
+ };
14
+ }
15
+
16
+ /**
17
+ * Convert normalized [0,1] coordinates to pixel coordinates.
18
+ */
19
+ export function denormalize(normalized: Point, canvasSize: Size): Point {
20
+ return {
21
+ x: normalized.x * canvasSize.width,
22
+ y: normalized.y * canvasSize.height,
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Normalize an array of pixel points.
28
+ */
29
+ export function normalizePoints(pixels: Point[], canvasSize: Size): Point[] {
30
+ return pixels.map((p) => normalize(p, canvasSize));
31
+ }
32
+
33
+ /**
34
+ * Denormalize an array of normalized points.
35
+ */
36
+ export function denormalizePoints(
37
+ normalized: Point[],
38
+ canvasSize: Size,
39
+ ): Point[] {
40
+ return normalized.map((p) => denormalize(p, canvasSize));
41
+ }
42
+
43
+ /**
44
+ * Distance between two points (in whatever coordinate space they share).
45
+ */
46
+ export function distance(a: Point, b: Point): number {
47
+ const dx = a.x - b.x;
48
+ const dy = a.y - b.y;
49
+ return Math.sqrt(dx * dx + dy * dy);
50
+ }
51
+
52
+ /**
53
+ * Angle in radians from point a to point b.
54
+ */
55
+ export function angle(a: Point, b: Point): number {
56
+ return Math.atan2(b.y - a.y, b.x - a.x);
57
+ }
58
+
59
+ /**
60
+ * Convert screen coordinates to canvas coordinates by inverting the viewport transform.
61
+ * Must be called from a worklet context.
62
+ */
63
+ export function screenToCanvas(
64
+ screenX: number,
65
+ screenY: number,
66
+ viewScale: number,
67
+ viewTranslateX: number,
68
+ viewTranslateY: number,
69
+ ): { x: number; y: number } {
70
+ "worklet";
71
+ return {
72
+ x: (screenX - viewTranslateX) / viewScale,
73
+ y: (screenY - viewTranslateY) / viewScale,
74
+ };
75
+ }
@@ -0,0 +1,242 @@
1
+ import type {
2
+ DrawingObject,
3
+ FreehandObject,
4
+ PolygonObject,
5
+ TwoPointObject,
6
+ TextObject,
7
+ Point,
8
+ Size,
9
+ } from "../types";
10
+ import { denormalize, denormalizePoints } from "./coordinates";
11
+ import { buildSmoothedPath } from "./smoothing";
12
+
13
+ const HIT_PADDING = 20; // pixels tolerance for hit-testing lines/paths
14
+
15
+ /**
16
+ * Test if a pixel-space tap point hits a drawing object.
17
+ * All object coordinates are normalized and must be denormalized with canvasSize.
18
+ *
19
+ * Returns true if the point is within hit range of the object.
20
+ */
21
+ export function hitTestObject(
22
+ object: DrawingObject,
23
+ tapPoint: Point,
24
+ canvasSize: Size,
25
+ ): boolean {
26
+ switch (object.type) {
27
+ case "freehand":
28
+ return hitTestFreehand(object, tapPoint, canvasSize);
29
+ case "line":
30
+ case "arrow":
31
+ case "measure":
32
+ return hitTestLine(object, tapPoint, canvasSize);
33
+ case "rectangle":
34
+ return hitTestRect(object, tapPoint, canvasSize);
35
+ case "circle":
36
+ return hitTestEllipse(object, tapPoint, canvasSize);
37
+ case "polygon":
38
+ return hitTestPolygon(object, tapPoint, canvasSize);
39
+ case "text":
40
+ return hitTestText(object, tapPoint, canvasSize);
41
+ }
42
+ }
43
+
44
+ function hitTestFreehand(
45
+ object: FreehandObject,
46
+ tap: Point,
47
+ canvasSize: Size,
48
+ ): boolean {
49
+ const points = denormalizePoints(object.points, canvasSize);
50
+ const padding = HIT_PADDING + object.lineWidth / 2;
51
+
52
+ // Check distance to each segment of the smoothed path
53
+ for (let i = 0; i < points.length - 1; i++) {
54
+ const a = points[i]!;
55
+ const b = points[i + 1]!;
56
+ if (distanceToSegment(tap, a, b) < padding) {
57
+ return true;
58
+ }
59
+ }
60
+ return false;
61
+ }
62
+
63
+ function hitTestLine(
64
+ object: TwoPointObject,
65
+ tap: Point,
66
+ canvasSize: Size,
67
+ ): boolean {
68
+ const from = denormalize(object.from, canvasSize);
69
+ const to = denormalize(object.to, canvasSize);
70
+ const padding = HIT_PADDING + object.lineWidth / 2;
71
+ return distanceToSegment(tap, from, to) < padding;
72
+ }
73
+
74
+ function hitTestRect(
75
+ object: DrawingObject & { from: Point; to: Point },
76
+ tap: Point,
77
+ canvasSize: Size,
78
+ ): boolean {
79
+ const from = denormalize(object.from, canvasSize);
80
+ const to = denormalize(object.to, canvasSize);
81
+ const padding = HIT_PADDING;
82
+
83
+ const minX = Math.min(from.x, to.x) - padding;
84
+ const maxX = Math.max(from.x, to.x) + padding;
85
+ const minY = Math.min(from.y, to.y) - padding;
86
+ const maxY = Math.max(from.y, to.y) + padding;
87
+
88
+ // If has fill, hit inside the rect
89
+ if ("backgroundColor" in object && object.backgroundColor) {
90
+ return tap.x >= minX && tap.x <= maxX && tap.y >= minY && tap.y <= maxY;
91
+ }
92
+
93
+ // Otherwise, hit the border only
94
+ const innerMinX = Math.min(from.x, to.x) + padding;
95
+ const innerMaxX = Math.max(from.x, to.x) - padding;
96
+ const innerMinY = Math.min(from.y, to.y) + padding;
97
+ const innerMaxY = Math.max(from.y, to.y) - padding;
98
+
99
+ const inOuter =
100
+ tap.x >= minX && tap.x <= maxX && tap.y >= minY && tap.y <= maxY;
101
+ const inInner =
102
+ tap.x >= innerMinX &&
103
+ tap.x <= innerMaxX &&
104
+ tap.y >= innerMinY &&
105
+ tap.y <= innerMaxY;
106
+
107
+ return inOuter && !inInner;
108
+ }
109
+
110
+ function hitTestEllipse(
111
+ object: DrawingObject & { from: Point; to: Point },
112
+ tap: Point,
113
+ canvasSize: Size,
114
+ ): boolean {
115
+ const from = denormalize(object.from, canvasSize);
116
+ const to = denormalize(object.to, canvasSize);
117
+ const cx = (from.x + to.x) / 2;
118
+ const cy = (from.y + to.y) / 2;
119
+ const rx = Math.abs(to.x - from.x) / 2;
120
+ const ry = Math.abs(to.y - from.y) / 2;
121
+
122
+ if (rx === 0 || ry === 0) return false;
123
+
124
+ const dx = tap.x - cx;
125
+ const dy = tap.y - cy;
126
+
127
+ // Normalized distance from center
128
+ // If has fill, hit inside the ellipse
129
+ if ("backgroundColor" in object && object.backgroundColor) {
130
+ const outerRx = rx + HIT_PADDING;
131
+ const outerRy = ry + HIT_PADDING;
132
+ return (
133
+ (dx * dx) / (outerRx * outerRx) + (dy * dy) / (outerRy * outerRy) <= 1
134
+ );
135
+ }
136
+
137
+ // Otherwise, hit the border ring
138
+ const padding =
139
+ HIT_PADDING +
140
+ ("lineWidth" in object
141
+ ? (object as { lineWidth: number }).lineWidth / 2
142
+ : 2);
143
+ const outerRx = rx + padding;
144
+ const outerRy = ry + padding;
145
+ const innerRx = Math.max(0, rx - padding);
146
+ const innerRy = Math.max(0, ry - padding);
147
+
148
+ const outerDist =
149
+ (dx * dx) / (outerRx * outerRx) + (dy * dy) / (outerRy * outerRy);
150
+ const innerDist =
151
+ innerRx > 0 && innerRy > 0
152
+ ? (dx * dx) / (innerRx * innerRx) + (dy * dy) / (innerRy * innerRy)
153
+ : Infinity;
154
+
155
+ return outerDist <= 1 && innerDist >= 1;
156
+ }
157
+
158
+ function hitTestText(
159
+ object: TextObject,
160
+ tap: Point,
161
+ canvasSize: Size,
162
+ ): boolean {
163
+ const pos = denormalize(object.position, canvasSize);
164
+ const w = object.width * canvasSize.width;
165
+ const h = object.height * canvasSize.height;
166
+
167
+ return (
168
+ tap.x >= pos.x && tap.x <= pos.x + w && tap.y >= pos.y && tap.y <= pos.y + h
169
+ );
170
+ }
171
+
172
+ function hitTestPolygon(
173
+ object: PolygonObject,
174
+ tap: Point,
175
+ canvasSize: Size,
176
+ ): boolean {
177
+ const points = denormalizePoints(object.points, canvasSize);
178
+ if (points.length < 2) return false;
179
+ const padding = HIT_PADDING + object.lineWidth / 2;
180
+
181
+ // If filled and closed, check if point is inside the polygon
182
+ if (object.closed && object.backgroundColor) {
183
+ if (pointInPolygon(tap, points)) return true;
184
+ }
185
+
186
+ // Check distance to each segment (including closing segment if closed)
187
+ const len = points.length;
188
+ for (let i = 0; i < len - 1; i++) {
189
+ if (distanceToSegment(tap, points[i]!, points[i + 1]!) < padding) {
190
+ return true;
191
+ }
192
+ }
193
+ // Closing segment
194
+ if (object.closed && len >= 3) {
195
+ if (distanceToSegment(tap, points[len - 1]!, points[0]!) < padding) {
196
+ return true;
197
+ }
198
+ }
199
+ return false;
200
+ }
201
+
202
+ /** Ray-casting point-in-polygon test */
203
+ function pointInPolygon(p: Point, polygon: Point[]): boolean {
204
+ let inside = false;
205
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
206
+ const xi = polygon[i]!.x;
207
+ const yi = polygon[i]!.y;
208
+ const xj = polygon[j]!.x;
209
+ const yj = polygon[j]!.y;
210
+ const intersect =
211
+ yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / (yj - yi) + xi;
212
+ if (intersect) inside = !inside;
213
+ }
214
+ return inside;
215
+ }
216
+
217
+ /**
218
+ * Returns the minimum distance from point p to segment [a, b].
219
+ */
220
+ function distanceToSegment(p: Point, a: Point, b: Point): number {
221
+ const dx = b.x - a.x;
222
+ const dy = b.y - a.y;
223
+ const lenSq = dx * dx + dy * dy;
224
+
225
+ if (lenSq === 0) {
226
+ // a and b are the same point
227
+ return Math.sqrt((p.x - a.x) ** 2 + (p.y - a.y) ** 2);
228
+ }
229
+
230
+ let t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / lenSq;
231
+ t = Math.max(0, Math.min(1, t));
232
+
233
+ const projX = a.x + t * dx;
234
+ const projY = a.y + t * dy;
235
+
236
+ return Math.sqrt((p.x - projX) ** 2 + (p.y - projY) ** 2);
237
+ }
238
+
239
+ /**
240
+ * Export for use in selection gesture calculations.
241
+ */
242
+ export { buildSmoothedPath, distanceToSegment };
@@ -0,0 +1,45 @@
1
+ import type { DrawingObject, Size } from "../types";
2
+
3
+ /**
4
+ * Serialize drawing state to a JSON-compatible structure.
5
+ */
6
+ export function serializeObjects(
7
+ objects: DrawingObject[],
8
+ canvasSize: Size,
9
+ ): string {
10
+ return JSON.stringify({
11
+ version: 1,
12
+ canvasSize,
13
+ objects,
14
+ });
15
+ }
16
+
17
+ /**
18
+ * Deserialize drawing state from JSON.
19
+ */
20
+ export function deserializeObjects(json: string): {
21
+ objects: DrawingObject[];
22
+ canvasSize: Size;
23
+ } | null {
24
+ try {
25
+ const data = JSON.parse(json) as {
26
+ version?: number;
27
+ canvasSize?: Size;
28
+ objects?: DrawingObject[];
29
+ };
30
+ if (!data.objects || !data.canvasSize) return null;
31
+ return {
32
+ objects: data.objects,
33
+ canvasSize: data.canvasSize,
34
+ };
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Generate a unique ID for drawing objects.
42
+ */
43
+ export function generateId(): string {
44
+ return Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
45
+ }
@@ -0,0 +1,160 @@
1
+ import type { Point } from "../types";
2
+
3
+ /**
4
+ * Count the number of direction changes (inflections) in a series of points.
5
+ * An inflection is when the general direction of movement reverses or sharply changes.
6
+ */
7
+ function countInflections(points: Point[], startIndex = 0): number {
8
+ if (points.length < 3) return 0;
9
+
10
+ let inflections = 0;
11
+ for (let i = Math.max(startIndex, 1); i < points.length - 1; i++) {
12
+ const prev = points[i - 1]!;
13
+ const curr = points[i]!;
14
+ const next = points[i + 1]!;
15
+
16
+ const dx1 = curr.x - prev.x;
17
+ const dy1 = curr.y - prev.y;
18
+ const dx2 = next.x - curr.x;
19
+ const dy2 = next.y - curr.y;
20
+
21
+ // Cross product sign change indicates direction change
22
+ const cross = dx1 * dy2 - dy1 * dx2;
23
+ if (i > Math.max(startIndex, 1)) {
24
+ const prevPrev = points[i - 2]!;
25
+ const pdx1 = prev.x - prevPrev.x;
26
+ const pdy1 = prev.y - prevPrev.y;
27
+ const prevCross = pdx1 * dy1 - pdy1 * dx1;
28
+ if (prevCross * cross < 0) {
29
+ inflections++;
30
+ }
31
+ }
32
+ }
33
+ return inflections;
34
+ }
35
+
36
+ /**
37
+ * Compute total angle traversed by the path.
38
+ * Used for circle detection (should be ~2π for a full circle).
39
+ */
40
+ function totalAngleTraversed(points: Point[]): number {
41
+ if (points.length < 3) return 0;
42
+
43
+ let totalAngle = 0;
44
+ for (let i = 1; i < points.length - 1; i++) {
45
+ const prev = points[i - 1]!;
46
+ const curr = points[i]!;
47
+ const next = points[i + 1]!;
48
+
49
+ const a1 = Math.atan2(curr.y - prev.y, curr.x - prev.x);
50
+ const a2 = Math.atan2(next.y - curr.y, next.x - curr.x);
51
+ let delta = a2 - a1;
52
+
53
+ // Normalize to [-π, π]
54
+ while (delta > Math.PI) delta -= 2 * Math.PI;
55
+ while (delta < -Math.PI) delta += 2 * Math.PI;
56
+
57
+ totalAngle += delta;
58
+ }
59
+ return totalAngle;
60
+ }
61
+
62
+ function dist(a: Point, b: Point): number {
63
+ const dx = a.x - b.x;
64
+ const dy = a.y - b.y;
65
+ return Math.sqrt(dx * dx + dy * dy);
66
+ }
67
+
68
+ function boundingRect(points: Point[]): {
69
+ x: number;
70
+ y: number;
71
+ width: number;
72
+ height: number;
73
+ } {
74
+ let minX = Infinity;
75
+ let minY = Infinity;
76
+ let maxX = -Infinity;
77
+ let maxY = -Infinity;
78
+
79
+ for (const p of points) {
80
+ if (p.x < minX) minX = p.x;
81
+ if (p.y < minY) minY = p.y;
82
+ if (p.x > maxX) maxX = p.x;
83
+ if (p.y > maxY) maxY = p.y;
84
+ }
85
+
86
+ return {
87
+ x: minX,
88
+ y: minY,
89
+ width: maxX - minX,
90
+ height: maxY - minY,
91
+ };
92
+ }
93
+
94
+ export type DetectedShape =
95
+ | { type: "circle"; from: Point; to: Point }
96
+ | { type: "rectangle"; from: Point; to: Point }
97
+ | { type: "line"; from: Point; to: Point }
98
+ | null;
99
+
100
+ /**
101
+ * Attempt to detect a geometric shape from freehand points.
102
+ * Returns the detected shape or null if no shape is recognized.
103
+ *
104
+ * Points should be in pixel coordinates. Duration in milliseconds.
105
+ */
106
+ export function detectShape(
107
+ points: Point[],
108
+ durationMs: number,
109
+ ): DetectedShape {
110
+ if (points.length < 5) return null;
111
+
112
+ const first = points[0]!;
113
+ const last = points[points.length - 1]!;
114
+ const startEndDist = dist(first, last);
115
+ const inflections = countInflections(points);
116
+
117
+ // ── Circle detection ──
118
+ // Duration < 1.5s, inflections ≤ 5, distance start↔end < threshold,
119
+ // total angle ≈ 2π (±45°)
120
+ if (durationMs < 1500 && inflections <= 5 && startEndDist < 150) {
121
+ const totalAngle = Math.abs(totalAngleTraversed(points));
122
+ const twoPi = 2 * Math.PI;
123
+ const tolerance = Math.PI / 4; // 45°
124
+
125
+ if (Math.abs(totalAngle - twoPi) < tolerance) {
126
+ const bbox = boundingRect(points);
127
+ return {
128
+ type: "circle",
129
+ from: { x: bbox.x, y: bbox.y },
130
+ to: { x: bbox.x + bbox.width, y: bbox.y + bbox.height },
131
+ };
132
+ }
133
+ }
134
+
135
+ // ── Rectangle detection ──
136
+ // Duration < 12s, inflections ≤ 15, distance start↔end < threshold
137
+ if (durationMs < 12000 && inflections <= 15 && startEndDist < 30) {
138
+ const bbox = boundingRect(points);
139
+ return {
140
+ type: "rectangle",
141
+ from: { x: bbox.x, y: bbox.y },
142
+ to: { x: bbox.x + bbox.width, y: bbox.y + bbox.height },
143
+ };
144
+ }
145
+
146
+ // ── Line detection ──
147
+ // Duration < 4s, inflections ≤ 4 (checked from 11th point onward)
148
+ if (durationMs < 4000 && points.length >= 11) {
149
+ const lineInflections = countInflections(points, 10);
150
+ if (lineInflections <= 4) {
151
+ return {
152
+ type: "line",
153
+ from: first,
154
+ to: last,
155
+ };
156
+ }
157
+ }
158
+
159
+ return null;
160
+ }
@@ -0,0 +1,84 @@
1
+ import type { Point } from "../types";
2
+
3
+ /**
4
+ * Build a smoothed SVG path string from an array of pixel-space points
5
+ * using quadratic Bezier curves through midpoints.
6
+ *
7
+ * This is the standard "midpoint smoothing" algorithm for touch-based drawing:
8
+ * each raw point becomes a control point, and the curve passes through the
9
+ * midpoints between consecutive raw points.
10
+ */
11
+ export function buildSmoothedPath(points: Point[]): string {
12
+ const n = points.length;
13
+ if (n === 0) return "";
14
+ if (n === 1) return `M ${points[0]!.x} ${points[0]!.y}`;
15
+
16
+ const first = points[0]!;
17
+
18
+ if (n === 2) {
19
+ const second = points[1]!;
20
+ return `M ${first.x} ${first.y} L ${second.x} ${second.y}`;
21
+ }
22
+
23
+ // Start at first point
24
+ const parts: string[] = [`M ${first.x} ${first.y}`];
25
+
26
+ // Line to first midpoint
27
+ const p1 = points[1]!;
28
+ const m0x = (first.x + p1.x) / 2;
29
+ const m0y = (first.y + p1.y) / 2;
30
+ parts.push(`L ${m0x} ${m0y}`);
31
+
32
+ // Quadratic curves through midpoints
33
+ for (let i = 1; i < n - 1; i++) {
34
+ const cp = points[i]!;
35
+ const next = points[i + 1]!;
36
+ const mx = (cp.x + next.x) / 2;
37
+ const my = (cp.y + next.y) / 2;
38
+ parts.push(`Q ${cp.x} ${cp.y} ${mx} ${my}`);
39
+ }
40
+
41
+ // Line to last point
42
+ const last = points[n - 1]!;
43
+ parts.push(`L ${last.x} ${last.y}`);
44
+
45
+ return parts.join(" ");
46
+ }
47
+
48
+ /**
49
+ * Build a smoothed SVG path from a flat array [x0, y0, x1, y1, ...].
50
+ * Used in worklets where Point objects aren't available.
51
+ */
52
+ export function buildSmoothedPathFromFlat(coords: number[]): string {
53
+ "worklet";
54
+ const n = coords.length / 2;
55
+ if (n < 1) return "";
56
+ if (n === 1) return `M ${coords[0]} ${coords[1]}`;
57
+
58
+ if (n === 2) {
59
+ return `M ${coords[0]} ${coords[1]} L ${coords[2]} ${coords[3]}`;
60
+ }
61
+
62
+ let svg = `M ${coords[0]} ${coords[1]}`;
63
+
64
+ // Line to first midpoint
65
+ const m0x = (coords[0]! + coords[2]!) / 2;
66
+ const m0y = (coords[1]! + coords[3]!) / 2;
67
+ svg += ` L ${m0x} ${m0y}`;
68
+
69
+ // Quadratic curves through midpoints
70
+ for (let i = 1; i < n - 1; i++) {
71
+ const cx = coords[i * 2]!;
72
+ const cy = coords[i * 2 + 1]!;
73
+ const nx = coords[(i + 1) * 2]!;
74
+ const ny = coords[(i + 1) * 2 + 1]!;
75
+ const mx = (cx + nx) / 2;
76
+ const my = (cy + ny) / 2;
77
+ svg += ` Q ${cx} ${cy} ${mx} ${my}`;
78
+ }
79
+
80
+ // Line to last point
81
+ svg += ` L ${coords[(n - 1) * 2]} ${coords[(n - 1) * 2 + 1]}`;
82
+
83
+ return svg;
84
+ }