@node-projects/web-component-designer 0.1.347 → 0.1.350
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/elements/helper/CssCombiner.d.ts +9 -0
- package/dist/elements/helper/CssCombiner.js +147 -7
- package/dist/elements/helper/CssCombiner.js.map +1 -1
- package/dist/elements/helper/Helper.js +3 -1
- package/dist/elements/helper/Helper.js.map +1 -1
- package/dist/elements/helper/LayoutHelper.js +44 -4
- package/dist/elements/helper/LayoutHelper.js.map +1 -1
- package/dist/elements/helper/PopupHelper.d.ts +1 -0
- package/dist/elements/helper/PopupHelper.js +51 -0
- package/dist/elements/helper/PopupHelper.js.map +1 -0
- package/dist/elements/helper/SelectionHelper.d.ts +2 -2
- package/dist/elements/helper/SelectionHelper.js +22 -9
- package/dist/elements/helper/SelectionHelper.js.map +1 -1
- package/dist/elements/helper/contextMenu/ContextMenu.js +11 -1
- package/dist/elements/helper/contextMenu/ContextMenu.js.map +1 -1
- package/dist/elements/helper/contextMenu/IContextMenuItem.d.ts +2 -1
- package/dist/elements/helper/contextMenu/IContextMenuItem.js.map +1 -1
- package/dist/elements/services/DefaultServiceBootstrap.js +4 -3
- package/dist/elements/services/DefaultServiceBootstrap.js.map +1 -1
- package/dist/elements/services/copyPasteService/CopyPasteAsJsonService.d.ts +3 -0
- package/dist/elements/services/copyPasteService/CopyPasteAsJsonService.js +41 -27
- package/dist/elements/services/copyPasteService/CopyPasteAsJsonService.js.map +1 -1
- package/dist/elements/services/copyPasteService/CopyPasteService.js +1 -1
- package/dist/elements/services/copyPasteService/CopyPasteService.js.map +1 -1
- package/dist/elements/services/copyPasteService/PasteFormatSnapshot.d.ts +18 -0
- package/dist/elements/services/copyPasteService/PasteFormatSnapshot.js +87 -0
- package/dist/elements/services/copyPasteService/PasteFormatSnapshot.js.map +1 -0
- package/dist/elements/services/htmlWriterService/FormatingHtmlWriterService.d.ts +4 -2
- package/dist/elements/services/htmlWriterService/FormatingHtmlWriterService.js +29 -9
- package/dist/elements/services/htmlWriterService/FormatingHtmlWriterService.js.map +1 -1
- package/dist/elements/services/htmlWriterService/HtmlWriterService.d.ts +6 -2
- package/dist/elements/services/htmlWriterService/HtmlWriterService.js +48 -22
- package/dist/elements/services/htmlWriterService/HtmlWriterService.js.map +1 -1
- package/dist/elements/services/propertiesService/IEditorTypesService.d.ts +1 -1
- package/dist/elements/widgets/designerView/DomConverter.d.ts +1 -1
- package/dist/elements/widgets/designerView/designerCanvas.js +2 -2
- package/dist/elements/widgets/designerView/designerCanvas.js.map +1 -1
- package/dist/elements/widgets/designerView/extensions/EditText/EditTextExtension.d.ts +30 -2
- package/dist/elements/widgets/designerView/extensions/EditText/EditTextExtension.js +390 -9
- package/dist/elements/widgets/designerView/extensions/EditText/EditTextExtension.js.map +1 -1
- package/dist/elements/widgets/designerView/extensions/contextMenu/PasteFormatContextMenu.d.ts +11 -0
- package/dist/elements/widgets/designerView/extensions/contextMenu/PasteFormatContextMenu.js +62 -0
- package/dist/elements/widgets/designerView/extensions/contextMenu/PasteFormatContextMenu.js.map +1 -0
- package/dist/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtension.d.ts +20 -0
- package/dist/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtension.js +412 -19
- package/dist/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtension.js.map +1 -1
- package/dist/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtensionProvider.d.ts +1 -0
- package/dist/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtensionProvider.js +16 -0
- package/dist/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtensionProvider.js.map +1 -1
- package/dist/elements/widgets/designerView/tools/DrawPathTool.d.ts +8 -5
- package/dist/elements/widgets/designerView/tools/DrawPathTool.js +50 -36
- package/dist/elements/widgets/designerView/tools/DrawPathTool.js.map +1 -1
- package/dist/elements/widgets/designerView/tools/ITool.d.ts +1 -1
- package/dist/index-min.js +13 -6
- package/dist/index-min.js.map +4 -4
- package/dist/index.d.ts +17 -2
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/tests/CssCombiner.test.ts +340 -0
- package/tests/PasteFormatSnapshot.test.ts +92 -0
|
@@ -9,6 +9,7 @@ export class UnifiedGeometryExtension extends AbstractExtension {
|
|
|
9
9
|
_reader;
|
|
10
10
|
_dragState = null;
|
|
11
11
|
_options;
|
|
12
|
+
_overlayStructureSignature = null;
|
|
12
13
|
_eventCleanups = [];
|
|
13
14
|
/** Map from 'anchor-{index}', 'cp1-{index}', 'cp2-{index}' to overlay circle */
|
|
14
15
|
_overlayCircles = new Map();
|
|
@@ -20,18 +21,31 @@ export class UnifiedGeometryExtension extends AbstractExtension {
|
|
|
20
21
|
this._options = options ?? {};
|
|
21
22
|
}
|
|
22
23
|
extend() {
|
|
23
|
-
this.
|
|
24
|
-
if (!this._reader)
|
|
25
|
-
return;
|
|
26
|
-
this._geometry = this._reader.read(this.extendedItem.element);
|
|
27
|
-
this._drawOverlays();
|
|
24
|
+
this.refresh();
|
|
28
25
|
}
|
|
29
26
|
refresh() {
|
|
30
|
-
this.
|
|
31
|
-
|
|
27
|
+
if (!this._reader) {
|
|
28
|
+
this._cleanupAll();
|
|
32
29
|
return;
|
|
30
|
+
}
|
|
33
31
|
this._geometry = this._reader.read(this.extendedItem.element);
|
|
34
|
-
this.
|
|
32
|
+
if (!this._geometry) {
|
|
33
|
+
this._cleanupAll();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const structureSignature = this._getOverlayStructureSignature(this._geometry);
|
|
37
|
+
const shouldRebuild = this._overlayStructureSignature !== structureSignature || this._hasOverlayStructureMismatch(this._geometry);
|
|
38
|
+
if (shouldRebuild) {
|
|
39
|
+
this._cleanupAll();
|
|
40
|
+
this._overlayStructureSignature = structureSignature;
|
|
41
|
+
this._drawOverlays();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (!this._valuesHaveChanges(...this._getRefreshValues(this._geometry))) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this._updateOverlayAppearance();
|
|
48
|
+
this._updateOverlayPositions();
|
|
35
49
|
}
|
|
36
50
|
dispose() {
|
|
37
51
|
this._cleanupAll();
|
|
@@ -46,20 +60,138 @@ export class UnifiedGeometryExtension extends AbstractExtension {
|
|
|
46
60
|
catch { /* ignore */ }
|
|
47
61
|
}
|
|
48
62
|
this._eventCleanups = [];
|
|
63
|
+
this._overlayStructureSignature = null;
|
|
49
64
|
this._overlayCircles.clear();
|
|
50
65
|
this._overlayLines = [];
|
|
51
66
|
this._removeAllOverlays();
|
|
52
67
|
}
|
|
68
|
+
_getOverlayStructureSignature(geometry) {
|
|
69
|
+
return geometry.segments.map(segment => `${segment.type}:${segment.cp1 ? 1 : 0}:${segment.cp2 ? 1 : 0}`).join('|');
|
|
70
|
+
}
|
|
71
|
+
_hasOverlayStructureMismatch(geometry) {
|
|
72
|
+
let expectedLines = 0;
|
|
73
|
+
let expectedCircles = 0;
|
|
74
|
+
for (let i = 0; i < geometry.segments.length; i++) {
|
|
75
|
+
const segment = geometry.segments[i];
|
|
76
|
+
if (segment.type === SegmentType.Close) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (this._shouldRenderAnchorHandle(i, segment, geometry)) {
|
|
80
|
+
expectedCircles++;
|
|
81
|
+
}
|
|
82
|
+
if (segment.cp1) {
|
|
83
|
+
expectedCircles++;
|
|
84
|
+
expectedLines++;
|
|
85
|
+
if (segment.type === SegmentType.QuadraticBezier) {
|
|
86
|
+
expectedLines++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (segment.cp2) {
|
|
90
|
+
expectedCircles++;
|
|
91
|
+
expectedLines++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return this._overlayCircles.size !== expectedCircles || this._overlayLines.length !== expectedLines;
|
|
95
|
+
}
|
|
96
|
+
_getRefreshValues(geometry) {
|
|
97
|
+
const transformedBoxQuad = this.extendedItem.element.getBoxQuads({ relativeTo: this.designerCanvas.canvas })[0];
|
|
98
|
+
const values = [
|
|
99
|
+
this.designerCanvas.zoomFactor,
|
|
100
|
+
this.designerCanvas.scaleFactor,
|
|
101
|
+
this._overlayStructureSignature ?? ''
|
|
102
|
+
];
|
|
103
|
+
if (transformedBoxQuad) {
|
|
104
|
+
values.push(transformedBoxQuad.p1.x, transformedBoxQuad.p1.y, transformedBoxQuad.p2.x, transformedBoxQuad.p2.y, transformedBoxQuad.p3.x, transformedBoxQuad.p3.y, transformedBoxQuad.p4.x, transformedBoxQuad.p4.y);
|
|
105
|
+
}
|
|
106
|
+
for (const segment of geometry.segments) {
|
|
107
|
+
values.push(segment.type, segment.point.x, segment.point.y);
|
|
108
|
+
if (segment.cp1) {
|
|
109
|
+
values.push(segment.cp1.x, segment.cp1.y);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
values.push('cp1:none');
|
|
113
|
+
}
|
|
114
|
+
if (segment.cp2) {
|
|
115
|
+
values.push(segment.cp2.x, segment.cp2.y);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
values.push('cp2:none');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return values;
|
|
122
|
+
}
|
|
53
123
|
/** Convert an SVG-element-local coordinate to overlay (canvas) coordinate
|
|
54
124
|
* using the getBoxQuads polyfill convertPointFromNode */
|
|
55
125
|
_toOverlay(p) {
|
|
56
|
-
const
|
|
126
|
+
const transform = this._getSvgOverlayTransform();
|
|
127
|
+
if (transform && transform.bbox.width !== 0 && transform.bbox.height !== 0) {
|
|
128
|
+
return this._mapSvgPointToOverlay(p, transform);
|
|
129
|
+
}
|
|
130
|
+
const fallbackLocalPoint = this._toElementReferenceBoxPoint(p);
|
|
131
|
+
const tp = this.designerCanvas.canvas.convertPointFromNode(fallbackLocalPoint, this.extendedItem.element);
|
|
57
132
|
return { x: tp.x, y: tp.y };
|
|
58
133
|
}
|
|
59
134
|
/** Convert overlay (canvas) coordinate back to SVG-element-local coordinate */
|
|
60
135
|
_fromOverlay(p) {
|
|
136
|
+
const transform = this._getSvgOverlayTransform();
|
|
137
|
+
if (transform && transform.bbox.width !== 0 && transform.bbox.height !== 0) {
|
|
138
|
+
return this._mapOverlayPointToSvg(p, transform);
|
|
139
|
+
}
|
|
61
140
|
const tp = this.extendedItem.element.convertPointFromNode({ x: p.x, y: p.y }, this.designerCanvas.canvas);
|
|
62
|
-
return { x: tp.x, y: tp.y };
|
|
141
|
+
return this._fromElementReferenceBoxPoint({ x: tp.x, y: tp.y });
|
|
142
|
+
}
|
|
143
|
+
_getSvgOverlayTransform() {
|
|
144
|
+
const element = this.extendedItem.element;
|
|
145
|
+
if (!(element instanceof SVGGraphicsElement) || element instanceof SVGSVGElement) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const quad = element.getBoxQuads({ relativeTo: this.designerCanvas.canvas })[0];
|
|
149
|
+
if (!quad) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
return { bbox: element.getBBox(), quad };
|
|
153
|
+
}
|
|
154
|
+
_mapSvgPointToOverlay(p, transform) {
|
|
155
|
+
const { bbox, quad } = transform;
|
|
156
|
+
const u = (p.x - bbox.x) / bbox.width;
|
|
157
|
+
const v = (p.y - bbox.y) / bbox.height;
|
|
158
|
+
return {
|
|
159
|
+
x: quad.p1.x + (quad.p2.x - quad.p1.x) * u + (quad.p4.x - quad.p1.x) * v,
|
|
160
|
+
y: quad.p1.y + (quad.p2.y - quad.p1.y) * u + (quad.p4.y - quad.p1.y) * v,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
_mapOverlayPointToSvg(p, transform) {
|
|
164
|
+
const { bbox, quad } = transform;
|
|
165
|
+
const basisX = { x: quad.p2.x - quad.p1.x, y: quad.p2.y - quad.p1.y };
|
|
166
|
+
const basisY = { x: quad.p4.x - quad.p1.x, y: quad.p4.y - quad.p1.y };
|
|
167
|
+
const dx = p.x - quad.p1.x;
|
|
168
|
+
const dy = p.y - quad.p1.y;
|
|
169
|
+
const determinant = basisX.x * basisY.y - basisX.y * basisY.x;
|
|
170
|
+
if (Math.abs(determinant) < 1e-10) {
|
|
171
|
+
return this._fromElementReferenceBoxPoint(this.extendedItem.element.convertPointFromNode({ x: p.x, y: p.y }, this.designerCanvas.canvas));
|
|
172
|
+
}
|
|
173
|
+
const u = (dx * basisY.y - dy * basisY.x) / determinant;
|
|
174
|
+
const v = (dy * basisX.x - dx * basisX.y) / determinant;
|
|
175
|
+
return {
|
|
176
|
+
x: bbox.x + u * bbox.width,
|
|
177
|
+
y: bbox.y + v * bbox.height,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
_toElementReferenceBoxPoint(p) {
|
|
181
|
+
const bbox = this._getSvgGeometryBBoxOffset();
|
|
182
|
+
return { x: p.x - bbox.x, y: p.y - bbox.y };
|
|
183
|
+
}
|
|
184
|
+
_fromElementReferenceBoxPoint(p) {
|
|
185
|
+
const bbox = this._getSvgGeometryBBoxOffset();
|
|
186
|
+
return { x: p.x + bbox.x, y: p.y + bbox.y };
|
|
187
|
+
}
|
|
188
|
+
_getSvgGeometryBBoxOffset() {
|
|
189
|
+
const element = this.extendedItem.element;
|
|
190
|
+
if (!(element instanceof SVGGraphicsElement) || element instanceof SVGSVGElement) {
|
|
191
|
+
return { x: 0, y: 0 };
|
|
192
|
+
}
|
|
193
|
+
const bbox = element.getBBox();
|
|
194
|
+
return { x: bbox.x, y: bbox.y };
|
|
63
195
|
}
|
|
64
196
|
_drawOverlays() {
|
|
65
197
|
if (!this._geometry)
|
|
@@ -77,7 +209,11 @@ export class UnifiedGeometryExtension extends AbstractExtension {
|
|
|
77
209
|
if (prevAnchor) {
|
|
78
210
|
const prevOverlay = this._toOverlay(prevAnchor);
|
|
79
211
|
const line = this._drawLine(prevOverlay.x, prevOverlay.y, cp1Overlay.x, cp1Overlay.y, 'svg-control-line');
|
|
80
|
-
|
|
212
|
+
this._overlayLines.push(line);
|
|
213
|
+
}
|
|
214
|
+
if (seg.type === SegmentType.QuadraticBezier) {
|
|
215
|
+
const anchorOverlay = this._toOverlay(seg.point);
|
|
216
|
+
const line = this._drawLine(anchorOverlay.x, anchorOverlay.y, cp1Overlay.x, cp1Overlay.y, 'svg-control-line');
|
|
81
217
|
this._overlayLines.push(line);
|
|
82
218
|
}
|
|
83
219
|
this._drawControlCircle(cp1Overlay, i, 'cp1');
|
|
@@ -86,15 +222,31 @@ export class UnifiedGeometryExtension extends AbstractExtension {
|
|
|
86
222
|
const cp2Overlay = this._toOverlay(seg.cp2);
|
|
87
223
|
const anchorOverlay = this._toOverlay(seg.point);
|
|
88
224
|
const line = this._drawLine(anchorOverlay.x, anchorOverlay.y, cp2Overlay.x, cp2Overlay.y, 'svg-control-line');
|
|
89
|
-
line.style.strokeDasharray = (4 / this.designerCanvas.scaleFactor).toString();
|
|
90
225
|
this._overlayLines.push(line);
|
|
91
226
|
this._drawControlCircle(cp2Overlay, i, 'cp2');
|
|
92
227
|
}
|
|
93
228
|
// Draw the anchor point
|
|
94
|
-
|
|
95
|
-
|
|
229
|
+
if (this._shouldRenderAnchorHandle(i, seg, this._geometry)) {
|
|
230
|
+
const overlayPt = this._toOverlay(seg.point);
|
|
231
|
+
this._drawAnchorCircle(overlayPt, i, seg);
|
|
232
|
+
}
|
|
96
233
|
prevAnchor = seg.point;
|
|
97
234
|
}
|
|
235
|
+
this._updateOverlayAppearance();
|
|
236
|
+
}
|
|
237
|
+
_updateOverlayAppearance() {
|
|
238
|
+
const anchorRadius = 5 / this.designerCanvas.scaleFactor;
|
|
239
|
+
const controlRadius = 4 / this.designerCanvas.scaleFactor;
|
|
240
|
+
const strokeWidth = (1 / this.designerCanvas.zoomFactor).toString();
|
|
241
|
+
const dashArray = (4 / this.designerCanvas.scaleFactor).toString();
|
|
242
|
+
for (const [key, circle] of this._overlayCircles) {
|
|
243
|
+
circle.setAttribute('r', (key.startsWith('anchor-') ? anchorRadius : controlRadius).toString());
|
|
244
|
+
circle.style.strokeWidth = strokeWidth;
|
|
245
|
+
}
|
|
246
|
+
for (const line of this._overlayLines) {
|
|
247
|
+
line.style.strokeDasharray = dashArray;
|
|
248
|
+
line.style.strokeWidth = strokeWidth;
|
|
249
|
+
}
|
|
98
250
|
}
|
|
99
251
|
_addTrackedListener(el, event, handler) {
|
|
100
252
|
el.addEventListener(event, handler);
|
|
@@ -202,6 +354,15 @@ export class UnifiedGeometryExtension extends AbstractExtension {
|
|
|
202
354
|
const newLocal = this._fromOverlay(newOverlay);
|
|
203
355
|
const seg = this._geometry.segments[segIndex];
|
|
204
356
|
if (handleType === 'anchor') {
|
|
357
|
+
if (this.extendedItem.element instanceof SVGRectElement && this._dragRectCorner(segIndex, newLocal)) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (this.extendedItem.element instanceof SVGCircleElement && this._dragCircleCardinal(segIndex, newLocal)) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (this.extendedItem.element instanceof SVGEllipseElement && this._dragEllipseCardinal(segIndex, newLocal)) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
205
366
|
seg.point.x = newLocal.x;
|
|
206
367
|
seg.point.y = newLocal.y;
|
|
207
368
|
}
|
|
@@ -214,6 +375,172 @@ export class UnifiedGeometryExtension extends AbstractExtension {
|
|
|
214
375
|
seg.cp2.y = newLocal.y;
|
|
215
376
|
}
|
|
216
377
|
}
|
|
378
|
+
_dragRectCorner(segIndex, newPoint) {
|
|
379
|
+
if (!this._geometry || !this._dragState?.originalSegments)
|
|
380
|
+
return false;
|
|
381
|
+
if (this._geometry.segments.length < 4 || segIndex < 0 || segIndex > 3)
|
|
382
|
+
return false;
|
|
383
|
+
const originalSegments = this._dragState.originalSegments;
|
|
384
|
+
switch (segIndex) {
|
|
385
|
+
case 0:
|
|
386
|
+
this._setRectCornerPoints(newPoint, { x: originalSegments[2].point.x, y: newPoint.y }, { ...originalSegments[2].point }, { x: newPoint.x, y: originalSegments[2].point.y });
|
|
387
|
+
break;
|
|
388
|
+
case 1:
|
|
389
|
+
this._setRectCornerPoints({ x: originalSegments[3].point.x, y: newPoint.y }, newPoint, { x: newPoint.x, y: originalSegments[3].point.y }, { ...originalSegments[3].point });
|
|
390
|
+
break;
|
|
391
|
+
case 2:
|
|
392
|
+
this._setRectCornerPoints({ ...originalSegments[0].point }, { x: newPoint.x, y: originalSegments[0].point.y }, newPoint, { x: originalSegments[0].point.x, y: newPoint.y });
|
|
393
|
+
break;
|
|
394
|
+
case 3:
|
|
395
|
+
this._setRectCornerPoints({ x: newPoint.x, y: originalSegments[1].point.y }, { ...originalSegments[1].point }, { x: originalSegments[1].point.x, y: newPoint.y }, newPoint);
|
|
396
|
+
break;
|
|
397
|
+
default:
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
const closeSegment = this._geometry.segments[4];
|
|
401
|
+
if (closeSegment?.type === SegmentType.Close) {
|
|
402
|
+
closeSegment.point = { ...this._geometry.segments[0].point };
|
|
403
|
+
}
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
_setRectCornerPoints(topLeft, topRight, bottomRight, bottomLeft) {
|
|
407
|
+
this._geometry.segments[0].point = { ...topLeft };
|
|
408
|
+
this._geometry.segments[1].point = { ...topRight };
|
|
409
|
+
this._geometry.segments[2].point = { ...bottomRight };
|
|
410
|
+
this._geometry.segments[3].point = { ...bottomLeft };
|
|
411
|
+
}
|
|
412
|
+
_dragCircleCardinal(segIndex, newPoint) {
|
|
413
|
+
if (!this._geometry || !this._dragState?.originalSegments)
|
|
414
|
+
return false;
|
|
415
|
+
const cardinalIndex = this._normalizeCardinalIndex(segIndex);
|
|
416
|
+
if (cardinalIndex === null)
|
|
417
|
+
return false;
|
|
418
|
+
const original = this._dragState.originalSegments;
|
|
419
|
+
const centerX = (original[1].point.x + original[3].point.x) / 2;
|
|
420
|
+
const centerY = (original[0].point.y + original[2].point.y) / 2;
|
|
421
|
+
let cx = centerX;
|
|
422
|
+
let cy = centerY;
|
|
423
|
+
let radius = Math.abs(original[1].point.x - original[3].point.x) / 2;
|
|
424
|
+
switch (cardinalIndex) {
|
|
425
|
+
case 0: {
|
|
426
|
+
const oppositeY = original[2].point.y;
|
|
427
|
+
cy = (newPoint.y + oppositeY) / 2;
|
|
428
|
+
radius = Math.abs(oppositeY - newPoint.y) / 2;
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
case 1: {
|
|
432
|
+
const oppositeX = original[3].point.x;
|
|
433
|
+
cx = (newPoint.x + oppositeX) / 2;
|
|
434
|
+
radius = Math.abs(newPoint.x - oppositeX) / 2;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
case 2: {
|
|
438
|
+
const oppositeY = original[0].point.y;
|
|
439
|
+
cy = (original[0].point.y + newPoint.y) / 2;
|
|
440
|
+
radius = Math.abs(newPoint.y - oppositeY) / 2;
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
case 3: {
|
|
444
|
+
const oppositeX = original[1].point.x;
|
|
445
|
+
cx = (oppositeX + newPoint.x) / 2;
|
|
446
|
+
radius = Math.abs(oppositeX - newPoint.x) / 2;
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
default:
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
this._setCardinalArcPoints(cx, cy, radius, radius);
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
_dragEllipseCardinal(segIndex, newPoint) {
|
|
456
|
+
if (!this._geometry || !this._dragState?.originalSegments)
|
|
457
|
+
return false;
|
|
458
|
+
const cardinalIndex = this._normalizeCardinalIndex(segIndex);
|
|
459
|
+
if (cardinalIndex === null)
|
|
460
|
+
return false;
|
|
461
|
+
const original = this._dragState.originalSegments;
|
|
462
|
+
let cx = (original[1].point.x + original[3].point.x) / 2;
|
|
463
|
+
let cy = (original[0].point.y + original[2].point.y) / 2;
|
|
464
|
+
let rx = Math.abs(original[1].point.x - original[3].point.x) / 2;
|
|
465
|
+
let ry = Math.abs(original[2].point.y - original[0].point.y) / 2;
|
|
466
|
+
switch (cardinalIndex) {
|
|
467
|
+
case 0: {
|
|
468
|
+
const oppositeY = original[2].point.y;
|
|
469
|
+
cy = (newPoint.y + oppositeY) / 2;
|
|
470
|
+
ry = Math.abs(oppositeY - newPoint.y) / 2;
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
case 1: {
|
|
474
|
+
const oppositeX = original[3].point.x;
|
|
475
|
+
cx = (newPoint.x + oppositeX) / 2;
|
|
476
|
+
rx = Math.abs(newPoint.x - oppositeX) / 2;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case 2: {
|
|
480
|
+
const oppositeY = original[0].point.y;
|
|
481
|
+
cy = (oppositeY + newPoint.y) / 2;
|
|
482
|
+
ry = Math.abs(newPoint.y - oppositeY) / 2;
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
case 3: {
|
|
486
|
+
const oppositeX = original[1].point.x;
|
|
487
|
+
cx = (oppositeX + newPoint.x) / 2;
|
|
488
|
+
rx = Math.abs(oppositeX - newPoint.x) / 2;
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
default:
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
this._setCardinalArcPoints(cx, cy, rx, ry);
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
_normalizeCardinalIndex(segIndex) {
|
|
498
|
+
if (segIndex >= 0 && segIndex <= 3) {
|
|
499
|
+
return segIndex;
|
|
500
|
+
}
|
|
501
|
+
if (segIndex === 4) {
|
|
502
|
+
return 0;
|
|
503
|
+
}
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
_setCardinalArcPoints(cx, cy, rx, ry) {
|
|
507
|
+
if (!this._geometry)
|
|
508
|
+
return;
|
|
509
|
+
this._geometry.segments[0].point = { x: cx, y: cy - ry };
|
|
510
|
+
this._geometry.segments[1].point = { x: cx + rx, y: cy };
|
|
511
|
+
this._geometry.segments[2].point = { x: cx, y: cy + ry };
|
|
512
|
+
this._geometry.segments[3].point = { x: cx - rx, y: cy };
|
|
513
|
+
if (this._geometry.segments[1].arc) {
|
|
514
|
+
this._geometry.segments[1].arc.rx = rx;
|
|
515
|
+
this._geometry.segments[1].arc.ry = ry;
|
|
516
|
+
}
|
|
517
|
+
if (this._geometry.segments[2].arc) {
|
|
518
|
+
this._geometry.segments[2].arc.rx = rx;
|
|
519
|
+
this._geometry.segments[2].arc.ry = ry;
|
|
520
|
+
}
|
|
521
|
+
if (this._geometry.segments[3].arc) {
|
|
522
|
+
this._geometry.segments[3].arc.rx = rx;
|
|
523
|
+
this._geometry.segments[3].arc.ry = ry;
|
|
524
|
+
}
|
|
525
|
+
if (this._geometry.segments[4]?.arc) {
|
|
526
|
+
this._geometry.segments[4].point = { x: cx, y: cy - ry };
|
|
527
|
+
this._geometry.segments[4].arc.rx = rx;
|
|
528
|
+
this._geometry.segments[4].arc.ry = ry;
|
|
529
|
+
}
|
|
530
|
+
if (this._geometry.segments[5]?.type === SegmentType.Close) {
|
|
531
|
+
this._geometry.segments[5].point = { x: cx, y: cy - ry };
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
_shouldRenderAnchorHandle(segIndex, seg, geometry) {
|
|
535
|
+
if (!(this.extendedItem.element instanceof SVGCircleElement) && !(this.extendedItem.element instanceof SVGEllipseElement)) {
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
if (segIndex !== 4) {
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
const firstSegment = geometry.segments[0];
|
|
542
|
+
return seg.point.x !== firstSegment?.point.x || seg.point.y !== firstSegment?.point.y;
|
|
543
|
+
}
|
|
217
544
|
_dragWholeShape(dx, dy) {
|
|
218
545
|
if (!this._dragState?.originalSegments || !this._geometry)
|
|
219
546
|
return;
|
|
@@ -264,6 +591,14 @@ export class UnifiedGeometryExtension extends AbstractExtension {
|
|
|
264
591
|
line.setAttribute('x2', cp1Overlay.x.toString());
|
|
265
592
|
line.setAttribute('y2', cp1Overlay.y.toString());
|
|
266
593
|
}
|
|
594
|
+
if (seg.type === SegmentType.QuadraticBezier && lineIdx < this._overlayLines.length) {
|
|
595
|
+
const anchorOverlay = this._toOverlay(seg.point);
|
|
596
|
+
const line = this._overlayLines[lineIdx++];
|
|
597
|
+
line.setAttribute('x1', anchorOverlay.x.toString());
|
|
598
|
+
line.setAttribute('y1', anchorOverlay.y.toString());
|
|
599
|
+
line.setAttribute('x2', cp1Overlay.x.toString());
|
|
600
|
+
line.setAttribute('y2', cp1Overlay.y.toString());
|
|
601
|
+
}
|
|
267
602
|
const cp1Circle = this._overlayCircles.get(`cp1-${i}`);
|
|
268
603
|
if (cp1Circle) {
|
|
269
604
|
cp1Circle.setAttribute('cx', cp1Overlay.x.toString());
|
|
@@ -286,11 +621,13 @@ export class UnifiedGeometryExtension extends AbstractExtension {
|
|
|
286
621
|
cp2Circle.setAttribute('cy', cp2Overlay.y.toString());
|
|
287
622
|
}
|
|
288
623
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
624
|
+
if (this._shouldRenderAnchorHandle(i, seg, this._geometry)) {
|
|
625
|
+
const overlayPt = this._toOverlay(seg.point);
|
|
626
|
+
const anchorCircle = this._overlayCircles.get(`anchor-${i}`);
|
|
627
|
+
if (anchorCircle) {
|
|
628
|
+
anchorCircle.setAttribute('cx', overlayPt.x.toString());
|
|
629
|
+
anchorCircle.setAttribute('cy', overlayPt.y.toString());
|
|
630
|
+
}
|
|
294
631
|
}
|
|
295
632
|
prevAnchor = seg.point;
|
|
296
633
|
}
|
|
@@ -423,6 +760,31 @@ export class UnifiedGeometryExtension extends AbstractExtension {
|
|
|
423
760
|
}
|
|
424
761
|
});
|
|
425
762
|
}
|
|
763
|
+
if (seg.type !== SegmentType.Arc) {
|
|
764
|
+
items.push({
|
|
765
|
+
title: 'Convert to arc', action: () => {
|
|
766
|
+
this._convertToArc(segIndex, false);
|
|
767
|
+
this._applyGeometryToElement();
|
|
768
|
+
this._commitGeometryChange();
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
items.push({
|
|
772
|
+
title: 'Convert to inverted arc', action: () => {
|
|
773
|
+
this._convertToArc(segIndex, true);
|
|
774
|
+
this._applyGeometryToElement();
|
|
775
|
+
this._commitGeometryChange();
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
items.push({
|
|
781
|
+
title: 'Invert arc', action: () => {
|
|
782
|
+
this._invertArc(segIndex);
|
|
783
|
+
this._applyGeometryToElement();
|
|
784
|
+
this._commitGeometryChange();
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
}
|
|
426
788
|
}
|
|
427
789
|
if (items.length > 0) {
|
|
428
790
|
ContextMenu.show(items, event);
|
|
@@ -565,6 +927,37 @@ export class UnifiedGeometryExtension extends AbstractExtension {
|
|
|
565
927
|
}
|
|
566
928
|
delete seg.arc;
|
|
567
929
|
}
|
|
930
|
+
_convertToArc(segIndex, inverted = false) {
|
|
931
|
+
if (!this._geometry)
|
|
932
|
+
return;
|
|
933
|
+
const seg = this._geometry.segments[segIndex];
|
|
934
|
+
const prevPt = this._findPreviousAnchor(segIndex);
|
|
935
|
+
if (!prevPt)
|
|
936
|
+
return;
|
|
937
|
+
const endPt = seg.point;
|
|
938
|
+
const dx = endPt.x - prevPt.x;
|
|
939
|
+
const dy = endPt.y - prevPt.y;
|
|
940
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
941
|
+
const radius = Math.max(1, dist / 2);
|
|
942
|
+
seg.type = SegmentType.Arc;
|
|
943
|
+
seg.arc = {
|
|
944
|
+
rx: radius,
|
|
945
|
+
ry: radius,
|
|
946
|
+
rotation: 0,
|
|
947
|
+
largeArc: false,
|
|
948
|
+
sweep: !inverted,
|
|
949
|
+
};
|
|
950
|
+
delete seg.cp1;
|
|
951
|
+
delete seg.cp2;
|
|
952
|
+
}
|
|
953
|
+
_invertArc(segIndex) {
|
|
954
|
+
if (!this._geometry)
|
|
955
|
+
return;
|
|
956
|
+
const seg = this._geometry.segments[segIndex];
|
|
957
|
+
if (seg.type !== SegmentType.Arc || !seg.arc)
|
|
958
|
+
return;
|
|
959
|
+
seg.arc.sweep = !seg.arc.sweep;
|
|
960
|
+
}
|
|
568
961
|
// -- Bezier evaluation --
|
|
569
962
|
_evalCubicBezier(p0, p1, p2, p3, t) {
|
|
570
963
|
const mt = 1 - t;
|