@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.
- package/README.md +181 -0
- package/lib/commonjs/DrawingEditor.js +815 -0
- package/lib/commonjs/DrawingEditor.js.map +1 -0
- package/lib/commonjs/assets/toolbar-icons/arrow-disabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/arrow-enabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/arrow.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/circle-disabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/circle-enabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/circle.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/freehand-disabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/freehand-enabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/freehand.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/line-disabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/line-enabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/line.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/measure-disabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/measure-enabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/measure.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/move-disabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/move-enabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/move.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/polygon-disabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/polygon-enabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/polygon.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/rectangle-disabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/rectangle-enabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/rectangle.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/text-disabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/text-enabled.png +0 -0
- package/lib/commonjs/assets/toolbar-icons/text.png +0 -0
- package/lib/commonjs/components/ColorPalette.js +379 -0
- package/lib/commonjs/components/ColorPalette.js.map +1 -0
- package/lib/commonjs/components/LineWidthSlider.js +70 -0
- package/lib/commonjs/components/LineWidthSlider.js.map +1 -0
- package/lib/commonjs/components/MeasurementEditModal.js +153 -0
- package/lib/commonjs/components/MeasurementEditModal.js.map +1 -0
- package/lib/commonjs/components/MiniMap.js +244 -0
- package/lib/commonjs/components/MiniMap.js.map +1 -0
- package/lib/commonjs/components/TextAnnotation.js +162 -0
- package/lib/commonjs/components/TextAnnotation.js.map +1 -0
- package/lib/commonjs/components/TextEditModal.js +133 -0
- package/lib/commonjs/components/TextEditModal.js.map +1 -0
- package/lib/commonjs/components/Toolbar.js +198 -0
- package/lib/commonjs/components/Toolbar.js.map +1 -0
- package/lib/commonjs/components/ZoomBadge.js +161 -0
- package/lib/commonjs/components/ZoomBadge.js.map +1 -0
- package/lib/commonjs/hooks/useFreehandGesture.js +173 -0
- package/lib/commonjs/hooks/useFreehandGesture.js.map +1 -0
- package/lib/commonjs/hooks/usePolygonGesture.js +109 -0
- package/lib/commonjs/hooks/usePolygonGesture.js.map +1 -0
- package/lib/commonjs/hooks/useSelectionGesture.js +236 -0
- package/lib/commonjs/hooks/useSelectionGesture.js.map +1 -0
- package/lib/commonjs/hooks/useShapeGesture.js +181 -0
- package/lib/commonjs/hooks/useShapeGesture.js.map +1 -0
- package/lib/commonjs/hooks/useViewportGesture.js +238 -0
- package/lib/commonjs/hooks/useViewportGesture.js.map +1 -0
- package/lib/commonjs/index.js +104 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/renderers/ArrowRenderer.js +118 -0
- package/lib/commonjs/renderers/ArrowRenderer.js.map +1 -0
- package/lib/commonjs/renderers/CircleRenderer.js +51 -0
- package/lib/commonjs/renderers/CircleRenderer.js.map +1 -0
- package/lib/commonjs/renderers/FreehandRenderer.js +31 -0
- package/lib/commonjs/renderers/FreehandRenderer.js.map +1 -0
- package/lib/commonjs/renderers/InProgressRenderer.js +174 -0
- package/lib/commonjs/renderers/InProgressRenderer.js.map +1 -0
- package/lib/commonjs/renderers/LineRenderer.js +27 -0
- package/lib/commonjs/renderers/LineRenderer.js.map +1 -0
- package/lib/commonjs/renderers/MeasurementRenderer.js +134 -0
- package/lib/commonjs/renderers/MeasurementRenderer.js.map +1 -0
- package/lib/commonjs/renderers/ObjectRenderer.js +65 -0
- package/lib/commonjs/renderers/ObjectRenderer.js.map +1 -0
- package/lib/commonjs/renderers/PolygonRenderer.js +46 -0
- package/lib/commonjs/renderers/PolygonRenderer.js.map +1 -0
- package/lib/commonjs/renderers/RectRenderer.js +51 -0
- package/lib/commonjs/renderers/RectRenderer.js.map +1 -0
- package/lib/commonjs/renderers/SelectedObjectRenderer.js +592 -0
- package/lib/commonjs/renderers/SelectedObjectRenderer.js.map +1 -0
- package/lib/commonjs/renderers/SelectionOverlay.js +120 -0
- package/lib/commonjs/renderers/SelectionOverlay.js.map +1 -0
- package/lib/commonjs/store/useDrawingStore.js +354 -0
- package/lib/commonjs/store/useDrawingStore.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/utils/colors.js +44 -0
- package/lib/commonjs/utils/colors.js.map +1 -0
- package/lib/commonjs/utils/coordinates.js +81 -0
- package/lib/commonjs/utils/coordinates.js.map +1 -0
- package/lib/commonjs/utils/hitTesting.js +181 -0
- package/lib/commonjs/utils/hitTesting.js.map +1 -0
- package/lib/commonjs/utils/serialization.js +42 -0
- package/lib/commonjs/utils/serialization.js.map +1 -0
- package/lib/commonjs/utils/shapeDetection.js +151 -0
- package/lib/commonjs/utils/shapeDetection.js.map +1 -0
- package/lib/commonjs/utils/smoothing.js +85 -0
- package/lib/commonjs/utils/smoothing.js.map +1 -0
- package/lib/module/DrawingEditor.js +811 -0
- package/lib/module/DrawingEditor.js.map +1 -0
- package/lib/module/assets/toolbar-icons/arrow-disabled.png +0 -0
- package/lib/module/assets/toolbar-icons/arrow-enabled.png +0 -0
- package/lib/module/assets/toolbar-icons/arrow.png +0 -0
- package/lib/module/assets/toolbar-icons/circle-disabled.png +0 -0
- package/lib/module/assets/toolbar-icons/circle-enabled.png +0 -0
- package/lib/module/assets/toolbar-icons/circle.png +0 -0
- package/lib/module/assets/toolbar-icons/freehand-disabled.png +0 -0
- package/lib/module/assets/toolbar-icons/freehand-enabled.png +0 -0
- package/lib/module/assets/toolbar-icons/freehand.png +0 -0
- package/lib/module/assets/toolbar-icons/line-disabled.png +0 -0
- package/lib/module/assets/toolbar-icons/line-enabled.png +0 -0
- package/lib/module/assets/toolbar-icons/line.png +0 -0
- package/lib/module/assets/toolbar-icons/measure-disabled.png +0 -0
- package/lib/module/assets/toolbar-icons/measure-enabled.png +0 -0
- package/lib/module/assets/toolbar-icons/measure.png +0 -0
- package/lib/module/assets/toolbar-icons/move-disabled.png +0 -0
- package/lib/module/assets/toolbar-icons/move-enabled.png +0 -0
- package/lib/module/assets/toolbar-icons/move.png +0 -0
- package/lib/module/assets/toolbar-icons/polygon-disabled.png +0 -0
- package/lib/module/assets/toolbar-icons/polygon-enabled.png +0 -0
- package/lib/module/assets/toolbar-icons/polygon.png +0 -0
- package/lib/module/assets/toolbar-icons/rectangle-disabled.png +0 -0
- package/lib/module/assets/toolbar-icons/rectangle-enabled.png +0 -0
- package/lib/module/assets/toolbar-icons/rectangle.png +0 -0
- package/lib/module/assets/toolbar-icons/text-disabled.png +0 -0
- package/lib/module/assets/toolbar-icons/text-enabled.png +0 -0
- package/lib/module/assets/toolbar-icons/text.png +0 -0
- package/lib/module/components/ColorPalette.js +374 -0
- package/lib/module/components/ColorPalette.js.map +1 -0
- package/lib/module/components/LineWidthSlider.js +64 -0
- package/lib/module/components/LineWidthSlider.js.map +1 -0
- package/lib/module/components/MeasurementEditModal.js +148 -0
- package/lib/module/components/MeasurementEditModal.js.map +1 -0
- package/lib/module/components/MiniMap.js +239 -0
- package/lib/module/components/MiniMap.js.map +1 -0
- package/lib/module/components/TextAnnotation.js +157 -0
- package/lib/module/components/TextAnnotation.js.map +1 -0
- package/lib/module/components/TextEditModal.js +128 -0
- package/lib/module/components/TextEditModal.js.map +1 -0
- package/lib/module/components/Toolbar.js +193 -0
- package/lib/module/components/Toolbar.js.map +1 -0
- package/lib/module/components/ZoomBadge.js +155 -0
- package/lib/module/components/ZoomBadge.js.map +1 -0
- package/lib/module/hooks/useFreehandGesture.js +169 -0
- package/lib/module/hooks/useFreehandGesture.js.map +1 -0
- package/lib/module/hooks/usePolygonGesture.js +106 -0
- package/lib/module/hooks/usePolygonGesture.js.map +1 -0
- package/lib/module/hooks/useSelectionGesture.js +232 -0
- package/lib/module/hooks/useSelectionGesture.js.map +1 -0
- package/lib/module/hooks/useShapeGesture.js +177 -0
- package/lib/module/hooks/useShapeGesture.js.map +1 -0
- package/lib/module/hooks/useViewportGesture.js +234 -0
- package/lib/module/hooks/useViewportGesture.js.map +1 -0
- package/lib/module/index.js +20 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/renderers/ArrowRenderer.js +113 -0
- package/lib/module/renderers/ArrowRenderer.js.map +1 -0
- package/lib/module/renderers/CircleRenderer.js +46 -0
- package/lib/module/renderers/CircleRenderer.js.map +1 -0
- package/lib/module/renderers/FreehandRenderer.js +26 -0
- package/lib/module/renderers/FreehandRenderer.js.map +1 -0
- package/lib/module/renderers/InProgressRenderer.js +169 -0
- package/lib/module/renderers/InProgressRenderer.js.map +1 -0
- package/lib/module/renderers/LineRenderer.js +22 -0
- package/lib/module/renderers/LineRenderer.js.map +1 -0
- package/lib/module/renderers/MeasurementRenderer.js +129 -0
- package/lib/module/renderers/MeasurementRenderer.js.map +1 -0
- package/lib/module/renderers/ObjectRenderer.js +60 -0
- package/lib/module/renderers/ObjectRenderer.js.map +1 -0
- package/lib/module/renderers/PolygonRenderer.js +41 -0
- package/lib/module/renderers/PolygonRenderer.js.map +1 -0
- package/lib/module/renderers/RectRenderer.js +46 -0
- package/lib/module/renderers/RectRenderer.js.map +1 -0
- package/lib/module/renderers/SelectedObjectRenderer.js +587 -0
- package/lib/module/renderers/SelectedObjectRenderer.js.map +1 -0
- package/lib/module/renderers/SelectionOverlay.js +116 -0
- package/lib/module/renderers/SelectionOverlay.js.map +1 -0
- package/lib/module/store/useDrawingStore.js +350 -0
- package/lib/module/store/useDrawingStore.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/colors.js +40 -0
- package/lib/module/utils/colors.js.map +1 -0
- package/lib/module/utils/coordinates.js +71 -0
- package/lib/module/utils/coordinates.js.map +1 -0
- package/lib/module/utils/hitTesting.js +171 -0
- package/lib/module/utils/hitTesting.js.map +1 -0
- package/lib/module/utils/serialization.js +36 -0
- package/lib/module/utils/serialization.js.map +1 -0
- package/lib/module/utils/shapeDetection.js +147 -0
- package/lib/module/utils/shapeDetection.js.map +1 -0
- package/lib/module/utils/smoothing.js +80 -0
- package/lib/module/utils/smoothing.js.map +1 -0
- package/lib/typescript/DrawingEditor.d.ts +3 -0
- package/lib/typescript/DrawingEditor.d.ts.map +1 -0
- package/lib/typescript/components/ColorPalette.d.ts +9 -0
- package/lib/typescript/components/ColorPalette.d.ts.map +1 -0
- package/lib/typescript/components/LineWidthSlider.d.ts +11 -0
- package/lib/typescript/components/LineWidthSlider.d.ts.map +1 -0
- package/lib/typescript/components/MeasurementEditModal.d.ts +11 -0
- package/lib/typescript/components/MeasurementEditModal.d.ts.map +1 -0
- package/lib/typescript/components/MiniMap.d.ts +23 -0
- package/lib/typescript/components/MiniMap.d.ts.map +1 -0
- package/lib/typescript/components/TextAnnotation.d.ts +22 -0
- package/lib/typescript/components/TextAnnotation.d.ts.map +1 -0
- package/lib/typescript/components/TextEditModal.d.ts +10 -0
- package/lib/typescript/components/TextEditModal.d.ts.map +1 -0
- package/lib/typescript/components/Toolbar.d.ts +13 -0
- package/lib/typescript/components/Toolbar.d.ts.map +1 -0
- package/lib/typescript/components/ZoomBadge.d.ts +9 -0
- package/lib/typescript/components/ZoomBadge.d.ts.map +1 -0
- package/lib/typescript/hooks/useFreehandGesture.d.ts +47 -0
- package/lib/typescript/hooks/useFreehandGesture.d.ts.map +1 -0
- package/lib/typescript/hooks/usePolygonGesture.d.ts +47 -0
- package/lib/typescript/hooks/usePolygonGesture.d.ts.map +1 -0
- package/lib/typescript/hooks/useSelectionGesture.d.ts +32 -0
- package/lib/typescript/hooks/useSelectionGesture.d.ts.map +1 -0
- package/lib/typescript/hooks/useShapeGesture.d.ts +54 -0
- package/lib/typescript/hooks/useShapeGesture.d.ts.map +1 -0
- package/lib/typescript/hooks/useViewportGesture.d.ts +37 -0
- package/lib/typescript/hooks/useViewportGesture.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +11 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/renderers/ArrowRenderer.d.ts +9 -0
- package/lib/typescript/renderers/ArrowRenderer.d.ts.map +1 -0
- package/lib/typescript/renderers/CircleRenderer.d.ts +9 -0
- package/lib/typescript/renderers/CircleRenderer.d.ts.map +1 -0
- package/lib/typescript/renderers/FreehandRenderer.d.ts +9 -0
- package/lib/typescript/renderers/FreehandRenderer.d.ts.map +1 -0
- package/lib/typescript/renderers/InProgressRenderer.d.ts +32 -0
- package/lib/typescript/renderers/InProgressRenderer.d.ts.map +1 -0
- package/lib/typescript/renderers/LineRenderer.d.ts +9 -0
- package/lib/typescript/renderers/LineRenderer.d.ts.map +1 -0
- package/lib/typescript/renderers/MeasurementRenderer.d.ts +9 -0
- package/lib/typescript/renderers/MeasurementRenderer.d.ts.map +1 -0
- package/lib/typescript/renderers/ObjectRenderer.d.ts +12 -0
- package/lib/typescript/renderers/ObjectRenderer.d.ts.map +1 -0
- package/lib/typescript/renderers/PolygonRenderer.d.ts +13 -0
- package/lib/typescript/renderers/PolygonRenderer.d.ts.map +1 -0
- package/lib/typescript/renderers/RectRenderer.d.ts +9 -0
- package/lib/typescript/renderers/RectRenderer.d.ts.map +1 -0
- package/lib/typescript/renderers/SelectedObjectRenderer.d.ts +18 -0
- package/lib/typescript/renderers/SelectedObjectRenderer.d.ts.map +1 -0
- package/lib/typescript/renderers/SelectionOverlay.d.ts +21 -0
- package/lib/typescript/renderers/SelectionOverlay.d.ts.map +1 -0
- package/lib/typescript/store/useDrawingStore.d.ts +30 -0
- package/lib/typescript/store/useDrawingStore.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +130 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/lib/typescript/utils/colors.d.ts +11 -0
- package/lib/typescript/utils/colors.d.ts.map +1 -0
- package/lib/typescript/utils/coordinates.d.ts +34 -0
- package/lib/typescript/utils/coordinates.d.ts.map +1 -0
- package/lib/typescript/utils/hitTesting.d.ts +18 -0
- package/lib/typescript/utils/hitTesting.d.ts.map +1 -0
- package/lib/typescript/utils/serialization.d.ts +17 -0
- package/lib/typescript/utils/serialization.d.ts.map +1 -0
- package/lib/typescript/utils/shapeDetection.d.ts +22 -0
- package/lib/typescript/utils/shapeDetection.d.ts.map +1 -0
- package/lib/typescript/utils/smoothing.d.ts +16 -0
- package/lib/typescript/utils/smoothing.d.ts.map +1 -0
- package/package.json +108 -0
- package/src/DrawingEditor.tsx +1071 -0
- package/src/assets/toolbar-icons/arrow-disabled.png +0 -0
- package/src/assets/toolbar-icons/arrow-enabled.png +0 -0
- package/src/assets/toolbar-icons/arrow.png +0 -0
- package/src/assets/toolbar-icons/circle-disabled.png +0 -0
- package/src/assets/toolbar-icons/circle-enabled.png +0 -0
- package/src/assets/toolbar-icons/circle.png +0 -0
- package/src/assets/toolbar-icons/freehand-disabled.png +0 -0
- package/src/assets/toolbar-icons/freehand-enabled.png +0 -0
- package/src/assets/toolbar-icons/freehand.png +0 -0
- package/src/assets/toolbar-icons/line-disabled.png +0 -0
- package/src/assets/toolbar-icons/line-enabled.png +0 -0
- package/src/assets/toolbar-icons/line.png +0 -0
- package/src/assets/toolbar-icons/measure-disabled.png +0 -0
- package/src/assets/toolbar-icons/measure-enabled.png +0 -0
- package/src/assets/toolbar-icons/measure.png +0 -0
- package/src/assets/toolbar-icons/move-disabled.png +0 -0
- package/src/assets/toolbar-icons/move-enabled.png +0 -0
- package/src/assets/toolbar-icons/move.png +0 -0
- package/src/assets/toolbar-icons/polygon-disabled.png +0 -0
- package/src/assets/toolbar-icons/polygon-enabled.png +0 -0
- package/src/assets/toolbar-icons/polygon.png +0 -0
- package/src/assets/toolbar-icons/rectangle-disabled.png +0 -0
- package/src/assets/toolbar-icons/rectangle-enabled.png +0 -0
- package/src/assets/toolbar-icons/rectangle.png +0 -0
- package/src/assets/toolbar-icons/text-disabled.png +0 -0
- package/src/assets/toolbar-icons/text-enabled.png +0 -0
- package/src/assets/toolbar-icons/text.png +0 -0
- package/src/components/ColorPalette.tsx +497 -0
- package/src/components/LineWidthSlider.tsx +87 -0
- package/src/components/MeasurementEditModal.tsx +163 -0
- package/src/components/MiniMap.tsx +275 -0
- package/src/components/TextAnnotation.tsx +198 -0
- package/src/components/TextEditModal.tsx +139 -0
- package/src/components/Toolbar.tsx +254 -0
- package/src/components/ZoomBadge.tsx +166 -0
- package/src/hooks/useFreehandGesture.ts +249 -0
- package/src/hooks/usePolygonGesture.ts +162 -0
- package/src/hooks/useSelectionGesture.ts +293 -0
- package/src/hooks/useShapeGesture.ts +256 -0
- package/src/hooks/useViewportGesture.ts +337 -0
- package/src/index.tsx +51 -0
- package/src/renderers/ArrowRenderer.tsx +123 -0
- package/src/renderers/CircleRenderer.tsx +60 -0
- package/src/renderers/FreehandRenderer.tsx +33 -0
- package/src/renderers/InProgressRenderer.tsx +217 -0
- package/src/renderers/LineRenderer.tsx +34 -0
- package/src/renderers/MeasurementRenderer.tsx +179 -0
- package/src/renderers/ObjectRenderer.tsx +42 -0
- package/src/renderers/PolygonRenderer.tsx +66 -0
- package/src/renderers/RectRenderer.tsx +60 -0
- package/src/renderers/SelectedObjectRenderer.tsx +738 -0
- package/src/renderers/SelectionOverlay.tsx +170 -0
- package/src/store/useDrawingStore.ts +357 -0
- package/src/types.ts +186 -0
- package/src/utils/colors.ts +98 -0
- package/src/utils/coordinates.ts +75 -0
- package/src/utils/hitTesting.ts +242 -0
- package/src/utils/serialization.ts +45 -0
- package/src/utils/shapeDetection.ts +160 -0
- package/src/utils/smoothing.ts +84 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { Path } from "@shopify/react-native-skia";
|
|
3
|
+
import type { ArrowObject, Size } from "../types";
|
|
4
|
+
import { denormalize } from "../utils/coordinates";
|
|
5
|
+
|
|
6
|
+
interface ArrowRendererProps {
|
|
7
|
+
object: ArrowObject;
|
|
8
|
+
canvasSize: Size;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build an arrow path.
|
|
13
|
+
* - lineWidth < 8: simple line + triangular head
|
|
14
|
+
* - lineWidth >= 8: filled body with wider triangular head
|
|
15
|
+
*/
|
|
16
|
+
function buildArrowPath(
|
|
17
|
+
fx: number,
|
|
18
|
+
fy: number,
|
|
19
|
+
tx: number,
|
|
20
|
+
ty: number,
|
|
21
|
+
lineWidth: number,
|
|
22
|
+
): string {
|
|
23
|
+
const dx = tx - fx;
|
|
24
|
+
const dy = ty - fy;
|
|
25
|
+
const len = Math.sqrt(dx * dx + dy * dy);
|
|
26
|
+
if (len === 0) return "";
|
|
27
|
+
|
|
28
|
+
const angle = Math.atan2(dy, dx);
|
|
29
|
+
const cos = Math.cos(angle);
|
|
30
|
+
const sin = Math.sin(angle);
|
|
31
|
+
|
|
32
|
+
if (lineWidth < 8) {
|
|
33
|
+
// Simple arrow: line + triangular head
|
|
34
|
+
const headLen = lineWidth * 2;
|
|
35
|
+
const headW = lineWidth * 2;
|
|
36
|
+
|
|
37
|
+
// Head points
|
|
38
|
+
const hx1 = tx - headLen * cos + (headW / 2) * sin;
|
|
39
|
+
const hy1 = ty - headLen * sin - (headW / 2) * cos;
|
|
40
|
+
const hx2 = tx - headLen * cos - (headW / 2) * sin;
|
|
41
|
+
const hy2 = ty - headLen * sin + (headW / 2) * cos;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
`M ${fx} ${fy} L ${tx} ${ty} ` +
|
|
45
|
+
`M ${tx} ${ty} L ${hx1} ${hy1} M ${tx} ${ty} L ${hx2} ${hy2}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Filled arrow: rectangular body + triangular head
|
|
50
|
+
const headLen = lineWidth * 3;
|
|
51
|
+
const headW = lineWidth * 3;
|
|
52
|
+
const bodyW = lineWidth;
|
|
53
|
+
const bodyLen = Math.max(0, len - headLen);
|
|
54
|
+
|
|
55
|
+
// Body rectangle (in local space: from origin going right)
|
|
56
|
+
// Then transformed via rotation
|
|
57
|
+
const halfBody = bodyW / 2;
|
|
58
|
+
|
|
59
|
+
// Points in local space (arrow points to the right)
|
|
60
|
+
const b1 = { x: 0, y: -halfBody };
|
|
61
|
+
const b2 = { x: bodyLen, y: -halfBody };
|
|
62
|
+
const b3 = { x: bodyLen, y: halfBody };
|
|
63
|
+
const b4 = { x: 0, y: halfBody };
|
|
64
|
+
|
|
65
|
+
// Head triangle in local space
|
|
66
|
+
const h1 = { x: bodyLen, y: -headW / 2 };
|
|
67
|
+
const h2 = { x: len, y: 0 };
|
|
68
|
+
const h3 = { x: bodyLen, y: headW / 2 };
|
|
69
|
+
|
|
70
|
+
// Transform function: rotate + translate
|
|
71
|
+
const transform = (p: { x: number; y: number }) => ({
|
|
72
|
+
x: fx + p.x * cos - p.y * sin,
|
|
73
|
+
y: fy + p.x * sin + p.y * cos,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const tb1 = transform(b1);
|
|
77
|
+
const tb2 = transform(b2);
|
|
78
|
+
const tb3 = transform(b3);
|
|
79
|
+
const tb4 = transform(b4);
|
|
80
|
+
const th1 = transform(h1);
|
|
81
|
+
const th2 = transform(h2);
|
|
82
|
+
const th3 = transform(h3);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
`M ${tb1.x} ${tb1.y} L ${tb2.x} ${tb2.y} L ${tb3.x} ${tb3.y} L ${tb4.x} ${tb4.y} Z ` +
|
|
86
|
+
`M ${th1.x} ${th1.y} L ${th2.x} ${th2.y} L ${th3.x} ${th3.y} Z`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const ArrowRenderer = React.memo(function ArrowRenderer({
|
|
91
|
+
object,
|
|
92
|
+
canvasSize,
|
|
93
|
+
}: ArrowRendererProps) {
|
|
94
|
+
const from = useMemo(
|
|
95
|
+
() => denormalize(object.from, canvasSize),
|
|
96
|
+
[object.from, canvasSize],
|
|
97
|
+
);
|
|
98
|
+
const to = useMemo(
|
|
99
|
+
() => denormalize(object.to, canvasSize),
|
|
100
|
+
[object.to, canvasSize],
|
|
101
|
+
);
|
|
102
|
+
const pathString = useMemo(
|
|
103
|
+
() => buildArrowPath(from.x, from.y, to.x, to.y, object.lineWidth),
|
|
104
|
+
[from, to, object.lineWidth],
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (!pathString) return null;
|
|
108
|
+
|
|
109
|
+
if (object.lineWidth < 8) {
|
|
110
|
+
return (
|
|
111
|
+
<Path
|
|
112
|
+
path={pathString}
|
|
113
|
+
style="stroke"
|
|
114
|
+
strokeWidth={object.lineWidth}
|
|
115
|
+
color={object.color}
|
|
116
|
+
strokeCap="round"
|
|
117
|
+
strokeJoin="round"
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return <Path path={pathString} style="fill" color={object.color} />;
|
|
123
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { Oval, Group } from "@shopify/react-native-skia";
|
|
3
|
+
import type { CircleObject, Size } from "../types";
|
|
4
|
+
import { denormalize } from "../utils/coordinates";
|
|
5
|
+
|
|
6
|
+
interface CircleRendererProps {
|
|
7
|
+
object: CircleObject;
|
|
8
|
+
canvasSize: Size;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const CircleRenderer = React.memo(function CircleRenderer({
|
|
12
|
+
object,
|
|
13
|
+
canvasSize,
|
|
14
|
+
}: CircleRendererProps) {
|
|
15
|
+
const from = useMemo(
|
|
16
|
+
() => denormalize(object.from, canvasSize),
|
|
17
|
+
[object.from, canvasSize],
|
|
18
|
+
);
|
|
19
|
+
const to = useMemo(
|
|
20
|
+
() => denormalize(object.to, canvasSize),
|
|
21
|
+
[object.to, canvasSize],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const rect = useMemo(() => {
|
|
25
|
+
const x = Math.min(from.x, to.x);
|
|
26
|
+
const y = Math.min(from.y, to.y);
|
|
27
|
+
const width = Math.abs(to.x - from.x);
|
|
28
|
+
const height = Math.abs(to.y - from.y);
|
|
29
|
+
return { x, y, width, height };
|
|
30
|
+
}, [from, to]);
|
|
31
|
+
|
|
32
|
+
const hasFill = object.backgroundColor && object.backgroundAlpha;
|
|
33
|
+
const fillColor = hasFill
|
|
34
|
+
? object.backgroundColor!.replace(/[\d.]+\)$/, `${object.backgroundAlpha})`)
|
|
35
|
+
: undefined;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Group>
|
|
39
|
+
{fillColor && (
|
|
40
|
+
<Oval
|
|
41
|
+
x={rect.x}
|
|
42
|
+
y={rect.y}
|
|
43
|
+
width={rect.width}
|
|
44
|
+
height={rect.height}
|
|
45
|
+
color={fillColor}
|
|
46
|
+
style="fill"
|
|
47
|
+
/>
|
|
48
|
+
)}
|
|
49
|
+
<Oval
|
|
50
|
+
x={rect.x}
|
|
51
|
+
y={rect.y}
|
|
52
|
+
width={rect.width}
|
|
53
|
+
height={rect.height}
|
|
54
|
+
color={object.color}
|
|
55
|
+
style="stroke"
|
|
56
|
+
strokeWidth={object.lineWidth}
|
|
57
|
+
/>
|
|
58
|
+
</Group>
|
|
59
|
+
);
|
|
60
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { Path } from "@shopify/react-native-skia";
|
|
3
|
+
import type { FreehandObject, Size } from "../types";
|
|
4
|
+
import { denormalizePoints } from "../utils/coordinates";
|
|
5
|
+
import { buildSmoothedPath } from "../utils/smoothing";
|
|
6
|
+
|
|
7
|
+
interface FreehandRendererProps {
|
|
8
|
+
object: FreehandObject;
|
|
9
|
+
canvasSize: Size;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const FreehandRenderer = React.memo(function FreehandRenderer({
|
|
13
|
+
object,
|
|
14
|
+
canvasSize,
|
|
15
|
+
}: FreehandRendererProps) {
|
|
16
|
+
const pathString = useMemo(() => {
|
|
17
|
+
const denormalized = denormalizePoints(object.points, canvasSize);
|
|
18
|
+
return buildSmoothedPath(denormalized);
|
|
19
|
+
}, [object.points, canvasSize]);
|
|
20
|
+
|
|
21
|
+
if (!pathString) return null;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Path
|
|
25
|
+
path={pathString}
|
|
26
|
+
style="stroke"
|
|
27
|
+
strokeWidth={object.lineWidth}
|
|
28
|
+
color={object.color}
|
|
29
|
+
strokeCap="round"
|
|
30
|
+
strokeJoin="round"
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
});
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Path,
|
|
4
|
+
Line as SkiaLine,
|
|
5
|
+
Rect,
|
|
6
|
+
Oval,
|
|
7
|
+
Group,
|
|
8
|
+
Skia,
|
|
9
|
+
} from "@shopify/react-native-skia";
|
|
10
|
+
import { useDerivedValue } from "react-native-reanimated";
|
|
11
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
12
|
+
import type { TwoPointShapeType } from "../types";
|
|
13
|
+
import type { SkPath } from "@shopify/react-native-skia";
|
|
14
|
+
|
|
15
|
+
// ─── Freehand In-Progress ────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
interface FreehandInProgressProps {
|
|
18
|
+
path: SharedValue<SkPath | null>;
|
|
19
|
+
color: SharedValue<string>;
|
|
20
|
+
lineWidth: SharedValue<number>;
|
|
21
|
+
isDrawing: SharedValue<boolean>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Empty path constant used as fallback when not drawing
|
|
25
|
+
const EMPTY_PATH = Skia.Path.Make();
|
|
26
|
+
|
|
27
|
+
export const FreehandInProgress = React.memo(function FreehandInProgress({
|
|
28
|
+
path,
|
|
29
|
+
color,
|
|
30
|
+
lineWidth,
|
|
31
|
+
}: FreehandInProgressProps) {
|
|
32
|
+
const safePath = useDerivedValue(() => path.value ?? EMPTY_PATH);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Path
|
|
36
|
+
path={safePath}
|
|
37
|
+
style="stroke"
|
|
38
|
+
strokeWidth={lineWidth}
|
|
39
|
+
color={color}
|
|
40
|
+
strokeCap="round"
|
|
41
|
+
strokeJoin="round"
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ─── Polygon In-Progress ─────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
const POLYGON_VERTEX_RADIUS = 8;
|
|
49
|
+
const POLYGON_VERTEX_COLOR = "rgba(0, 122, 255, 0.8)";
|
|
50
|
+
|
|
51
|
+
interface PolygonInProgressProps {
|
|
52
|
+
pointsFlat: SharedValue<number[]>;
|
|
53
|
+
pointCount: SharedValue<number>;
|
|
54
|
+
isActive: SharedValue<boolean>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const PolygonInProgress = React.memo(function PolygonInProgress({
|
|
58
|
+
pointsFlat,
|
|
59
|
+
pointCount,
|
|
60
|
+
}: PolygonInProgressProps) {
|
|
61
|
+
// Build a path of circles at each vertex using a derived path with addCircle
|
|
62
|
+
const verticesPath = useDerivedValue(() => {
|
|
63
|
+
const p = Skia.Path.Make();
|
|
64
|
+
const count = pointCount.value;
|
|
65
|
+
const flat = pointsFlat.value;
|
|
66
|
+
for (let i = 0; i < count; i++) {
|
|
67
|
+
const cx = flat[i * 2]!;
|
|
68
|
+
const cy = flat[i * 2 + 1]!;
|
|
69
|
+
p.addCircle(cx, cy, POLYGON_VERTEX_RADIUS);
|
|
70
|
+
}
|
|
71
|
+
return p;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Path
|
|
76
|
+
path={verticesPath}
|
|
77
|
+
style="fill"
|
|
78
|
+
color={POLYGON_VERTEX_COLOR}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ─── Shape In-Progress ───────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
interface ShapeInProgressProps {
|
|
86
|
+
shapeType: TwoPointShapeType;
|
|
87
|
+
startX: SharedValue<number>;
|
|
88
|
+
startY: SharedValue<number>;
|
|
89
|
+
currentX: SharedValue<number>;
|
|
90
|
+
currentY: SharedValue<number>;
|
|
91
|
+
color: SharedValue<string>;
|
|
92
|
+
lineWidth: SharedValue<number>;
|
|
93
|
+
fillColor: SharedValue<string | null>;
|
|
94
|
+
fillAlpha: SharedValue<number>;
|
|
95
|
+
isDrawing: SharedValue<boolean>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const ShapeInProgress = React.memo(function ShapeInProgress({
|
|
99
|
+
shapeType,
|
|
100
|
+
startX,
|
|
101
|
+
startY,
|
|
102
|
+
currentX,
|
|
103
|
+
currentY,
|
|
104
|
+
color,
|
|
105
|
+
lineWidth,
|
|
106
|
+
fillColor,
|
|
107
|
+
fillAlpha,
|
|
108
|
+
isDrawing,
|
|
109
|
+
}: ShapeInProgressProps) {
|
|
110
|
+
// Hide when not actively drawing (prevents ghost shapes when switching tools)
|
|
111
|
+
const opacity = useDerivedValue(() => (isDrawing.value ? 1 : 0));
|
|
112
|
+
|
|
113
|
+
// Derived values for the shape bounds
|
|
114
|
+
const x = useDerivedValue(() => Math.min(startX.value, currentX.value));
|
|
115
|
+
const y = useDerivedValue(() => Math.min(startY.value, currentY.value));
|
|
116
|
+
const width = useDerivedValue(() => Math.abs(currentX.value - startX.value));
|
|
117
|
+
const height = useDerivedValue(() => Math.abs(currentY.value - startY.value));
|
|
118
|
+
|
|
119
|
+
// Derived point values for Line-based shapes
|
|
120
|
+
const p1 = useDerivedValue(() => ({ x: startX.value, y: startY.value }));
|
|
121
|
+
const p2 = useDerivedValue(() => ({ x: currentX.value, y: currentY.value }));
|
|
122
|
+
|
|
123
|
+
const resolvedFillColor = useDerivedValue(() => {
|
|
124
|
+
if (!fillColor.value || !isDrawing.value) return "transparent";
|
|
125
|
+
return fillColor.value.replace(/[\d.]+\)$/, `${fillAlpha.value})`);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
switch (shapeType) {
|
|
129
|
+
case "line":
|
|
130
|
+
return (
|
|
131
|
+
<Group opacity={opacity}>
|
|
132
|
+
<SkiaLine
|
|
133
|
+
p1={p1}
|
|
134
|
+
p2={p2}
|
|
135
|
+
color={color}
|
|
136
|
+
style="stroke"
|
|
137
|
+
strokeWidth={lineWidth}
|
|
138
|
+
strokeCap="round"
|
|
139
|
+
/>
|
|
140
|
+
</Group>
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
case "arrow":
|
|
144
|
+
return (
|
|
145
|
+
<Group opacity={opacity}>
|
|
146
|
+
<SkiaLine
|
|
147
|
+
p1={p1}
|
|
148
|
+
p2={p2}
|
|
149
|
+
color={color}
|
|
150
|
+
style="stroke"
|
|
151
|
+
strokeWidth={lineWidth}
|
|
152
|
+
strokeCap="round"
|
|
153
|
+
/>
|
|
154
|
+
</Group>
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
case "rectangle":
|
|
158
|
+
return (
|
|
159
|
+
<Group opacity={opacity}>
|
|
160
|
+
<Rect
|
|
161
|
+
x={x}
|
|
162
|
+
y={y}
|
|
163
|
+
width={width}
|
|
164
|
+
height={height}
|
|
165
|
+
color={resolvedFillColor}
|
|
166
|
+
style="fill"
|
|
167
|
+
/>
|
|
168
|
+
<Rect
|
|
169
|
+
x={x}
|
|
170
|
+
y={y}
|
|
171
|
+
width={width}
|
|
172
|
+
height={height}
|
|
173
|
+
color={color}
|
|
174
|
+
style="stroke"
|
|
175
|
+
strokeWidth={lineWidth}
|
|
176
|
+
/>
|
|
177
|
+
</Group>
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
case "circle":
|
|
181
|
+
return (
|
|
182
|
+
<Group opacity={opacity}>
|
|
183
|
+
<Oval
|
|
184
|
+
x={x}
|
|
185
|
+
y={y}
|
|
186
|
+
width={width}
|
|
187
|
+
height={height}
|
|
188
|
+
color={resolvedFillColor}
|
|
189
|
+
style="fill"
|
|
190
|
+
/>
|
|
191
|
+
<Oval
|
|
192
|
+
x={x}
|
|
193
|
+
y={y}
|
|
194
|
+
width={width}
|
|
195
|
+
height={height}
|
|
196
|
+
color={color}
|
|
197
|
+
style="stroke"
|
|
198
|
+
strokeWidth={lineWidth}
|
|
199
|
+
/>
|
|
200
|
+
</Group>
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
case "measure":
|
|
204
|
+
return (
|
|
205
|
+
<Group opacity={opacity}>
|
|
206
|
+
<SkiaLine
|
|
207
|
+
p1={p1}
|
|
208
|
+
p2={p2}
|
|
209
|
+
color={color}
|
|
210
|
+
style="stroke"
|
|
211
|
+
strokeWidth={lineWidth}
|
|
212
|
+
strokeCap="round"
|
|
213
|
+
/>
|
|
214
|
+
</Group>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { Line as SkiaLine } from "@shopify/react-native-skia";
|
|
3
|
+
import type { LineObject, Size } from "../types";
|
|
4
|
+
import { denormalize } from "../utils/coordinates";
|
|
5
|
+
|
|
6
|
+
interface LineRendererProps {
|
|
7
|
+
object: LineObject;
|
|
8
|
+
canvasSize: Size;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const LineRenderer = React.memo(function LineRenderer({
|
|
12
|
+
object,
|
|
13
|
+
canvasSize,
|
|
14
|
+
}: LineRendererProps) {
|
|
15
|
+
const from = useMemo(
|
|
16
|
+
() => denormalize(object.from, canvasSize),
|
|
17
|
+
[object.from, canvasSize],
|
|
18
|
+
);
|
|
19
|
+
const to = useMemo(
|
|
20
|
+
() => denormalize(object.to, canvasSize),
|
|
21
|
+
[object.to, canvasSize],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<SkiaLine
|
|
26
|
+
p1={from}
|
|
27
|
+
p2={to}
|
|
28
|
+
color={object.color}
|
|
29
|
+
style="stroke"
|
|
30
|
+
strokeWidth={object.lineWidth}
|
|
31
|
+
strokeCap="round"
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
import { Path, Group, Text, matchFont } from "@shopify/react-native-skia";
|
|
4
|
+
import type { MeasureObject, Size } from "../types";
|
|
5
|
+
import { denormalize } from "../utils/coordinates";
|
|
6
|
+
|
|
7
|
+
const FONT_FAMILY = Platform.OS === "android" ? "sans-serif" : "System";
|
|
8
|
+
|
|
9
|
+
interface MeasurementRendererProps {
|
|
10
|
+
object: MeasureObject;
|
|
11
|
+
canvasSize: Size;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const TEXT_GAP_PADDING = 6;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build a measurement / cotation path with a gap in the middle for the label.
|
|
18
|
+
* - Two inward-pointing arrowheads
|
|
19
|
+
* - Perpendicular extension lines at endpoints
|
|
20
|
+
* - Main line split in two halves around a central gap
|
|
21
|
+
*/
|
|
22
|
+
function buildMeasurementPath(
|
|
23
|
+
fx: number,
|
|
24
|
+
fy: number,
|
|
25
|
+
tx: number,
|
|
26
|
+
ty: number,
|
|
27
|
+
lineWidth: number,
|
|
28
|
+
style: 0 | 1 | 2,
|
|
29
|
+
gapHalfWidth: number,
|
|
30
|
+
): string {
|
|
31
|
+
const dx = tx - fx;
|
|
32
|
+
const dy = ty - fy;
|
|
33
|
+
const len = Math.sqrt(dx * dx + dy * dy);
|
|
34
|
+
if (len === 0) return "";
|
|
35
|
+
|
|
36
|
+
const angle = Math.atan2(dy, dx);
|
|
37
|
+
const cos = Math.cos(angle);
|
|
38
|
+
const sin = Math.sin(angle);
|
|
39
|
+
const perpCos = Math.cos(angle + Math.PI / 2);
|
|
40
|
+
const perpSin = Math.sin(angle + Math.PI / 2);
|
|
41
|
+
|
|
42
|
+
const headLen = Math.min(lineWidth * 3, len / 4);
|
|
43
|
+
const headW = lineWidth * 2;
|
|
44
|
+
const extLen = lineWidth * 4;
|
|
45
|
+
|
|
46
|
+
// Midpoint
|
|
47
|
+
const mx = (fx + tx) / 2;
|
|
48
|
+
const my = (fy + ty) / 2;
|
|
49
|
+
|
|
50
|
+
let svg = "";
|
|
51
|
+
|
|
52
|
+
// Main line — split into two segments with gap
|
|
53
|
+
if (gapHalfWidth > 0 && len > gapHalfWidth * 2) {
|
|
54
|
+
const gapStartX = mx - gapHalfWidth * cos;
|
|
55
|
+
const gapStartY = my - gapHalfWidth * sin;
|
|
56
|
+
const gapEndX = mx + gapHalfWidth * cos;
|
|
57
|
+
const gapEndY = my + gapHalfWidth * sin;
|
|
58
|
+
svg += `M ${fx} ${fy} L ${gapStartX} ${gapStartY} `;
|
|
59
|
+
svg += `M ${gapEndX} ${gapEndY} L ${tx} ${ty} `;
|
|
60
|
+
} else {
|
|
61
|
+
svg += `M ${fx} ${fy} L ${tx} ${ty} `;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Arrowhead at "from" pointing toward "to"
|
|
65
|
+
const ah1x = fx + headLen * cos + (headW / 2) * perpCos;
|
|
66
|
+
const ah1y = fy + headLen * sin + (headW / 2) * perpSin;
|
|
67
|
+
const ah2x = fx + headLen * cos - (headW / 2) * perpCos;
|
|
68
|
+
const ah2y = fy + headLen * sin - (headW / 2) * perpSin;
|
|
69
|
+
svg += `M ${fx} ${fy} L ${ah1x} ${ah1y} M ${fx} ${fy} L ${ah2x} ${ah2y} `;
|
|
70
|
+
|
|
71
|
+
// Arrowhead at "to" pointing toward "from"
|
|
72
|
+
const bh1x = tx - headLen * cos + (headW / 2) * perpCos;
|
|
73
|
+
const bh1y = ty - headLen * sin + (headW / 2) * perpSin;
|
|
74
|
+
const bh2x = tx - headLen * cos - (headW / 2) * perpCos;
|
|
75
|
+
const bh2y = ty - headLen * sin - (headW / 2) * perpSin;
|
|
76
|
+
svg += `M ${tx} ${ty} L ${bh1x} ${bh1y} M ${tx} ${ty} L ${bh2x} ${bh2y} `;
|
|
77
|
+
|
|
78
|
+
// Extension lines (perpendicular to the main line at each endpoint)
|
|
79
|
+
if (style === 0 || style === 2) {
|
|
80
|
+
svg += `M ${fx + extLen * perpCos} ${fy + extLen * perpSin} L ${fx - extLen * perpCos} ${fy - extLen * perpSin} `;
|
|
81
|
+
svg += `M ${tx + extLen * perpCos} ${ty + extLen * perpSin} L ${tx - extLen * perpCos} ${ty - extLen * perpSin} `;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return svg;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const MeasurementRenderer = React.memo(function MeasurementRenderer({
|
|
88
|
+
object,
|
|
89
|
+
canvasSize,
|
|
90
|
+
}: MeasurementRendererProps) {
|
|
91
|
+
const from = useMemo(
|
|
92
|
+
() => denormalize(object.from, canvasSize),
|
|
93
|
+
[object.from, canvasSize],
|
|
94
|
+
);
|
|
95
|
+
const to = useMemo(
|
|
96
|
+
() => denormalize(object.to, canvasSize),
|
|
97
|
+
[object.to, canvasSize],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const labelText = `${object.text}${object.unit}`;
|
|
101
|
+
const hasLabel = object.text.length > 0;
|
|
102
|
+
|
|
103
|
+
const fontSize = Math.max(14, object.lineWidth * 3);
|
|
104
|
+
const font = useMemo(
|
|
105
|
+
() => matchFont({ fontFamily: FONT_FAMILY, fontSize }),
|
|
106
|
+
[fontSize],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Measure text width for the gap
|
|
110
|
+
const textWidth = useMemo(() => {
|
|
111
|
+
if (!hasLabel) return 0;
|
|
112
|
+
return font.getTextWidth(labelText);
|
|
113
|
+
}, [font, labelText, hasLabel]);
|
|
114
|
+
|
|
115
|
+
const gapHalfWidth = hasLabel ? textWidth / 2 + TEXT_GAP_PADDING : 0;
|
|
116
|
+
|
|
117
|
+
const pathString = useMemo(
|
|
118
|
+
() =>
|
|
119
|
+
buildMeasurementPath(
|
|
120
|
+
from.x,
|
|
121
|
+
from.y,
|
|
122
|
+
to.x,
|
|
123
|
+
to.y,
|
|
124
|
+
object.lineWidth,
|
|
125
|
+
object.measureStyle,
|
|
126
|
+
gapHalfWidth,
|
|
127
|
+
),
|
|
128
|
+
[from, to, object.lineWidth, object.measureStyle, gapHalfWidth],
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Center of the line
|
|
132
|
+
const midX = (from.x + to.x) / 2;
|
|
133
|
+
const midY = (from.y + to.y) / 2;
|
|
134
|
+
|
|
135
|
+
// Angle of the line — keep text readable (never upside down)
|
|
136
|
+
const rawAngle = Math.atan2(to.y - from.y, to.x - from.x);
|
|
137
|
+
const angle =
|
|
138
|
+
rawAngle > Math.PI / 2
|
|
139
|
+
? rawAngle - Math.PI
|
|
140
|
+
: rawAngle < -Math.PI / 2
|
|
141
|
+
? rawAngle + Math.PI
|
|
142
|
+
: rawAngle;
|
|
143
|
+
|
|
144
|
+
// Position text centered at origin (will be transformed to midpoint)
|
|
145
|
+
const textX = -textWidth / 2;
|
|
146
|
+
const textY = fontSize / 3;
|
|
147
|
+
|
|
148
|
+
if (!pathString) return null;
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<Group>
|
|
152
|
+
<Path
|
|
153
|
+
path={pathString}
|
|
154
|
+
style="stroke"
|
|
155
|
+
strokeWidth={object.lineWidth}
|
|
156
|
+
color={object.color}
|
|
157
|
+
strokeCap="round"
|
|
158
|
+
strokeJoin="round"
|
|
159
|
+
/>
|
|
160
|
+
{hasLabel && (
|
|
161
|
+
<Group
|
|
162
|
+
transform={[
|
|
163
|
+
{ translateX: midX },
|
|
164
|
+
{ translateY: midY },
|
|
165
|
+
{ rotate: angle },
|
|
166
|
+
]}
|
|
167
|
+
>
|
|
168
|
+
<Text
|
|
169
|
+
x={textX}
|
|
170
|
+
y={textY}
|
|
171
|
+
text={labelText}
|
|
172
|
+
font={font}
|
|
173
|
+
color={object.color}
|
|
174
|
+
/>
|
|
175
|
+
</Group>
|
|
176
|
+
)}
|
|
177
|
+
</Group>
|
|
178
|
+
);
|
|
179
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { DrawingObject, Size } from "../types";
|
|
3
|
+
import { FreehandRenderer } from "./FreehandRenderer";
|
|
4
|
+
import { LineRenderer } from "./LineRenderer";
|
|
5
|
+
import { ArrowRenderer } from "./ArrowRenderer";
|
|
6
|
+
import { RectRenderer } from "./RectRenderer";
|
|
7
|
+
import { CircleRenderer } from "./CircleRenderer";
|
|
8
|
+
import { PolygonRenderer } from "./PolygonRenderer";
|
|
9
|
+
import { MeasurementRenderer } from "./MeasurementRenderer";
|
|
10
|
+
|
|
11
|
+
interface ObjectRendererProps {
|
|
12
|
+
object: DrawingObject;
|
|
13
|
+
canvasSize: Size;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Routes a drawing object to its specialized renderer component.
|
|
18
|
+
*/
|
|
19
|
+
export const ObjectRenderer = React.memo(function ObjectRenderer({
|
|
20
|
+
object,
|
|
21
|
+
canvasSize,
|
|
22
|
+
}: ObjectRendererProps) {
|
|
23
|
+
switch (object.type) {
|
|
24
|
+
case "freehand":
|
|
25
|
+
return <FreehandRenderer object={object} canvasSize={canvasSize} />;
|
|
26
|
+
case "line":
|
|
27
|
+
return <LineRenderer object={object} canvasSize={canvasSize} />;
|
|
28
|
+
case "arrow":
|
|
29
|
+
return <ArrowRenderer object={object} canvasSize={canvasSize} />;
|
|
30
|
+
case "rectangle":
|
|
31
|
+
return <RectRenderer object={object} canvasSize={canvasSize} />;
|
|
32
|
+
case "circle":
|
|
33
|
+
return <CircleRenderer object={object} canvasSize={canvasSize} />;
|
|
34
|
+
case "polygon":
|
|
35
|
+
return <PolygonRenderer object={object} canvasSize={canvasSize} />;
|
|
36
|
+
case "measure":
|
|
37
|
+
return <MeasurementRenderer object={object} canvasSize={canvasSize} />;
|
|
38
|
+
case "text":
|
|
39
|
+
// Text objects are rendered outside the Skia Canvas as native RN views
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
});
|