@annotorious/annotorious 3.5.0 → 3.6.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/dist/annotation/shapes/Polyline.svelte.d.ts +1 -0
- package/dist/annotation/shapes/index.d.ts +2 -1
- package/dist/annotorious.css +1 -1
- package/dist/annotorious.es.js +1846 -1665
- package/dist/annotorious.es.js.map +1 -1
- package/dist/annotorious.js +2 -2
- package/dist/annotorious.js.map +1 -1
- package/dist/model/core/Shape.d.ts +2 -1
- package/dist/model/core/index.d.ts +1 -0
- package/dist/model/core/polyline/Polyline.d.ts +16 -0
- package/dist/model/core/polyline/index.d.ts +2 -0
- package/dist/model/core/polyline/polylineUtils.d.ts +3 -0
- package/package.json +2 -2
- package/src/annotation/SVGAnnotationLayer.svelte +28 -23
- package/src/annotation/SVGAnnotationLayerPointerEvent.ts +1 -1
- package/src/annotation/editors/editorsRegistry.ts +1 -1
- package/src/annotation/shapes/Polyline.svelte +35 -0
- package/src/annotation/shapes/index.ts +2 -1
- package/src/model/core/Shape.ts +3 -1
- package/src/model/core/index.ts +1 -0
- package/src/model/core/multipolygon/multiPolygonUtils.ts +1 -1
- package/src/model/core/polyline/Polyline.ts +31 -0
- package/src/model/core/polyline/index.ts +2 -0
- package/src/model/core/polyline/polylineUtils.ts +193 -0
- package/src/model/w3c/svg/SVGSelector.ts +2 -2
|
@@ -5,8 +5,9 @@ export interface Shape extends AbstractSelector {
|
|
|
5
5
|
}
|
|
6
6
|
export declare enum ShapeType {
|
|
7
7
|
ELLIPSE = "ELLIPSE",
|
|
8
|
-
|
|
8
|
+
MULTIPOLYGON = "MULTIPOLYGON",
|
|
9
9
|
POLYGON = "POLYGON",
|
|
10
|
+
POLYLINE = "POLYLINE",
|
|
10
11
|
RECTANGLE = "RECTANGLE",
|
|
11
12
|
LINE = "LINE"
|
|
12
13
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Bounds, Geometry, Shape } from '../Shape';
|
|
2
|
+
export interface Polyline extends Shape {
|
|
3
|
+
geometry: PolylineGeometry;
|
|
4
|
+
}
|
|
5
|
+
export interface PolylineGeometry extends Geometry {
|
|
6
|
+
points: PolylinePoint[];
|
|
7
|
+
closed?: boolean;
|
|
8
|
+
bounds: Bounds;
|
|
9
|
+
}
|
|
10
|
+
export interface PolylinePoint {
|
|
11
|
+
type: 'CORNER' | 'CURVE';
|
|
12
|
+
point: [number, number];
|
|
13
|
+
inHandle?: [number, number];
|
|
14
|
+
outHandle?: [number, number];
|
|
15
|
+
locked?: boolean;
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@annotorious/annotorious",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Add image annotation functionality to any web page with a few lines of JavaScript",
|
|
5
5
|
"author": "Rainer Simon",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"vitest": "^3.2.4"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@annotorious/core": "3.
|
|
52
|
+
"@annotorious/core": "3.6.0",
|
|
53
53
|
"dequal": "^2.0.3",
|
|
54
54
|
"rbush": "^4.0.1",
|
|
55
55
|
"simplify-js": "^1.2.4",
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import type { Annotation, DrawingStyleExpression, StoreChangeEvent, User } from '@annotorious/core';
|
|
5
5
|
import { isImageAnnotation, ShapeType } from '../model';
|
|
6
6
|
import type { ImageAnnotation, Shape} from '../model';
|
|
7
|
-
import { getEditor
|
|
8
|
-
import { Ellipse, Line, MultiPolygon, Polygon, Rectangle} from './shapes';
|
|
7
|
+
import { getEditor, EditorMount } from './editors';
|
|
8
|
+
import { Ellipse, Line, MultiPolygon, Polygon, Polyline, Rectangle} from './shapes';
|
|
9
9
|
import { getTool, listDrawingTools, ToolMount } from './tools';
|
|
10
10
|
import { enableResponsive } from './utils';
|
|
11
11
|
import { createSVGTransform } from './Transform';
|
|
@@ -56,8 +56,6 @@
|
|
|
56
56
|
|
|
57
57
|
let editableAnnotations: ImageAnnotation[] | undefined;
|
|
58
58
|
|
|
59
|
-
$: isEditable = (a: ImageAnnotation) => $selection.selected.find(s => s.id === a.id && s.editable);
|
|
60
|
-
|
|
61
59
|
$: trackSelection($selection.selected);
|
|
62
60
|
|
|
63
61
|
const trackSelection = (selected: { id: string, editable?: boolean }[]) => {
|
|
@@ -128,7 +126,7 @@
|
|
|
128
126
|
const onPointerMove = (evt: PointerEvent) => {
|
|
129
127
|
const { x, y } = getSVGPoint(evt, svgEl);
|
|
130
128
|
|
|
131
|
-
const hit = store.getAt(x, y);
|
|
129
|
+
const hit = store.getAt(x, y, undefined, 2);
|
|
132
130
|
if (hit) {
|
|
133
131
|
if ($hover !== hit.id) {
|
|
134
132
|
hover.set(hit.id);
|
|
@@ -138,8 +136,13 @@
|
|
|
138
136
|
}
|
|
139
137
|
}
|
|
140
138
|
|
|
141
|
-
//
|
|
142
|
-
|
|
139
|
+
// [annotation -> editor] - note that we may not have editors available for
|
|
140
|
+
// all annotations, because they might rely on plugins in some cases!
|
|
141
|
+
$: editors = editableAnnotations ? editableAnnotations.map(annotation => ({
|
|
142
|
+
annotation, editor: getEditor(annotation.target.selector)!
|
|
143
|
+
})).filter(t => t.editor) : undefined;
|
|
144
|
+
|
|
145
|
+
$: isEditable = (a: ImageAnnotation) => editors && editors.some(t => t.annotation.id === a.id);
|
|
143
146
|
</script>
|
|
144
147
|
|
|
145
148
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
@@ -175,11 +178,16 @@
|
|
|
175
178
|
annotation={annotation}
|
|
176
179
|
geom={selector.geometry}
|
|
177
180
|
style={style} />
|
|
178
|
-
{:else if (selector?.type === ShapeType.
|
|
181
|
+
{:else if (selector?.type === ShapeType.MULTIPOLYGON)}
|
|
179
182
|
<MultiPolygon
|
|
180
183
|
annotation={annotation}
|
|
181
184
|
geom={selector.geometry}
|
|
182
185
|
style={style} />
|
|
186
|
+
{:else if (selector?.type === ShapeType.POLYLINE)}
|
|
187
|
+
<Polyline
|
|
188
|
+
annotation={annotation}
|
|
189
|
+
geom={selector.geometry}
|
|
190
|
+
style={style} />
|
|
183
191
|
{:else if (selector?.type === ShapeType.LINE)}
|
|
184
192
|
<Line
|
|
185
193
|
annotation={annotation}
|
|
@@ -195,21 +203,18 @@
|
|
|
195
203
|
bind:this={drawingEl}
|
|
196
204
|
class="drawing" >
|
|
197
205
|
{#if drawingEl}
|
|
198
|
-
{#if
|
|
199
|
-
{#each
|
|
200
|
-
{
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
on:change={onChangeSelected(editable)} />
|
|
211
|
-
{/key}
|
|
212
|
-
{/if}
|
|
206
|
+
{#if editors}
|
|
207
|
+
{#each editors as editable}
|
|
208
|
+
{#key editable.annotation.id}
|
|
209
|
+
<EditorMount
|
|
210
|
+
target={drawingEl}
|
|
211
|
+
editor={editable.editor}
|
|
212
|
+
annotation={editable.annotation}
|
|
213
|
+
style={style}
|
|
214
|
+
transform={transform}
|
|
215
|
+
viewportScale={$scale}
|
|
216
|
+
on:change={onChangeSelected(editable.annotation)} />
|
|
217
|
+
{/key}
|
|
213
218
|
{/each}
|
|
214
219
|
{:else if (tool && drawingEnabled)}
|
|
215
220
|
{#key `${toolName}-${toolMountKey}`}
|
|
@@ -27,7 +27,7 @@ export const addEventListeners = <T extends Annotation>(svg: SVGSVGElement, stor
|
|
|
27
27
|
if (duration < MAX_CLICK_DURATION) {
|
|
28
28
|
const { x, y } = getSVGPoint(evt, svg);
|
|
29
29
|
|
|
30
|
-
const annotation = store.getAt(x, y) as T | undefined;
|
|
30
|
+
const annotation = store.getAt(x, y, undefined, 2) as T | undefined;
|
|
31
31
|
|
|
32
32
|
if (annotation)
|
|
33
33
|
dispatch('click', { originalEvent: evt, annotation });
|
|
@@ -7,7 +7,7 @@ import { RectangleEditor } from './rectangle';
|
|
|
7
7
|
const REGISTERED = new Map<ShapeType, typeof SvelteComponent>([
|
|
8
8
|
[ShapeType.RECTANGLE, RectangleEditor as typeof SvelteComponent],
|
|
9
9
|
[ShapeType.POLYGON, PolygonEditor as typeof SvelteComponent],
|
|
10
|
-
[ShapeType.
|
|
10
|
+
[ShapeType.MULTIPOLYGON, MultiPolygonEditor as typeof SvelteComponent]
|
|
11
11
|
]);
|
|
12
12
|
|
|
13
13
|
export const getEditor = (shape: Shape) => REGISTERED.get(shape.type);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { DrawingStyleExpression } from '@annotorious/core';
|
|
3
|
+
import { computeSVGPath} from '../../model';
|
|
4
|
+
import type { Geometry, ImageAnnotation, PolylineGeometry } from '../../model';
|
|
5
|
+
import { computeStyle } from '../utils/styling';
|
|
6
|
+
|
|
7
|
+
/** Props **/
|
|
8
|
+
export let annotation: ImageAnnotation;
|
|
9
|
+
export let geom: Geometry;
|
|
10
|
+
export let style: DrawingStyleExpression<ImageAnnotation> | undefined;
|
|
11
|
+
|
|
12
|
+
$: computedStyle = computeStyle(annotation, style);
|
|
13
|
+
|
|
14
|
+
$: d = computeSVGPath(geom as PolylineGeometry);
|
|
15
|
+
|
|
16
|
+
$: cssClass = (geom as PolylineGeometry).closed ? 'closed' : 'open'
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<g class="a9s-annotation" data-id={annotation.id}>
|
|
20
|
+
<path
|
|
21
|
+
class={`a9s-outer ${cssClass}`}
|
|
22
|
+
style={computedStyle ? 'display:none;' : undefined}
|
|
23
|
+
d={d} />
|
|
24
|
+
|
|
25
|
+
<path
|
|
26
|
+
class={`a9s-inner ${cssClass}`}
|
|
27
|
+
style={computedStyle}
|
|
28
|
+
d={d} />
|
|
29
|
+
</g>
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
path.open {
|
|
33
|
+
fill: transparent !important;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as Ellipse } from './Ellipse.svelte';
|
|
2
|
+
export { default as Line } from './Line.svelte';
|
|
2
3
|
export { default as MultiPolygon } from './MultiPolygon.svelte';
|
|
3
4
|
export { default as Polygon } from './Polygon.svelte';
|
|
5
|
+
export { default as Polyline } from './Polyline.svelte';
|
|
4
6
|
export { default as Rectangle } from './Rectangle.svelte';
|
|
5
|
-
export { default as Line } from './Line.svelte';
|
package/src/model/core/Shape.ts
CHANGED
package/src/model/core/index.ts
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Bounds, Geometry, Shape } from '../Shape';
|
|
2
|
+
|
|
3
|
+
export interface Polyline extends Shape {
|
|
4
|
+
|
|
5
|
+
geometry: PolylineGeometry;
|
|
6
|
+
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PolylineGeometry extends Geometry {
|
|
10
|
+
|
|
11
|
+
points: PolylinePoint[];
|
|
12
|
+
|
|
13
|
+
closed?: boolean;
|
|
14
|
+
|
|
15
|
+
bounds: Bounds;
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PolylinePoint {
|
|
20
|
+
|
|
21
|
+
type: 'CORNER' | 'CURVE';
|
|
22
|
+
|
|
23
|
+
point: [number, number];
|
|
24
|
+
|
|
25
|
+
inHandle?: [number, number];
|
|
26
|
+
|
|
27
|
+
outHandle?: [number, number];
|
|
28
|
+
|
|
29
|
+
locked?: boolean;
|
|
30
|
+
|
|
31
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { ShapeType } from '../Shape';
|
|
2
|
+
import { computePolygonArea, isPointInPolygon, registerShapeUtil, type ShapeUtil } from '../shapeUtils';
|
|
3
|
+
import type { Polyline, PolylineGeometry } from './Polyline';
|
|
4
|
+
|
|
5
|
+
const PolylineUtil: ShapeUtil<Polyline> = {
|
|
6
|
+
|
|
7
|
+
area: (polyline: Polyline): number => {
|
|
8
|
+
const geom = polyline.geometry;
|
|
9
|
+
|
|
10
|
+
if (!geom.closed || geom.points.length < 3)
|
|
11
|
+
return 0;
|
|
12
|
+
|
|
13
|
+
const points = approximateAsPolygon(geom);
|
|
14
|
+
return computePolygonArea(points);
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
intersects: (polyline: Polyline, x: number, y: number, buffer: number = 2): boolean => {
|
|
18
|
+
const geom = polyline.geometry;
|
|
19
|
+
|
|
20
|
+
if (geom.closed) {
|
|
21
|
+
const points = approximateAsPolygon(geom);
|
|
22
|
+
return isPointInPolygon(points, x, y);
|
|
23
|
+
} else {
|
|
24
|
+
return isPointNearPath(geom, [x, y], buffer);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const approximateAsPolygon = (geom: PolylineGeometry): [number, number][] => {
|
|
31
|
+
const points: [number, number][] = [];
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < geom.points.length; i++) {
|
|
34
|
+
const currentPoint = geom.points[i];
|
|
35
|
+
const nextPoint = geom.points[(i + 1) % geom.points.length];
|
|
36
|
+
|
|
37
|
+
points.push(currentPoint.point);
|
|
38
|
+
|
|
39
|
+
// If there's a curve to the next point, approximate it
|
|
40
|
+
if (i < geom.points.length - 1 || geom.closed) {
|
|
41
|
+
const hasCurve = currentPoint.outHandle || nextPoint.inHandle;
|
|
42
|
+
if (hasCurve) {
|
|
43
|
+
const curvePoints = approximateBezierCurve(
|
|
44
|
+
currentPoint.point,
|
|
45
|
+
currentPoint.outHandle || currentPoint.point,
|
|
46
|
+
nextPoint.inHandle || nextPoint.point,
|
|
47
|
+
nextPoint.point,
|
|
48
|
+
10 // number of approximation segments
|
|
49
|
+
);
|
|
50
|
+
points.push(...curvePoints.slice(1)); // Skip first point (already added)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return points;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const approximateBezierCurve = (
|
|
59
|
+
p0: [number, number],
|
|
60
|
+
p1: [number, number],
|
|
61
|
+
p2: [number, number],
|
|
62
|
+
p3: [number, number],
|
|
63
|
+
segments: number = 10
|
|
64
|
+
): [number, number][] => {
|
|
65
|
+
const points: [number, number][] = [];
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i <= segments; i++) {
|
|
68
|
+
const t = i / segments;
|
|
69
|
+
const x = Math.pow(1 - t, 3) * p0[0] +
|
|
70
|
+
3 * Math.pow(1 - t, 2) * t * p1[0] +
|
|
71
|
+
3 * (1 - t) * Math.pow(t, 2) * p2[0] +
|
|
72
|
+
Math.pow(t, 3) * p3[0];
|
|
73
|
+
const y = Math.pow(1 - t, 3) * p0[1] +
|
|
74
|
+
3 * Math.pow(1 - t, 2) * t * p1[1] +
|
|
75
|
+
3 * (1 - t) * Math.pow(t, 2) * p2[1] +
|
|
76
|
+
Math.pow(t, 3) * p3[1];
|
|
77
|
+
points.push([x, y]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return points;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const isPointNearPath = (geom: PolylineGeometry, point: [number, number], buffer: number): boolean => {
|
|
84
|
+
for (let i = 0; i < geom.points.length - 1; i++) {
|
|
85
|
+
const currentPoint = geom.points[i];
|
|
86
|
+
const nextPoint = geom.points[i + 1];
|
|
87
|
+
|
|
88
|
+
const hasCurve = currentPoint.outHandle || nextPoint.inHandle;
|
|
89
|
+
if (hasCurve) {
|
|
90
|
+
const curvePoints = approximateBezierCurve(
|
|
91
|
+
currentPoint.point,
|
|
92
|
+
currentPoint.outHandle || currentPoint.point,
|
|
93
|
+
nextPoint.inHandle || nextPoint.point,
|
|
94
|
+
nextPoint.point,
|
|
95
|
+
20 // TODO make configurable? Based on scale factor? Length?
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
for (let j = 0; j < curvePoints.length - 1; j++) {
|
|
99
|
+
const distance = distanceToLineSegment(point, curvePoints[j], curvePoints[j + 1]);
|
|
100
|
+
if (distance <= buffer) return true;
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
const distance = distanceToLineSegment(point, currentPoint.point, nextPoint.point);
|
|
104
|
+
if (distance <= buffer) return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const distanceToLineSegment = (
|
|
112
|
+
point: [number, number],
|
|
113
|
+
lineStart: [number, number],
|
|
114
|
+
lineEnd: [number, number]
|
|
115
|
+
): number => {
|
|
116
|
+
const [px, py] = point;
|
|
117
|
+
const [x1, y1] = lineStart;
|
|
118
|
+
const [x2, y2] = lineEnd;
|
|
119
|
+
|
|
120
|
+
const dx = x2 - x1;
|
|
121
|
+
const dy = y2 - y1;
|
|
122
|
+
const length = Math.sqrt(dx * dx + dy * dy);
|
|
123
|
+
|
|
124
|
+
if (length === 0) {
|
|
125
|
+
// Line segment is a point
|
|
126
|
+
return Math.sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Calculate the projection parameter t to see where the perpendicular falls
|
|
130
|
+
const t = ((px - x1) * dx + (py - y1) * dy) / (length * length);
|
|
131
|
+
|
|
132
|
+
if (t <= 0) {
|
|
133
|
+
// Closest point is the start of the segment
|
|
134
|
+
return Math.sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1));
|
|
135
|
+
} else if (t >= 1) {
|
|
136
|
+
// Closest point is the end of the segment
|
|
137
|
+
return Math.sqrt((px - x2) * (px - x2) + (py - y2) * (py - y2));
|
|
138
|
+
} else {
|
|
139
|
+
// Closest point is on the segment - use the exact line distance formula
|
|
140
|
+
// This is the same formula as your LineUtil.intersects
|
|
141
|
+
const area = Math.abs(((y2 - y1) * px) - ((x2 - x1) * py) + (x2 * y1) - (y2 * x1));
|
|
142
|
+
return area / length;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const computeSVGPath = (geom: PolylineGeometry) => {
|
|
147
|
+
if (!geom.points || geom.points.length === 0)
|
|
148
|
+
return '';
|
|
149
|
+
|
|
150
|
+
const pathCommands: string[] = [];
|
|
151
|
+
|
|
152
|
+
const firstPoint = geom.points[0];
|
|
153
|
+
pathCommands.push(`M ${firstPoint.point[0]} ${firstPoint.point[1]}`);
|
|
154
|
+
|
|
155
|
+
for (let i = 1; i < geom.points.length; i++) {
|
|
156
|
+
const currentPoint = geom.points[i];
|
|
157
|
+
const previousPoint = geom.points[i - 1];
|
|
158
|
+
|
|
159
|
+
if (currentPoint.type === 'CURVE' || previousPoint.type === 'CURVE') {
|
|
160
|
+
// Cubic Bézier curve
|
|
161
|
+
const cp1 = previousPoint.outHandle || previousPoint.point;
|
|
162
|
+
const cp2 = currentPoint.inHandle || currentPoint.point;
|
|
163
|
+
const endPoint = currentPoint.point;
|
|
164
|
+
|
|
165
|
+
pathCommands.push(`C ${cp1[0]} ${cp1[1]} ${cp2[0]} ${cp2[1]} ${endPoint[0]} ${endPoint[1]}`);
|
|
166
|
+
} else {
|
|
167
|
+
// Straight line
|
|
168
|
+
pathCommands.push(`L ${currentPoint.point[0]} ${currentPoint.point[1]}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (geom.closed) {
|
|
173
|
+
// Handle curve from last point back to first point
|
|
174
|
+
const lastPoint = geom.points[geom.points.length - 1];
|
|
175
|
+
const firstPointRef = geom.points[0];
|
|
176
|
+
|
|
177
|
+
const hasClosingCurve = lastPoint.outHandle || firstPointRef.inHandle;
|
|
178
|
+
|
|
179
|
+
if (hasClosingCurve) {
|
|
180
|
+
const cp1 = lastPoint.outHandle || lastPoint.point;
|
|
181
|
+
const cp2 = firstPointRef.inHandle || firstPointRef.point;
|
|
182
|
+
const endPoint = firstPointRef.point;
|
|
183
|
+
|
|
184
|
+
pathCommands.push(`C ${cp1[0]} ${cp1[1]} ${cp2[0]} ${cp2[1]} ${endPoint[0]} ${endPoint[1]}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
pathCommands.push('Z');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return pathCommands.join(' ');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
registerShapeUtil(ShapeType.POLYLINE, PolylineUtil);
|
|
@@ -108,7 +108,7 @@ const parseSVGPath = (value: string): Polygon | MultiPolygon => {
|
|
|
108
108
|
bounds
|
|
109
109
|
}
|
|
110
110
|
} : {
|
|
111
|
-
type: ShapeType.
|
|
111
|
+
type: ShapeType.MULTIPOLYGON,
|
|
112
112
|
geometry: {
|
|
113
113
|
polygons,
|
|
114
114
|
bounds
|
|
@@ -153,7 +153,7 @@ export const serializeSVGSelector = (shape: Shape): SVGSelector => {
|
|
|
153
153
|
value = `<svg><ellipse cx="${geom.cx}" cy="${geom.cy}" rx="${geom.rx}" ry="${geom.ry}" /></svg>`;
|
|
154
154
|
break;
|
|
155
155
|
}
|
|
156
|
-
case ShapeType.
|
|
156
|
+
case ShapeType.MULTIPOLYGON: {
|
|
157
157
|
const geom = shape.geometry as MultiPolygonGeometry;
|
|
158
158
|
value = `<svg>${serializeMultiPolygon(geom)}</svg>`;
|
|
159
159
|
break;
|