@design-core/core 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/.eslintrc.json +14 -7
  2. package/.github/workflows/DesignCoreQA.yml +3 -1
  3. package/README.md +11 -4
  4. package/TODO.md +19 -0
  5. package/core/core/core.js +49 -42
  6. package/core/designCore.js +61 -2
  7. package/core/dimensions/alignedDimension.js +142 -0
  8. package/core/dimensions/angularDimension.js +550 -0
  9. package/core/dimensions/baseCircularDimension.js +244 -0
  10. package/core/dimensions/baseDimension.js +558 -0
  11. package/core/dimensions/baseLinearDimension.js +371 -0
  12. package/core/dimensions/diametricDimension.js +82 -0
  13. package/core/dimensions/dimension.js +298 -0
  14. package/core/dimensions/linearDimension.js +29 -0
  15. package/core/dimensions/radialDimension.js +78 -0
  16. package/core/dimensions/rotatedDimension.js +167 -0
  17. package/core/entities/arc.js +314 -60
  18. package/core/entities/arctext.js +642 -0
  19. package/core/entities/basePolyline.js +553 -94
  20. package/core/entities/circle.js +181 -54
  21. package/core/entities/entity.js +154 -74
  22. package/core/entities/hatch.js +807 -0
  23. package/core/entities/insert.js +200 -39
  24. package/core/entities/line.js +196 -134
  25. package/core/entities/lwpolyline.js +21 -44
  26. package/core/entities/point.js +194 -29
  27. package/core/entities/polyline.js +54 -7
  28. package/core/entities/rectangle.js +43 -13
  29. package/core/entities/solid.js +43 -6
  30. package/core/entities/text.js +324 -181
  31. package/core/lib/boundingBox.js +73 -13
  32. package/core/lib/canvas.js +256 -65
  33. package/core/lib/clipboard.js +198 -0
  34. package/core/lib/colour.js +144 -0
  35. package/core/lib/colours.js +367 -301
  36. package/core/lib/commandLine.js +16 -28
  37. package/core/lib/commandManager.js +115 -58
  38. package/core/lib/constants.js +23 -0
  39. package/core/lib/dxf/dxf.js +249 -34
  40. package/core/lib/dxf/dxfFile.js +23 -38
  41. package/core/lib/dxf/dxfIterator.js +17 -5
  42. package/core/lib/dxf/dxfRead.js +23 -7
  43. package/core/lib/dxf/dxfWrite.js +39 -129
  44. package/core/lib/dxf/handleManager.js +97 -0
  45. package/core/lib/dxf/sections/blocks.js +16 -2
  46. package/core/lib/dxf/sections/entities.js +17 -3
  47. package/core/lib/dxf/sections/header.js +10 -1
  48. package/core/lib/dxf/sections/objects.js +36 -0
  49. package/core/lib/dxf/sections/section.js +43 -5
  50. package/core/lib/dxf/sections/tables.js +17 -3
  51. package/core/lib/entityManager.js +178 -0
  52. package/core/lib/fileio.js +14 -4
  53. package/core/lib/headers.js +229 -0
  54. package/core/lib/input.js +46 -0
  55. package/core/lib/inputManager.js +199 -194
  56. package/core/lib/intersect.js +341 -468
  57. package/core/lib/logging.js +25 -1
  58. package/core/lib/matrix.js +6 -8
  59. package/core/lib/mouse.js +24 -79
  60. package/core/lib/patterns.js +690 -0
  61. package/core/lib/promptOptions.js +133 -0
  62. package/core/lib/scene.js +71 -174
  63. package/core/lib/selectionManager.js +74 -48
  64. package/core/lib/selectionWindow.js +24 -10
  65. package/core/lib/settings.js +18 -11
  66. package/core/lib/snapping.js +129 -21
  67. package/core/lib/stateManager.js +288 -0
  68. package/core/lib/strings.js +57 -5
  69. package/core/lib/utils.js +95 -11
  70. package/core/objects/dictionary.js +43 -0
  71. package/core/objects/dictionaryManager.js +80 -0
  72. package/core/properties/dimType.js +117 -0
  73. package/core/properties/flags.js +66 -0
  74. package/core/properties/property.js +26 -0
  75. package/core/properties/propertyManager.js +65 -19
  76. package/core/tables/appId.js +31 -0
  77. package/core/tables/appIdManager.js +44 -0
  78. package/core/tables/block.js +323 -0
  79. package/core/tables/blockManager.js +48 -0
  80. package/core/tables/blockRecord.js +29 -0
  81. package/core/tables/blockRecordManager.js +37 -0
  82. package/core/tables/dimStyle.js +627 -0
  83. package/core/tables/dimStyleManager.js +187 -0
  84. package/core/tables/endblk.js +28 -0
  85. package/core/tables/layer.js +219 -0
  86. package/core/tables/layerManager.js +47 -0
  87. package/core/tables/ltype.js +85 -0
  88. package/core/tables/ltypeManager.js +65 -0
  89. package/core/tables/style.js +145 -0
  90. package/core/tables/styleManager.js +55 -0
  91. package/core/tables/styleManagerBase.js +110 -0
  92. package/core/tables/tableManagerBase.js +354 -0
  93. package/core/tables/ucs.js +28 -0
  94. package/core/tables/ucsManager.js +44 -0
  95. package/core/tables/view.js +28 -0
  96. package/core/tables/viewManager.js +44 -0
  97. package/core/tables/vport.js +70 -0
  98. package/core/tables/vportManager.js +65 -0
  99. package/core/tools/chamfer.js +343 -0
  100. package/core/tools/chamferFilletBase.js +162 -0
  101. package/core/tools/copy.js +68 -40
  102. package/core/tools/copybase.js +65 -0
  103. package/core/tools/copyclip.js +59 -0
  104. package/core/tools/cornerEntity.js +138 -0
  105. package/core/tools/cutclip.js +72 -0
  106. package/core/tools/distance.js +37 -9
  107. package/core/tools/erase.js +43 -13
  108. package/core/tools/explode.js +117 -0
  109. package/core/tools/extend.js +71 -32
  110. package/core/tools/fillet.js +336 -0
  111. package/core/tools/identify.js +33 -6
  112. package/core/tools/matchProp.js +106 -0
  113. package/core/tools/mirror.js +141 -0
  114. package/core/tools/move.js +65 -32
  115. package/core/tools/offset.js +321 -0
  116. package/core/tools/pan.js +54 -0
  117. package/core/tools/pasteclip.js +93 -0
  118. package/core/tools/purge.js +90 -0
  119. package/core/tools/rotate.js +105 -70
  120. package/core/tools/scale.js +166 -0
  121. package/core/tools/tool.js +14 -4
  122. package/core/tools/trim.js +71 -32
  123. package/core/tools/zoom.js +152 -0
  124. package/package.json +3 -305
  125. package/test/dimensions/alignedDimension.test.js +211 -0
  126. package/test/dimensions/angularDimension.test.js +243 -0
  127. package/test/dimensions/baseDimension.test.js +239 -0
  128. package/test/dimensions/diametricDimension.test.js +203 -0
  129. package/test/dimensions/dimension.test.js +102 -0
  130. package/test/dimensions/radialDimension.test.js +203 -0
  131. package/test/dimensions/rotatedDimension.test.js +224 -0
  132. package/test/entities/arc.test.js +463 -41
  133. package/test/entities/arctext.test.js +329 -0
  134. package/test/entities/basePolyline.test.js +459 -10
  135. package/test/entities/circle.test.js +312 -7
  136. package/test/entities/entity.test.js +28 -9
  137. package/test/entities/hatch.test.js +606 -0
  138. package/test/entities/insert.test.js +90 -0
  139. package/test/entities/line.test.js +182 -6
  140. package/test/entities/point.test.js +375 -37
  141. package/test/entities/solid.test.js +156 -0
  142. package/test/entities/text.test.js +533 -7
  143. package/test/integration/exportIntegration.reference.dxf +1706 -0
  144. package/test/integration/exportIntegration.test.js +186 -0
  145. package/test/integration/importExportIntegration.test.js +126 -0
  146. package/test/integration/intersectionIntegration.test.js +880 -0
  147. package/test/integration/userInputIntegration.reference.dxf +1410 -0
  148. package/test/integration/userInputIntegration.test.js +269 -0
  149. package/test/lib/boundingBox.test.js +26 -2
  150. package/test/lib/canvas.test.js +613 -5
  151. package/test/lib/clipboard.test.js +70 -0
  152. package/test/lib/colour.test.js +114 -0
  153. package/test/lib/colours.test.js +63 -18
  154. package/test/lib/commandLine.test.js +159 -22
  155. package/test/lib/commandManager.test.js +4 -4
  156. package/test/lib/constants.test.js +9 -0
  157. package/test/lib/dxf/dxfFile.test.js +6 -2
  158. package/test/lib/dxf/dxfIterator.test.js +1 -1
  159. package/test/lib/dxf/dxfWrite.test.js +173 -0
  160. package/test/lib/dxf/handleManager.test.js +137 -0
  161. package/test/lib/dxf/sections/blocks.test.js +5 -5
  162. package/test/lib/dxf/sections/entities.test.js +4 -4
  163. package/test/lib/dxf/sections/header.test.js +18 -4
  164. package/test/lib/dxf/sections/objects.test.js +132 -0
  165. package/test/lib/dxf/sections/section.test.js +29 -29
  166. package/test/lib/dxf/sections/tables.test.js +3 -3
  167. package/test/lib/entityManager.test.js +163 -0
  168. package/test/lib/headers.test.js +233 -0
  169. package/test/lib/inputManager.test.js +1144 -5
  170. package/test/lib/intersect.test.js +454 -331
  171. package/test/lib/logging.test.js +1 -1
  172. package/test/lib/matrix.test.js +1 -1
  173. package/test/lib/mouse.test.js +327 -4
  174. package/test/lib/patterns.test.js +105 -0
  175. package/test/lib/promptOptions.test.js +179 -0
  176. package/test/lib/scene.test.js +16 -52
  177. package/test/lib/selectionManager.test.js +17 -5
  178. package/test/lib/settings.test.js +111 -5
  179. package/test/lib/snapping.test.js +142 -4
  180. package/test/lib/stateManager.test.js +155 -0
  181. package/test/lib/utils.test.js +81 -1
  182. package/test/objects/dictionary.test.js +81 -0
  183. package/test/objects/dictionaryManager.test.js +34 -0
  184. package/test/properties/dimType.test.js +62 -0
  185. package/test/properties/flags.test.js +164 -0
  186. package/test/properties/property.test.js +13 -0
  187. package/test/properties/propertyManager.test.js +10 -11
  188. package/test/tables/appId.test.js +46 -0
  189. package/test/tables/appIdManager.test.js +72 -0
  190. package/test/tables/block.test.js +157 -0
  191. package/test/tables/blockManager.test.js +52 -0
  192. package/test/tables/blockRecord.test.js +39 -0
  193. package/test/tables/blockRecordManager.test.js +60 -0
  194. package/test/tables/dimstyle.test.js +72 -0
  195. package/test/tables/endblk.test.js +30 -0
  196. package/test/tables/layer.test.js +95 -0
  197. package/test/tables/layerManager.test.js +172 -0
  198. package/test/{styles → tables}/ltype.test.js +7 -7
  199. package/test/{styles → tables}/ltypeManager.test.js +2 -2
  200. package/test/tables/style.test.js +44 -0
  201. package/test/tables/stylemanagerbase.test.js +220 -0
  202. package/test/tables/tableManagerBase.test.js +194 -0
  203. package/test/tables/ucs.test.js +37 -0
  204. package/test/tables/ucsManager.test.js +48 -0
  205. package/test/tables/view.test.js +37 -0
  206. package/test/tables/viewManager.test.js +48 -0
  207. package/test/tables/vport.test.js +117 -0
  208. package/test/tables/vportManager.test.js +54 -0
  209. package/test/test-helpers/test-helpers.js +66 -0
  210. package/test/tools/chamfer.test.js +970 -0
  211. package/test/tools/chamferFilletBase.test.js +446 -0
  212. package/test/tools/copy.test.js +22 -14
  213. package/test/tools/copybase.test.js +93 -0
  214. package/test/tools/copyclip.test.js +92 -0
  215. package/test/tools/cornerEntity.test.js +201 -0
  216. package/test/tools/cutclip.test.js +94 -0
  217. package/test/tools/distance.test.js +140 -18
  218. package/test/tools/erase.test.js +12 -12
  219. package/test/tools/explode.test.js +45 -0
  220. package/test/tools/extend.test.js +326 -44
  221. package/test/tools/fillet.test.js +831 -0
  222. package/test/tools/identify.test.js +115 -16
  223. package/test/tools/matchProp.test.js +84 -0
  224. package/test/tools/mirror.test.js +475 -0
  225. package/test/tools/move.test.js +75 -19
  226. package/test/tools/offset.test.js +748 -0
  227. package/test/tools/pan.test.js +72 -0
  228. package/test/tools/pasteclip.test.js +100 -0
  229. package/test/tools/purge.test.js +89 -0
  230. package/test/tools/rotate.test.js +89 -23
  231. package/test/tools/scale.test.js +529 -0
  232. package/test/tools/trim.test.js +419 -84
  233. package/test/tools/zoom.test.js +504 -0
  234. package/core/entities/alignedDimension.js +0 -279
  235. package/core/entities/angularDimension.js +0 -194
  236. package/core/entities/baseDimension.js +0 -227
  237. package/core/entities/block.js +0 -247
  238. package/core/entities/diametricDimension.js +0 -133
  239. package/core/entities/dimension.js +0 -202
  240. package/core/entities/radialDimension.js +0 -126
  241. package/core/styles/dimStyle.js +0 -719
  242. package/core/styles/dimStyleManager.js +0 -47
  243. package/core/styles/layer.js +0 -133
  244. package/core/styles/layerManager.js +0 -40
  245. package/core/styles/ltype.js +0 -86
  246. package/core/styles/ltypeManager.js +0 -50
  247. package/core/styles/style.js +0 -158
  248. package/core/styles/styleManager.js +0 -43
  249. package/core/styles/styleManagerBase.js +0 -299
  250. package/test/entities/block.test.js +0 -22
  251. package/test/styles/dimstyle.test.js +0 -19
  252. package/test/styles/layerManager.test.js +0 -150
  253. package/test/styles/style.test.js +0 -33
  254. package/test/styles/stylemanagerbase.test.js +0 -219
@@ -0,0 +1,880 @@
1
+ import { Point } from '../../core/entities/point.js';
2
+ import { Line } from '../../core/entities/line.js';
3
+ import { Circle } from '../../core/entities/circle.js';
4
+ import { Arc } from '../../core/entities/arc.js';
5
+ import { Polyline } from '../../core/entities/polyline.js';
6
+ import { Lwpolyline } from '../../core/entities/lwpolyline.js';
7
+ import { Solid } from '../../core/entities/solid.js';
8
+ import { Text } from '../../core/entities/text.js';
9
+ import { ArcAlignedText } from '../../core/entities/arctext.js';
10
+ import { Hatch } from '../../core/entities/hatch.js';
11
+ import { AlignedDimension } from '../../core/dimensions/alignedDimension.js';
12
+ import { RotatedDimension } from '../../core/dimensions/rotatedDimension.js';
13
+ import { DiametricDimension } from '../../core/dimensions/diametricDimension.js';
14
+ import { RadialDimension } from '../../core/dimensions/radialDimension.js';
15
+ import { AngularDimension } from '../../core/dimensions/angularDimension.js';
16
+ import { Intersection } from '../../core/lib/intersect.js';
17
+ import { Core } from '../../core/core/core.js';
18
+
19
+ beforeAll(() => {
20
+ new Core();
21
+ });
22
+
23
+ /**
24
+ * Create selection bounds from two points
25
+ * @param {Point} p1
26
+ * @param {Point} p2
27
+ * @return {Object} {min: Point, max: Point}
28
+ */
29
+ function selectionExtremes(p1, p2) {
30
+ return {
31
+ min: new Point(Math.min(p1.x, p2.x), Math.min(p1.y, p2.y)),
32
+ max: new Point(Math.max(p1.x, p2.x), Math.max(p1.y, p2.y)),
33
+ };
34
+ }
35
+
36
+ // ========================================================
37
+ // Entity toPolylinePoints format tests
38
+ // ========================================================
39
+ describe('Entity toPolylinePoints', () => {
40
+ test('Line.toPolylinePoints returns 2 points with bulge 0', () => {
41
+ const line = new Line({ points: [new Point(0, 0), new Point(10, 5)] });
42
+ const pts = line.toPolylinePoints();
43
+ expect(pts.length).toBe(2);
44
+ expect(pts[0].x).toBe(0);
45
+ expect(pts[0].y).toBe(0);
46
+ expect(pts[0].bulge).toBe(0);
47
+ expect(pts[1].x).toBe(10);
48
+ expect(pts[1].y).toBe(5);
49
+ expect(pts[1].bulge).toBe(0);
50
+ });
51
+
52
+ test('Circle.toPolylinePoints returns 3 points with bulge 1', () => {
53
+ const circle = new Circle({ points: [new Point(10, 10), new Point(15, 10)] });
54
+ const pts = circle.toPolylinePoints();
55
+ expect(pts.length).toBe(3);
56
+ // Two semicircular arcs
57
+ expect(pts[0].bulge).toBe(1);
58
+ expect(pts[1].bulge).toBe(1);
59
+ // Closure point
60
+ expect(pts[2].x).toBe(pts[0].x);
61
+ expect(pts[2].y).toBe(pts[0].y);
62
+ });
63
+
64
+ test('Arc.toPolylinePoints returns 2 points with calculated bulge', () => {
65
+ // CCW arc from (10,0) to (0,10) centred at origin, radius 10
66
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
67
+ const pts = arc.toPolylinePoints();
68
+ expect(pts.length).toBe(2);
69
+ expect(pts[0].bulge).not.toBe(0);
70
+ expect(pts[1].bulge).toBe(0);
71
+ });
72
+
73
+ test('Polyline.toPolylinePoints returns points array', () => {
74
+ const poly = new Polyline({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10)] });
75
+ const pts = poly.toPolylinePoints();
76
+ expect(pts.length).toBe(3);
77
+ expect(pts[0].x).toBe(0);
78
+ expect(pts[2].x).toBe(10);
79
+ expect(pts[2].y).toBe(10);
80
+ });
81
+
82
+ test('Lwpolyline.toPolylinePoints returns points array', () => {
83
+ const lwpoly = new Lwpolyline({ points: [new Point(0, 0), new Point(5, 5), new Point(10, 0)] });
84
+ const pts = lwpoly.toPolylinePoints();
85
+ expect(pts.length).toBe(3);
86
+ });
87
+
88
+ test('Solid.toPolylinePoints returns closed polygon', () => {
89
+ const solid = new Solid({ points: [new Point(0, 0), new Point(10, 0), new Point(5, 10)] });
90
+ const pts = solid.toPolylinePoints();
91
+ // 3 points + closing point
92
+ expect(pts.length).toBe(4);
93
+ expect(pts[3].x).toBe(pts[0].x);
94
+ expect(pts[3].y).toBe(pts[0].y);
95
+ });
96
+
97
+ test('Solid.toPolylinePoints with 4 points returns 5 points', () => {
98
+ const solid = new Solid({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
99
+ const pts = solid.toPolylinePoints();
100
+ expect(pts.length).toBe(5);
101
+ expect(pts[4].x).toBe(pts[0].x);
102
+ expect(pts[4].y).toBe(pts[0].y);
103
+ });
104
+
105
+ test('Text.toPolylinePoints returns closed rectangle (5 points)', () => {
106
+ const text = new Text({ points: [new Point(0, 0)] });
107
+ text.boundingRect = { width: 10, height: 5 };
108
+ const pts = text.toPolylinePoints();
109
+ expect(pts.length).toBe(5);
110
+ // Closed polygon: first and last point match
111
+ expect(pts[4].x).toBe(pts[0].x);
112
+ expect(pts[4].y).toBe(pts[0].y);
113
+ });
114
+
115
+ test('ArcAlignedText.toPolylinePoints returns character positions', () => {
116
+ const arcText = new ArcAlignedText({
117
+ points: [new Point(0, 0)],
118
+ string: 'AB',
119
+ radius: 20,
120
+ height: 2.5,
121
+ startAngle: 0,
122
+ endAngle: 180,
123
+ });
124
+ const pts = arcText.toPolylinePoints();
125
+ expect(pts.length).toBe(2);
126
+ // Points should be at character positions along the arc
127
+ pts.forEach((pt) => {
128
+ expect(pt).toBeInstanceOf(Point);
129
+ });
130
+ });
131
+
132
+ test('ArcAlignedText with empty string returns empty points', () => {
133
+ const arcText = new ArcAlignedText({
134
+ points: [new Point(0, 0)],
135
+ string: '',
136
+ radius: 20,
137
+ });
138
+ const pts = arcText.toPolylinePoints();
139
+ expect(pts.length).toBe(0);
140
+ });
141
+
142
+ test('Polyline with bulge segments toPolylinePoints correctly', () => {
143
+ // Polyline with an arc segment (bulge=1 is a semicircle)
144
+ const poly = new Polyline({ points: [new Point(0, 0, 1), new Point(10, 0)] });
145
+ const pts = poly.toPolylinePoints();
146
+ expect(pts.length).toBe(2);
147
+ expect(pts[0].bulge).toBe(1);
148
+ expect(pts[1].bulge).toBe(0);
149
+ });
150
+ });
151
+
152
+ // ========================================================
153
+ // Entity touched (selection) tests - via intersectPolylinePolyline
154
+ // ========================================================
155
+ describe('Entity.touched - selection rectangle intersection', () => {
156
+ test('Line touching selection rectangle', () => {
157
+ const line = new Line({ points: [new Point(0, 0), new Point(20, 20)] });
158
+ // Selection rectangle crossing the line
159
+ const sE = selectionExtremes(new Point(5, 5), new Point(15, 15));
160
+ expect(line.touched(sE)).toBe(true);
161
+ });
162
+
163
+ test('Line not touching selection rectangle', () => {
164
+ const line = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
165
+ // Selection rectangle above the line
166
+ const sE = selectionExtremes(new Point(0, 5), new Point(10, 15));
167
+ expect(line.touched(sE)).toBe(false);
168
+ });
169
+
170
+ test('Line partially crossing selection rectangle edge', () => {
171
+ // Line crossing only one edge of the rectangle
172
+ const line = new Line({ points: [new Point(-10, 5), new Point(5, 5)] });
173
+ const sE = selectionExtremes(new Point(0, 0), new Point(10, 10));
174
+ expect(line.touched(sE)).toBe(true);
175
+ });
176
+
177
+ test('Circle touching selection rectangle', () => {
178
+ const circle = new Circle({ points: [new Point(10, 10), new Point(15, 10)] }); // radius=5
179
+ // Rectangle crossing the circle
180
+ const sE = selectionExtremes(new Point(12, 0), new Point(18, 20));
181
+ expect(circle.touched(sE)).toBe(true);
182
+ });
183
+
184
+ test('Circle not touching selection rectangle', () => {
185
+ const circle = new Circle({ points: [new Point(10, 10), new Point(15, 10)] }); // radius=5
186
+ // Rectangle far from circle
187
+ const sE = selectionExtremes(new Point(50, 50), new Point(60, 60));
188
+ expect(circle.touched(sE)).toBe(false);
189
+ });
190
+
191
+ test('Circle surrounding selection rectangle (no edge crossing)', () => {
192
+ const circle = new Circle({ points: [new Point(0, 0), new Point(100, 0)] }); // radius=100
193
+ // Tiny rectangle inside circle - no edge crossings
194
+ const sE = selectionExtremes(new Point(-1, -1), new Point(1, 1));
195
+ expect(circle.touched(sE)).toBe(false);
196
+ });
197
+
198
+ test('Arc touching selection rectangle', () => {
199
+ // CCW arc from (10,0) to (0,10) centred at origin, radius 10
200
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
201
+ // Rectangle crossing the arc
202
+ const sE = selectionExtremes(new Point(4, 4), new Point(12, 12));
203
+ expect(arc.touched(sE)).toBe(true);
204
+ });
205
+
206
+ test('Arc not touching selection rectangle', () => {
207
+ // CCW arc from (10,0) to (0,10) centred at origin (positive quadrant)
208
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
209
+ // Rectangle in negative quadrant
210
+ const sE = selectionExtremes(new Point(-20, -20), new Point(-10, -10));
211
+ expect(arc.touched(sE)).toBe(false);
212
+ });
213
+
214
+ test('Polyline touching selection rectangle', () => {
215
+ const poly = new Polyline({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20)] });
216
+ const sE = selectionExtremes(new Point(15, -5), new Point(25, 5));
217
+ expect(poly.touched(sE)).toBe(true);
218
+ });
219
+
220
+ test('Polyline not touching selection rectangle', () => {
221
+ const poly = new Polyline({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10)] });
222
+ const sE = selectionExtremes(new Point(50, 50), new Point(60, 60));
223
+ expect(poly.touched(sE)).toBe(false);
224
+ });
225
+
226
+ test('Lwpolyline touching selection rectangle', () => {
227
+ const lwpoly = new Lwpolyline({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20)] });
228
+ const sE = selectionExtremes(new Point(15, -5), new Point(25, 5));
229
+ expect(lwpoly.touched(sE)).toBe(true);
230
+ });
231
+
232
+ test('Lwpolyline not touching selection rectangle', () => {
233
+ const lwpoly = new Lwpolyline({ points: [new Point(0, 0), new Point(10, 0)] });
234
+ const sE = selectionExtremes(new Point(0, 5), new Point(10, 15));
235
+ expect(lwpoly.touched(sE)).toBe(false);
236
+ });
237
+
238
+ test('Solid touching selection rectangle', () => {
239
+ const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(10, 20)] });
240
+ const sE = selectionExtremes(new Point(5, -5), new Point(15, 5));
241
+ expect(solid.touched(sE)).toBe(true);
242
+ });
243
+
244
+ test('Solid not touching selection rectangle', () => {
245
+ const solid = new Solid({ points: [new Point(0, 0), new Point(10, 0), new Point(5, 10)] });
246
+ const sE = selectionExtremes(new Point(50, 50), new Point(60, 60));
247
+ expect(solid.touched(sE)).toBe(false);
248
+ });
249
+
250
+ test('Solid 4-point touching selection rectangle', () => {
251
+ const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20), new Point(0, 20)] });
252
+ // Rectangle crossing the top edge
253
+ const sE = selectionExtremes(new Point(5, 15), new Point(15, 25));
254
+ expect(solid.touched(sE)).toBe(true);
255
+ });
256
+
257
+ test('Text touching selection rectangle', () => {
258
+ const text = new Text({ points: [new Point(0, 0)] });
259
+ text.boundingRect = { width: 20, height: 10 };
260
+ // Rectangle crossing through the text frame
261
+ const sE = selectionExtremes(new Point(-5, -5), new Point(5, 5));
262
+ expect(text.touched(sE)).toBe(true);
263
+ });
264
+
265
+ test('Text not touching selection rectangle', () => {
266
+ const text = new Text({ points: [new Point(0, 0)] });
267
+ text.boundingRect = { width: 10, height: 5 };
268
+ // Rectangle far from text
269
+ const sE = selectionExtremes(new Point(50, 50), new Point(60, 60));
270
+ expect(text.touched(sE)).toBe(false);
271
+ });
272
+
273
+ test('ArcAlignedText touching selection rectangle', () => {
274
+ const arcText = new ArcAlignedText({
275
+ points: [new Point(0, 0)],
276
+ string: 'Hello',
277
+ radius: 20,
278
+ height: 2.5,
279
+ startAngle: 0,
280
+ endAngle: 180,
281
+ });
282
+ // Get the character positions to determine where to place selection rectangle
283
+ const pts = arcText.toPolylinePoints();
284
+ if (pts.length > 0) {
285
+ const charPos = pts[0];
286
+ const sE = selectionExtremes(
287
+ new Point(charPos.x - 5, charPos.y - 5),
288
+ new Point(charPos.x + 5, charPos.y + 5),
289
+ );
290
+ expect(arcText.touched(sE)).toBe(true);
291
+ }
292
+ });
293
+
294
+ test('ArcAlignedText not touching selection rectangle', () => {
295
+ const arcText = new ArcAlignedText({
296
+ points: [new Point(0, 0)],
297
+ string: 'Test',
298
+ radius: 20,
299
+ height: 2.5,
300
+ startAngle: 0,
301
+ endAngle: 180,
302
+ });
303
+ // Rectangle far away
304
+ const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
305
+ expect(arcText.touched(sE)).toBe(false);
306
+ });
307
+
308
+ test('Polyline with arc segment touching selection rectangle', () => {
309
+ // Semicircular polyline arc from (0,0) to (0,10) bulging right
310
+ const poly = new Polyline({ points: [new Point(0, 0, 1), new Point(0, 10)] });
311
+ // Rectangle to the right of the midpoint where the arc bulges
312
+ const sE = selectionExtremes(new Point(3, 3), new Point(7, 7));
313
+ expect(poly.touched(sE)).toBe(true);
314
+ });
315
+
316
+ test('Closed polyline touching selection rectangle', () => {
317
+ // Closed square polyline
318
+ const poly = new Polyline({
319
+ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0)],
320
+ });
321
+ // Rectangle crossing the right edge
322
+ const sE = selectionExtremes(new Point(8, 3), new Point(12, 7));
323
+ expect(poly.touched(sE)).toBe(true);
324
+ });
325
+ });
326
+
327
+ // ========================================================
328
+ // Entity-to-entity intersections via intersectPolylinePolyline
329
+ // ========================================================
330
+ describe('Entity-to-entity intersection via intersectPolylinePolyline', () => {
331
+ test('Line vs Line - crossing', () => {
332
+ const line1 = new Line({ points: [new Point(-10, 0), new Point(10, 0)] });
333
+ const line2 = new Line({ points: [new Point(0, -10), new Point(0, 10)] });
334
+ const result = Intersection.intersectPolylinePolyline(line1.toPolylinePoints(), line2.toPolylinePoints());
335
+ expect(result.status).toBe('Intersection');
336
+ expect(result.points.length).toBe(1);
337
+ expect(result.points[0].x).toBeCloseTo(0);
338
+ expect(result.points[0].y).toBeCloseTo(0);
339
+ });
340
+
341
+ test('Line vs Line - no intersection', () => {
342
+ const line1 = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
343
+ const line2 = new Line({ points: [new Point(0, 5), new Point(10, 5)] });
344
+ const result = Intersection.intersectPolylinePolyline(line1.toPolylinePoints(), line2.toPolylinePoints());
345
+ expect(result.status).toBe('None');
346
+ expect(result.points.length).toBe(0);
347
+ });
348
+
349
+ test('Line vs Circle', () => {
350
+ const line = new Line({ points: [new Point(-10, 0), new Point(10, 0)] });
351
+ const circle = new Circle({ points: [new Point(0, 0), new Point(5, 0)] }); // radius=5
352
+ const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), circle.toPolylinePoints());
353
+ expect(result.status).toBe('Intersection');
354
+ expect(result.points.length).toBe(2);
355
+ });
356
+
357
+ test('Line vs Arc - intersection', () => {
358
+ // Horizontal line through arc
359
+ const line = new Line({ points: [new Point(-20, 5), new Point(20, 5)] });
360
+ // CCW arc from (10,0) to (0,10) centred at origin, radius 10
361
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
362
+ const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), arc.toPolylinePoints());
363
+ expect(result.status).toBe('Intersection');
364
+ expect(result.points.length).toBeGreaterThanOrEqual(1);
365
+ });
366
+
367
+ test('Line vs Polyline - multiple intersections', () => {
368
+ const line = new Line({ points: [new Point(5, -5), new Point(5, 25)] });
369
+ const poly = new Polyline({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
370
+ const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), poly.toPolylinePoints());
371
+ expect(result.status).toBe('Intersection');
372
+ expect(result.points.length).toBe(2);
373
+ // Line at x=5 crosses bottom edge (y=0) and top edge (y=10) - but poly is not closed
374
+ // Actually crosses bottom (0,0)-(10,0) and the third segment (10,10)-(0,10)
375
+ expect(result.points[0].x).toBeCloseTo(5);
376
+ });
377
+
378
+ test('Line vs Solid', () => {
379
+ const line = new Line({ points: [new Point(-5, 5), new Point(25, 5)] });
380
+ const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20), new Point(0, 20)] });
381
+ const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), solid.toPolylinePoints());
382
+ expect(result.status).toBe('Intersection');
383
+ // Line crosses left and right edges of the closed solid
384
+ expect(result.points.length).toBe(2);
385
+ });
386
+
387
+ test('Circle vs Circle - overlapping', () => {
388
+ const circle1 = new Circle({ points: [new Point(0, 0), new Point(5, 0)] }); // radius=5
389
+ const circle2 = new Circle({ points: [new Point(5, 0), new Point(10, 0)] }); // radius=5
390
+ const result = Intersection.intersectPolylinePolyline(circle1.toPolylinePoints(), circle2.toPolylinePoints());
391
+ expect(result.status).toBe('Intersection');
392
+ expect(result.points.length).toBe(2);
393
+ });
394
+
395
+ test('Circle vs Circle - no intersection', () => {
396
+ const circle1 = new Circle({ points: [new Point(0, 0), new Point(5, 0)] }); // radius=5
397
+ const circle2 = new Circle({ points: [new Point(50, 50), new Point(55, 50)] }); // radius=5
398
+ const result = Intersection.intersectPolylinePolyline(circle1.toPolylinePoints(), circle2.toPolylinePoints());
399
+ expect(result.status).toBe('None');
400
+ });
401
+
402
+ test('Circle vs Arc', () => {
403
+ const circle = new Circle({ points: [new Point(10, 0), new Point(15, 0)] }); // radius=5 at (10,0)
404
+ // CCW arc from (10,0) to (0,10) centred at origin, radius 10
405
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
406
+ const result = Intersection.intersectPolylinePolyline(circle.toPolylinePoints(), arc.toPolylinePoints());
407
+ expect(result.status).toBe('Intersection');
408
+ });
409
+
410
+ test('Arc vs Arc - intersecting', () => {
411
+ // Two overlapping arcs: arc1 upper semicircle, arc2 shifted
412
+ const arc1 = new Arc({ points: [new Point(0, 0), new Point(-10, 0), new Point(10, 0)] }); // upper semicircle r=10
413
+ const arc2 = new Arc({ points: [new Point(5, 0), new Point(-5, 0), new Point(15, 0)] }); // upper semicircle r=10 shifted right
414
+ const result = Intersection.intersectPolylinePolyline(arc1.toPolylinePoints(), arc2.toPolylinePoints());
415
+ expect(result.status).toBe('Intersection');
416
+ expect(result.points.length).toBeGreaterThanOrEqual(1);
417
+ });
418
+
419
+ test('Polyline vs Polyline - crossing', () => {
420
+ const poly1 = new Polyline({ points: [new Point(-10, 0), new Point(10, 0)] });
421
+ const poly2 = new Polyline({ points: [new Point(0, -10), new Point(0, 10)] });
422
+ const result = Intersection.intersectPolylinePolyline(poly1.toPolylinePoints(), poly2.toPolylinePoints());
423
+ expect(result.status).toBe('Intersection');
424
+ expect(result.points.length).toBe(1);
425
+ expect(result.points[0].x).toBeCloseTo(0);
426
+ expect(result.points[0].y).toBeCloseTo(0);
427
+ });
428
+
429
+ test('Polyline vs Polyline - multi-segment', () => {
430
+ // V-shaped polyline vs horizontal line
431
+ const poly1 = new Polyline({ points: [new Point(0, 10), new Point(10, 0), new Point(20, 10)] });
432
+ const poly2 = new Polyline({ points: [new Point(-5, 5), new Point(25, 5)] });
433
+ const result = Intersection.intersectPolylinePolyline(poly1.toPolylinePoints(), poly2.toPolylinePoints());
434
+ expect(result.status).toBe('Intersection');
435
+ // Crosses both segments of poly1
436
+ expect(result.points.length).toBe(2);
437
+ });
438
+
439
+ test('Lwpolyline vs Line', () => {
440
+ const lwpoly = new Lwpolyline({ points: [new Point(-10, 0), new Point(10, 0)] });
441
+ const line = new Line({ points: [new Point(0, -10), new Point(0, 10)] });
442
+ const result = Intersection.intersectPolylinePolyline(lwpoly.toPolylinePoints(), line.toPolylinePoints());
443
+ expect(result.status).toBe('Intersection');
444
+ expect(result.points.length).toBe(1);
445
+ });
446
+
447
+ test('Lwpolyline vs Polyline', () => {
448
+ const lwpoly = new Lwpolyline({ points: [new Point(-10, 0), new Point(10, 0)] });
449
+ const poly = new Polyline({ points: [new Point(0, -10), new Point(0, 10)] });
450
+ const result = Intersection.intersectPolylinePolyline(lwpoly.toPolylinePoints(), poly.toPolylinePoints());
451
+ expect(result.status).toBe('Intersection');
452
+ expect(result.points.length).toBe(1);
453
+ });
454
+
455
+ test('Lwpolyline vs Lwpolyline', () => {
456
+ const lwpoly1 = new Lwpolyline({ points: [new Point(-10, 0), new Point(10, 0)] });
457
+ const lwpoly2 = new Lwpolyline({ points: [new Point(0, -10), new Point(0, 10)] });
458
+ const result = Intersection.intersectPolylinePolyline(lwpoly1.toPolylinePoints(), lwpoly2.toPolylinePoints());
459
+ expect(result.status).toBe('Intersection');
460
+ expect(result.points.length).toBe(1);
461
+ });
462
+
463
+ test('Polyline with bulge vs Line', () => {
464
+ // Semicircular arc polyline bulging right
465
+ const poly = new Polyline({ points: [new Point(0, 0, 1), new Point(0, 10)] });
466
+ // Vertical line through the middle of the arc
467
+ const line = new Line({ points: [new Point(3, -5), new Point(3, 15)] });
468
+ const result = Intersection.intersectPolylinePolyline(poly.toPolylinePoints(), line.toPolylinePoints());
469
+ expect(result.status).toBe('Intersection');
470
+ // Line should cross the arc in two places
471
+ expect(result.points.length).toBe(2);
472
+ });
473
+
474
+ test('Solid vs Line - crossing closed polygon', () => {
475
+ // Triangle solid
476
+ const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(10, 20)] });
477
+ // Horizontal line crossing two edges
478
+ const line = new Line({ points: [new Point(-5, 10), new Point(25, 10)] });
479
+ const result = Intersection.intersectPolylinePolyline(solid.toPolylinePoints(), line.toPolylinePoints());
480
+ expect(result.status).toBe('Intersection');
481
+ expect(result.points.length).toBe(2);
482
+ });
483
+
484
+ test('Solid vs Polyline', () => {
485
+ const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20), new Point(0, 20)] });
486
+ const poly = new Polyline({ points: [new Point(10, -5), new Point(10, 25)] });
487
+ const result = Intersection.intersectPolylinePolyline(solid.toPolylinePoints(), poly.toPolylinePoints());
488
+ expect(result.status).toBe('Intersection');
489
+ expect(result.points.length).toBe(2);
490
+ });
491
+
492
+ test('Solid vs Circle', () => {
493
+ // Square solid with a circle crossing through an edge
494
+ const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20), new Point(0, 20)] });
495
+ const circle = new Circle({ points: [new Point(0, 10), new Point(5, 10)] }); // radius=5 at left edge
496
+ const result = Intersection.intersectPolylinePolyline(solid.toPolylinePoints(), circle.toPolylinePoints());
497
+ expect(result.status).toBe('Intersection');
498
+ expect(result.points.length).toBeGreaterThanOrEqual(2);
499
+ });
500
+
501
+ test('Text vs Line', () => {
502
+ const text = new Text({ points: [new Point(0, 0)] });
503
+ text.boundingRect = { width: 20, height: 10 };
504
+ // Line crossing through the text frame
505
+ const line = new Line({ points: [new Point(-5, 5), new Point(25, 5)] });
506
+ const result = Intersection.intersectPolylinePolyline(text.toPolylinePoints(), line.toPolylinePoints());
507
+ expect(result.status).toBe('Intersection');
508
+ // Line enters and exits the text frame
509
+ expect(result.points.length).toBe(2);
510
+ });
511
+
512
+ test('Text vs Circle', () => {
513
+ const text = new Text({ points: [new Point(0, 0)] });
514
+ text.boundingRect = { width: 20, height: 10 };
515
+ const circle = new Circle({ points: [new Point(0, 5), new Point(5, 5)] }); // radius=5 at left edge
516
+ const result = Intersection.intersectPolylinePolyline(text.toPolylinePoints(), circle.toPolylinePoints());
517
+ expect(result.status).toBe('Intersection');
518
+ });
519
+ });
520
+
521
+ // ========================================================
522
+ // Cross-type intersection combinations
523
+ // ========================================================
524
+ describe('Cross-type entity intersections', () => {
525
+ test('Line vs Solid vs Circle - chain verification', () => {
526
+ // Verify that intersections work consistently across entity types
527
+ const line = new Line({ points: [new Point(0, 0), new Point(20, 0)] });
528
+ const solid = new Solid({ points: [new Point(5, -5), new Point(15, -5), new Point(15, 5), new Point(5, 5)] });
529
+
530
+ // Line crosses left and right edges of solid
531
+ const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), solid.toPolylinePoints());
532
+ expect(result.status).toBe('Intersection');
533
+ expect(result.points.length).toBe(2);
534
+ expect(result.points[0].x).toBeCloseTo(15);
535
+ expect(result.points[0].y).toBeCloseTo(0);
536
+ expect(result.points[1].x).toBeCloseTo(5);
537
+ expect(result.points[1].y).toBeCloseTo(0);
538
+ });
539
+
540
+ test('Polyline with bulge vs Circle', () => {
541
+ // Polyline with arc segment
542
+ const poly = new Polyline({ points: [new Point(0, -5, 1), new Point(0, 5)] });
543
+ // Circle overlapping the arc
544
+ const circle = new Circle({ points: [new Point(5, 0), new Point(10, 0)] }); // radius=5
545
+ const result = Intersection.intersectPolylinePolyline(poly.toPolylinePoints(), circle.toPolylinePoints());
546
+ expect(result.status).toBe('Intersection');
547
+ });
548
+
549
+ test('Closed polyline vs Closed polyline', () => {
550
+ // Two overlapping squares
551
+ const poly1 = new Polyline({
552
+ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0)],
553
+ });
554
+ const poly2 = new Polyline({
555
+ points: [new Point(5, 5), new Point(15, 5), new Point(15, 15), new Point(5, 15), new Point(5, 5)],
556
+ });
557
+ const result = Intersection.intersectPolylinePolyline(poly1.toPolylinePoints(), poly2.toPolylinePoints());
558
+ expect(result.status).toBe('Intersection');
559
+ // Two overlapping squares intersect at multiple points
560
+ expect(result.points.length).toBeGreaterThanOrEqual(2);
561
+ });
562
+
563
+ test('Arc vs Solid - crossing triangle', () => {
564
+ const arc = new Arc({ points: [new Point(10, 0), new Point(20, 0), new Point(0, 0)] }); // semicircle top
565
+ const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(10, 15)] });
566
+ const result = Intersection.intersectPolylinePolyline(arc.toPolylinePoints(), solid.toPolylinePoints());
567
+ expect(result.status).toBe('Intersection');
568
+ });
569
+
570
+ test('Line vs Lwpolyline', () => {
571
+ const line = new Line({ points: [new Point(0, 5), new Point(20, 5)] });
572
+ const lwpoly = new Lwpolyline({ points: [new Point(10, 0), new Point(10, 10)] });
573
+ const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), lwpoly.toPolylinePoints());
574
+ expect(result.status).toBe('Intersection');
575
+ expect(result.points.length).toBe(1);
576
+ expect(result.points[0].x).toBeCloseTo(10);
577
+ expect(result.points[0].y).toBeCloseTo(5);
578
+ });
579
+
580
+ test('Circle vs Lwpolyline', () => {
581
+ const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] }); // radius=10
582
+ const lwpoly = new Lwpolyline({ points: [new Point(0, -15), new Point(0, 15)] });
583
+ const result = Intersection.intersectPolylinePolyline(circle.toPolylinePoints(), lwpoly.toPolylinePoints());
584
+ expect(result.status).toBe('Intersection');
585
+ expect(result.points.length).toBe(2);
586
+ });
587
+
588
+ test('Arc vs Lwpolyline', () => {
589
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
590
+ const lwpoly = new Lwpolyline({ points: [new Point(0, 0), new Point(15, 15)] });
591
+ const result = Intersection.intersectPolylinePolyline(arc.toPolylinePoints(), lwpoly.toPolylinePoints());
592
+ expect(result.status).toBe('Intersection');
593
+ });
594
+ });
595
+
596
+ // ========================================================
597
+ // Dimension touched tests
598
+ // ========================================================
599
+ describe('Dimension touched tests', () => {
600
+ test('AlignedDimension.touched delegates to block', () => {
601
+ const pt13 = new Point(0, 0, 0, 13); // first definition point
602
+ const pt14 = new Point(100, 0, 0, 14); // second definition point
603
+ const pt11 = new Point(50, 20, 0, 11); // text position
604
+ const pt10 = new Point(100, 20, 0, 10); // dimension line location
605
+ const dim = new AlignedDimension({
606
+ points: [pt13, pt14, pt11, pt10],
607
+ });
608
+ dim.refresh();
609
+
610
+ // Selection rectangle around the dimension text area
611
+ const sE = selectionExtremes(new Point(40, 15), new Point(60, 25));
612
+ const touched = dim.touched(sE);
613
+ expect(typeof touched).toBe('boolean');
614
+ });
615
+
616
+ test('AlignedDimension.touched returns false when far away', () => {
617
+ const pt13 = new Point(0, 0, 0, 13);
618
+ const pt14 = new Point(100, 0, 0, 14);
619
+ const pt11 = new Point(50, 20, 0, 11);
620
+ const pt10 = new Point(100, 20, 0, 10);
621
+ const dim = new AlignedDimension({
622
+ points: [pt13, pt14, pt11, pt10],
623
+ });
624
+ dim.refresh();
625
+
626
+ // Selection rectangle far from dimension
627
+ const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
628
+ expect(dim.touched(sE)).toBe(false);
629
+ });
630
+
631
+ test('RotatedDimension.touched delegates to block', () => {
632
+ const pt13 = new Point(0, 0, 0, 13);
633
+ const pt14 = new Point(100, 0, 0, 14);
634
+ const pt11 = new Point(50, 20, 0, 11);
635
+ const pt10 = new Point(100, 20, 0, 10);
636
+ const dim = new RotatedDimension({
637
+ points: [pt13, pt14, pt11, pt10],
638
+ });
639
+ dim.refresh();
640
+
641
+ const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
642
+ expect(dim.touched(sE)).toBe(false);
643
+ });
644
+
645
+ test('DiametricDimension.touched delegates to block', () => {
646
+ const dim = new DiametricDimension({
647
+ points: [new Point(0, 0), new Point(100, 0)],
648
+ });
649
+ dim.refresh();
650
+
651
+ const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
652
+ expect(dim.touched(sE)).toBe(false);
653
+ });
654
+
655
+ test('RadialDimension.touched delegates to block', () => {
656
+ const dim = new RadialDimension({
657
+ points: [new Point(0, 0), new Point(100, 0)],
658
+ });
659
+ dim.refresh();
660
+
661
+ const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
662
+ expect(dim.touched(sE)).toBe(false);
663
+ });
664
+
665
+ test('AngularDimension.touched delegates to block', () => {
666
+ const pt13 = new Point(0, 0, 0, 13); // line 1 start
667
+ const pt14 = new Point(100, 0, 0, 14); // line 1 end
668
+ const pt15 = new Point(0, 0, 0, 15); // line 2 start
669
+ const pt10 = new Point(0, 100, 0, 10); // line 2 end
670
+ const pt16 = new Point(50, 50, 0, 16); // dimension line position
671
+ const pt11 = new Point(50, 50, 0, 11); // text position
672
+ const dim = new AngularDimension({
673
+ points: [pt13, pt14, pt15, pt10, pt16, pt11],
674
+ });
675
+ dim.refresh();
676
+
677
+ const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
678
+ expect(dim.touched(sE)).toBe(false);
679
+ });
680
+
681
+ test('AlignedDimension.touched uses block.touched not toPolylinePoints', () => {
682
+ // Dimensions delegate touched() to block.touched() which iterates block items.
683
+ const pt13 = new Point(0, 0, 0, 13);
684
+ const pt14 = new Point(100, 0, 0, 14);
685
+ const pt11 = new Point(50, 20, 0, 11);
686
+ const pt10 = new Point(100, 20, 0, 10);
687
+ const dim = new AlignedDimension({
688
+ points: [pt13, pt14, pt11, pt10],
689
+ });
690
+ dim.refresh();
691
+
692
+ // touched() works via block delegation - returns boolean
693
+ const sE = selectionExtremes(new Point(-10, -10), new Point(110, 30));
694
+ expect(dim.touched(sE)).toBe(false);
695
+ });
696
+ });
697
+
698
+ // ========================================================
699
+ // Hatch touched tests
700
+ // ========================================================
701
+ describe('Hatch touched tests', () => {
702
+ test('Hatch.touched delegates to child entities', () => {
703
+ const hatch = new Hatch();
704
+ // Add a polyline as child entity
705
+ const boundary = new Polyline({
706
+ points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100), new Point(0, 0)],
707
+ });
708
+ hatch.childEntities.push(boundary);
709
+
710
+ // Selection rectangle crossing the boundary
711
+ const sE = selectionExtremes(new Point(-5, 50), new Point(5, 60));
712
+ expect(hatch.touched(sE)).toBe(true);
713
+ });
714
+
715
+ test('Hatch.touched returns false when no child entity touched', () => {
716
+ const hatch = new Hatch();
717
+ const boundary = new Polyline({
718
+ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0)],
719
+ });
720
+ hatch.childEntities.push(boundary);
721
+
722
+ // Selection rectangle far from hatch
723
+ const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
724
+ expect(hatch.touched(sE)).toBe(false);
725
+ });
726
+
727
+ test('Hatch.touched with multiple child entities', () => {
728
+ const hatch = new Hatch();
729
+ const boundary1 = new Polyline({
730
+ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0)],
731
+ });
732
+ const boundary2 = new Polyline({
733
+ points: [new Point(50, 50), new Point(60, 50), new Point(60, 60), new Point(50, 60), new Point(50, 50)],
734
+ });
735
+ hatch.childEntities.push(boundary1);
736
+ hatch.childEntities.push(boundary2);
737
+
738
+ // Touch only the second boundary
739
+ const sE = selectionExtremes(new Point(48, 55), new Point(52, 58));
740
+ expect(hatch.touched(sE)).toBe(true);
741
+ });
742
+ });
743
+
744
+ // ========================================================
745
+ // Edge cases
746
+ // ========================================================
747
+ describe('Intersection edge cases', () => {
748
+ test('Zero-length line toPolylinePoints', () => {
749
+ const line = new Line({ points: [new Point(5, 5), new Point(5, 5)] });
750
+ const pts = line.toPolylinePoints();
751
+ expect(pts.length).toBe(2);
752
+ });
753
+
754
+ test('Large line touched by large rectangle', () => {
755
+ // Line that crosses the selection rectangle edge
756
+ const line = new Line({ points: [new Point(-200, 0), new Point(200, 0)] });
757
+ const sE = selectionExtremes(new Point(-100, -100), new Point(100, 100));
758
+ expect(line.touched(sE)).toBe(true);
759
+ });
760
+
761
+ test('Entity exactly on rectangle edge', () => {
762
+ const line = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
763
+ // Rectangle edge coincides with the line
764
+ const sE = selectionExtremes(new Point(0, 0), new Point(10, 10));
765
+ // Coincident lines detected
766
+ expect(line.touched(sE)).toBe(true);
767
+ });
768
+
769
+ test('Polyline with single point returns valid toPolylinePoints', () => {
770
+ const poly = new Polyline({ points: [new Point(5, 5)] });
771
+ const pts = poly.toPolylinePoints();
772
+ expect(pts.length).toBe(1);
773
+ });
774
+
775
+ test('Coincident lines intersection', () => {
776
+ const line1 = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
777
+ const line2 = new Line({ points: [new Point(2, 0), new Point(8, 0)] });
778
+ const result = Intersection.intersectPolylinePolyline(line1.toPolylinePoints(), line2.toPolylinePoints());
779
+ // Coincident lines have special status
780
+ expect(result.points.length).toBe(1);
781
+ });
782
+
783
+ test('Perpendicular lines at endpoint', () => {
784
+ const line1 = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
785
+ const line2 = new Line({ points: [new Point(10, 0), new Point(10, 10)] });
786
+ const result = Intersection.intersectPolylinePolyline(line1.toPolylinePoints(), line2.toPolylinePoints());
787
+ expect(result.status).toBe('Intersection');
788
+ expect(result.points[0].x).toBeCloseTo(10);
789
+ expect(result.points[0].y).toBeCloseTo(0);
790
+ });
791
+
792
+ test('Diagonal line through circle centre', () => {
793
+ const line = new Line({ points: [new Point(-20, -20), new Point(20, 20)] });
794
+ const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] }); // radius=10
795
+ const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), circle.toPolylinePoints());
796
+ expect(result.status).toBe('Intersection');
797
+ expect(result.points.length).toBe(2);
798
+ // Points should be equidistant from centre
799
+ const dist1 = Math.sqrt(result.points[0].x ** 2 + result.points[0].y ** 2);
800
+ const dist2 = Math.sqrt(result.points[1].x ** 2 + result.points[1].y ** 2);
801
+ expect(dist1).toBeCloseTo(10);
802
+ expect(dist2).toBeCloseTo(10);
803
+ });
804
+
805
+ test('Two concentric circles - no intersection', () => {
806
+ const circle1 = new Circle({ points: [new Point(0, 0), new Point(5, 0)] }); // radius=5
807
+ const circle2 = new Circle({ points: [new Point(0, 0), new Point(10, 0)] }); // radius=10
808
+ const result = Intersection.intersectPolylinePolyline(circle1.toPolylinePoints(), circle2.toPolylinePoints());
809
+ expect(result.status).toBe('None');
810
+ });
811
+
812
+ test('Polyline segment with negative bulge', () => {
813
+ // Negative bulge = clockwise arc
814
+ const poly = new Polyline({ points: [new Point(0, 0, -1), new Point(0, 10)] });
815
+ const line = new Line({ points: [new Point(-10, 5), new Point(10, 5)] });
816
+ const result = Intersection.intersectPolylinePolyline(poly.toPolylinePoints(), line.toPolylinePoints());
817
+ expect(result.status).toBe('Intersection');
818
+ // CW arc bulges to the left, line crosses through
819
+ expect(result.points.length).toBeGreaterThanOrEqual(1);
820
+ expect(result.points[0].x).toBeCloseTo(-5);
821
+ expect(result.points[0].y).toBeCloseTo(5);
822
+ });
823
+
824
+ test('Polyline segment with small bulge (shallow arc)', () => {
825
+ // Small bulge creates a shallow arc
826
+ const poly = new Polyline({ points: [new Point(0, 0, 0.1), new Point(10, 0)] });
827
+ const line = new Line({ points: [new Point(5, -5), new Point(5, 5)] });
828
+ const result = Intersection.intersectPolylinePolyline(poly.toPolylinePoints(), line.toPolylinePoints());
829
+ expect(result.status).toBe('Intersection');
830
+ expect(result.points.length).toBe(1);
831
+ });
832
+ });
833
+
834
+ // ========================================================
835
+ // Extend flag tests
836
+ // ========================================================
837
+ describe('Intersection with extend flag', () => {
838
+ test('intersectSegmentSegment with extend finds projected intersection', () => {
839
+ // line1 (boundary) is long enough to contain intersection
840
+ // line2 (selected) doesn't reach line1 without extend
841
+ const result1 = Intersection.intersectSegmentSegment(
842
+ new Point(10, -10), new Point(10, 10),
843
+ new Point(0, 0), new Point(5, 0),
844
+ false,
845
+ );
846
+ expect(result1.status).toBe('None');
847
+ // With extend - line2 projects beyond its endpoint
848
+ const result2 = Intersection.intersectSegmentSegment(
849
+ new Point(10, -10), new Point(10, 10),
850
+ new Point(0, 0), new Point(5, 0),
851
+ false, true,
852
+ );
853
+ expect(result2.status).toBe('Perpendicular');
854
+ expect(result2.points[0].x).toBeCloseTo(10);
855
+ expect(result2.points[0].y).toBeCloseTo(0);
856
+ });
857
+
858
+ test('intersectPolylinePolyline with extend', () => {
859
+ // Polyline (boundary) is long, line (selected) doesn't reach it
860
+ const poly = [new Point(10, -10), new Point(10, 10)];
861
+ const line = [new Point(0, 0), new Point(5, 0)];
862
+ const result = Intersection.intersectPolylinePolyline(poly, line, true);
863
+ expect(result.status).toBe('Intersection');
864
+ expect(result.points[0].x).toBeCloseTo(10);
865
+ });
866
+
867
+ test('intersectPolylinePolyline arc with extend finds full circle intersection', () => {
868
+ // Arc from (5,0) to (-5,0) CCW, bulge=1 -> upper semicircle centred at (0,0), r=5
869
+ const arcPolyline = [new Point(5, 0, 1), new Point(-5, 0)];
870
+ // Line below the arc - doesn't intersect the arc without extend
871
+ const polyline = [new Point(0, -10), new Point(0, -3)];
872
+ const result1 = Intersection.intersectPolylinePolyline(polyline, arcPolyline, false);
873
+ expect(result1.status).toBe('None');
874
+ // With extend - uses full circle, finds intersection at (0,-5)
875
+ const result2 = Intersection.intersectPolylinePolyline(polyline, arcPolyline, true);
876
+ expect(result2.status).toBe('Intersection');
877
+ expect(result2.points[0].x).toBeCloseTo(0);
878
+ expect(result2.points[0].y).toBeCloseTo(-5);
879
+ });
880
+ });