@annotorious/annotorious 3.7.22 → 3.8.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/editors/rectangle/rotationUtils.d.ts +26 -0
- package/dist/annotorious.css +1 -1
- package/dist/annotorious.es.js +3362 -3209
- package/dist/annotorious.es.js.map +1 -1
- package/dist/annotorious.js +1 -1
- package/dist/annotorious.js.map +1 -1
- package/dist/model/core/rectangle/Rectangle.d.ts +1 -0
- package/package.json +2 -2
- package/src/Annotorious.css +1 -0
- package/src/annotation/SVGAnnotationLayer.svelte +3 -3
- package/src/annotation/editors/rectangle/RectangleEditor.svelte +184 -68
- package/src/annotation/editors/rectangle/rotationUtils.ts +104 -0
- package/src/annotation/shapes/Rectangle.svelte +22 -15
- package/src/annotation/tools/rectangle/RubberbandRectangle.svelte +1 -0
- package/src/model/core/rectangle/Rectangle.ts +2 -0
- package/src/model/core/rectangle/rectangleUtils.ts +31 -5
- package/src/model/w3c/W3CImageFormatAdapter.ts +6 -4
- package/src/model/w3c/fragment/FragmentSelector.ts +1 -0
- package/src/model/w3c/svg/SVGSelector.ts +81 -0
- package/src/state/spatialTree.ts +2 -5
|
@@ -12,6 +12,8 @@ import type {
|
|
|
12
12
|
PolygonGeometry,
|
|
13
13
|
Polyline,
|
|
14
14
|
PolylineGeometry,
|
|
15
|
+
Rectangle,
|
|
16
|
+
RectangleGeometry,
|
|
15
17
|
Shape
|
|
16
18
|
} from '../../core';
|
|
17
19
|
|
|
@@ -107,6 +109,68 @@ const parseSVGPathToPolyline = (value: string): Polyline => {
|
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
const parseSVGRect = (value: string): Rectangle => {
|
|
113
|
+
const doc = parseSVGXML(value);
|
|
114
|
+
|
|
115
|
+
const rect = doc.nodeName === 'rect' ? doc : Array.from(doc.querySelectorAll('rect'))[0];
|
|
116
|
+
if (!rect) throw new Error('Could not parse SVG rect');
|
|
117
|
+
|
|
118
|
+
const x = parseFloat(rect.getAttribute('x')!);
|
|
119
|
+
const y = parseFloat(rect.getAttribute('y')!);
|
|
120
|
+
const w = parseFloat(rect.getAttribute('width')!);
|
|
121
|
+
const h = parseFloat(rect.getAttribute('height')!);
|
|
122
|
+
|
|
123
|
+
const transform = rect.getAttribute('transform');
|
|
124
|
+
let rot = 0;
|
|
125
|
+
|
|
126
|
+
if (transform && transform.startsWith('rotate(')) {
|
|
127
|
+
const match = transform.match(/rotate\(([^)]+)\)/);
|
|
128
|
+
if (match) {
|
|
129
|
+
const params = match[1].split(/\s+/).map(parseFloat);
|
|
130
|
+
rot = (params[0] * Math.PI) / 180;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Compute bounds
|
|
135
|
+
const cx = x + w / 2;
|
|
136
|
+
const cy = y + h / 2;
|
|
137
|
+
|
|
138
|
+
const corners = [
|
|
139
|
+
[x, y],
|
|
140
|
+
[x + w, y],
|
|
141
|
+
[x + w, y + h],
|
|
142
|
+
[x, y + h]
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
// Rotate corners around center
|
|
146
|
+
const rotatedCorners = corners.map(([px, py]) => {
|
|
147
|
+
const dx = px - cx;
|
|
148
|
+
const dy = py - cy;
|
|
149
|
+
|
|
150
|
+
const cos = Math.cos(rot);
|
|
151
|
+
const sin = Math.sin(rot);
|
|
152
|
+
|
|
153
|
+
return [
|
|
154
|
+
cx + dx * cos - dy * sin,
|
|
155
|
+
cy + dx * sin + dy * cos
|
|
156
|
+
];
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const bounds = boundsFromPoints(rotatedCorners as [number, number][]);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
type: ShapeType.RECTANGLE,
|
|
163
|
+
geometry: {
|
|
164
|
+
x,
|
|
165
|
+
y,
|
|
166
|
+
w,
|
|
167
|
+
h,
|
|
168
|
+
rot,
|
|
169
|
+
bounds
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
110
174
|
const parseSVGPathToPolygon = (value: string): Polygon | MultiPolygon => {
|
|
111
175
|
const doc = parseSVGXML(value);
|
|
112
176
|
|
|
@@ -151,6 +215,8 @@ export const parseSVGSelector = <T extends Shape>(valueOrSelector: SVGSelector |
|
|
|
151
215
|
return parseSVGEllipse(value) as unknown as T;
|
|
152
216
|
else if (value.includes("<line "))
|
|
153
217
|
return parseSVGLine(value) as unknown as T;
|
|
218
|
+
else if (value.includes('<rect '))
|
|
219
|
+
return parseSVGRect(value) as unknown as T;
|
|
154
220
|
else
|
|
155
221
|
throw 'Unsupported SVG shape: ' + value;
|
|
156
222
|
}
|
|
@@ -166,6 +232,21 @@ export const serializeSVGSelector = (shape: Shape): SVGSelector => {
|
|
|
166
232
|
let value: string | undefined;
|
|
167
233
|
|
|
168
234
|
switch (shape.type) {
|
|
235
|
+
case ShapeType.RECTANGLE: {
|
|
236
|
+
const geom = shape.geometry as RectangleGeometry;
|
|
237
|
+
const { x, y, w, h, rot } = geom;
|
|
238
|
+
|
|
239
|
+
if (!rot) {
|
|
240
|
+
value = `<svg><rect x="${x}" y="${y}" width="${w}" height="${h}" /></svg>`;
|
|
241
|
+
} else {
|
|
242
|
+
const cx = x + w / 2;
|
|
243
|
+
const cy = y + h / 2;
|
|
244
|
+
const angle = ((rot ?? 0) * 180) / Math.PI;
|
|
245
|
+
|
|
246
|
+
value = `<svg><rect x="${x}" y="${y}" width="${w}" height="${h}" transform="rotate(${angle} ${cx} ${cy})" /></svg>`;
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
169
250
|
case ShapeType.POLYGON: {
|
|
170
251
|
const geom = shape.geometry as PolygonGeometry;
|
|
171
252
|
const { points } = geom;
|
package/src/state/spatialTree.ts
CHANGED
|
@@ -83,11 +83,8 @@ export const createSpatialTree = () => {
|
|
|
83
83
|
maxY: y + buffer
|
|
84
84
|
}).map(item => item.target);
|
|
85
85
|
|
|
86
|
-
// Exact hit test on shape
|
|
87
|
-
const exactHits = idxHits.filter(
|
|
88
|
-
return (target.selector.type === ShapeType.RECTANGLE) ||
|
|
89
|
-
intersects(target.selector, x, y, buffer);
|
|
90
|
-
});
|
|
86
|
+
// Exact hit test on shape
|
|
87
|
+
const exactHits = idxHits.filter(({ selector }) => intersects(selector, x, y, buffer));
|
|
91
88
|
|
|
92
89
|
// Get smallest shape
|
|
93
90
|
if (exactHits.length > 0) {
|