@design-core/core 0.0.6 → 50.1.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.
Files changed (173) hide show
  1. package/.eslintrc.json +4 -0
  2. package/.github/workflows/DesignCoreQA.yml +3 -0
  3. package/TODO.md +49 -6
  4. package/core/core/core.js +0 -3
  5. package/core/designCore.js +1 -1
  6. package/core/dimensions/alignedDimension.js +12 -11
  7. package/core/dimensions/angularDimension.js +38 -37
  8. package/core/dimensions/{baseCircularDimension.js → circularDimensionBase.js} +22 -21
  9. package/core/dimensions/diametricDimension.js +14 -13
  10. package/core/dimensions/dimension.js +62 -55
  11. package/core/dimensions/{baseDimension.js → dimensionBase.js} +73 -107
  12. package/core/dimensions/{baseLinearDimension.js → linearDimensionBase.js} +15 -14
  13. package/core/dimensions/radialDimension.js +12 -11
  14. package/core/dimensions/rotatedDimension.js +20 -19
  15. package/core/entities/arc.js +123 -156
  16. package/core/entities/arctext.js +222 -184
  17. package/core/entities/circle.js +77 -99
  18. package/core/entities/entity.js +114 -93
  19. package/core/entities/hatch.js +346 -273
  20. package/core/entities/insert.js +68 -83
  21. package/core/entities/line.js +21 -142
  22. package/core/entities/lwpolyline.js +16 -11
  23. package/core/entities/point.js +19 -19
  24. package/core/entities/polyline.js +7 -7
  25. package/core/entities/{basePolyline.js → polylineBase.js} +47 -299
  26. package/core/entities/rectangle.js +3 -1
  27. package/core/entities/solid.js +6 -15
  28. package/core/entities/text.js +190 -201
  29. package/core/lib/auxiliary/rubberBand.js +32 -0
  30. package/core/lib/auxiliary/selectionWindow.js +63 -0
  31. package/core/lib/auxiliary/snapPoint.js +113 -0
  32. package/core/lib/auxiliary/trackingLine.js +76 -0
  33. package/core/lib/boundingBox.js +11 -11
  34. package/core/lib/canvas.js +232 -168
  35. package/core/lib/clipboard.js +9 -3
  36. package/core/lib/colour.js +16 -0
  37. package/core/lib/commandLine.js +1 -6
  38. package/core/lib/commandManager.js +18 -8
  39. package/core/lib/constants.js +28 -0
  40. package/core/lib/dxf/dxf.js +15 -14
  41. package/core/lib/dxf/dxfIterator.js +2 -2
  42. package/core/lib/entityManager.js +33 -26
  43. package/core/lib/inputManager.js +36 -12
  44. package/core/lib/intersect.js +25 -0
  45. package/core/lib/matrix.js +1 -1
  46. package/core/lib/patterns.js +9 -2
  47. package/core/lib/plotOptions.js +67 -0
  48. package/core/lib/polylineUtils.js +75 -0
  49. package/core/lib/renderers/cairoRenderer.js +371 -0
  50. package/core/lib/renderers/canvasRenderer.js +315 -0
  51. package/core/lib/renderers/pdfRenderer.js +366 -0
  52. package/core/lib/renderers/rendererBase.js +284 -0
  53. package/core/lib/renderers/svgRenderer.js +343 -0
  54. package/core/lib/scene.js +28 -6
  55. package/core/lib/selectionManager.js +20 -20
  56. package/core/lib/settings.js +4 -2
  57. package/core/lib/snapping.js +4 -191
  58. package/core/lib/stateManager.js +30 -1
  59. package/core/lib/utils.js +26 -7
  60. package/core/properties/entityProperties.js +104 -0
  61. package/core/properties/property.js +106 -2
  62. package/core/properties/propertyManager.js +60 -44
  63. package/core/tables/block.js +50 -81
  64. package/core/tables/dimStyle.js +5 -5
  65. package/core/tables/endblk.js +1 -1
  66. package/core/tables/layer.js +8 -0
  67. package/core/tables/ltypeManager.js +1 -1
  68. package/core/tables/styleManagerBase.js +3 -2
  69. package/core/tables/tableManagerBase.js +8 -3
  70. package/core/tables/vport.js +2 -2
  71. package/core/tools/chamfer.js +117 -143
  72. package/core/tools/chamferFilletBase.js +138 -37
  73. package/core/tools/copy.js +11 -11
  74. package/core/tools/copybase.js +3 -3
  75. package/core/tools/copyclip.js +3 -3
  76. package/core/tools/cornerEntity.js +2 -2
  77. package/core/tools/cutclip.js +3 -3
  78. package/core/tools/explode.js +7 -7
  79. package/core/tools/extend.js +138 -25
  80. package/core/tools/fillet.js +92 -189
  81. package/core/tools/matchProp.js +3 -12
  82. package/core/tools/mirror.js +15 -14
  83. package/core/tools/move.js +8 -8
  84. package/core/tools/offset.js +31 -33
  85. package/core/tools/pasteclip.js +1 -1
  86. package/core/tools/purge.js +10 -10
  87. package/core/tools/rotate.js +19 -16
  88. package/core/tools/scale.js +7 -7
  89. package/core/tools/trim.js +271 -29
  90. package/core/tools/zoom.js +1 -1
  91. package/cspell.json +321 -0
  92. package/jest.config.js +6 -0
  93. package/package.json +5 -3
  94. package/test/dimensions/alignedDimension.test.js +17 -10
  95. package/test/dimensions/angularDimension.test.js +13 -13
  96. package/test/dimensions/diametricDimension.test.js +15 -15
  97. package/test/dimensions/dimension.test.js +12 -12
  98. package/test/dimensions/{baseDimension.test.js → dimensionBase.test.js} +73 -12
  99. package/test/dimensions/linearDimensionBase.test.js +62 -0
  100. package/test/dimensions/radialDimension.test.js +15 -15
  101. package/test/dimensions/rotatedDimension.test.js +18 -11
  102. package/test/entities/arc.test.js +83 -86
  103. package/test/entities/arctext.test.js +184 -57
  104. package/test/entities/circle.test.js +68 -96
  105. package/test/entities/entity.test.js +42 -8
  106. package/test/entities/hatch.test.js +569 -96
  107. package/test/entities/insert.test.js +99 -4
  108. package/test/entities/line.test.js +15 -41
  109. package/test/entities/lwpolyline.test.js +104 -0
  110. package/test/entities/point.test.js +3 -3
  111. package/test/entities/polyline.test.js +11 -0
  112. package/test/entities/{basePolyline.test.js → polylineBase.test.js} +192 -57
  113. package/test/entities/rectangle.test.js +11 -0
  114. package/test/entities/solid.test.js +1 -1
  115. package/test/entities/text.test.js +302 -77
  116. package/test/integration/exportIntegration.reference.dxf +9 -9
  117. package/test/integration/exportIntegration.test.js +5 -5
  118. package/test/integration/intersectionIntegration.test.js +4 -5
  119. package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
  120. package/test/integration/pdfRendererIntegration.test.js +61 -0
  121. package/test/integration/svgRendererIntegration.reference.svg +3343 -0
  122. package/test/integration/svgRendererIntegration.test.js +61 -0
  123. package/test/integration/userInputIntegration.reference.dxf +1165 -105
  124. package/test/integration/userInputIntegration.test.js +239 -132
  125. package/test/lib/boundingBox.test.js +3 -3
  126. package/test/lib/canvas.test.js +744 -125
  127. package/test/lib/constants.test.js +27 -0
  128. package/test/lib/entityManager.test.js +4 -4
  129. package/test/lib/inputManager.test.js +81 -20
  130. package/test/lib/intersect.test.js +83 -0
  131. package/test/lib/plotOptions.test.js +179 -0
  132. package/test/lib/rubberBand.test.js +84 -0
  133. package/test/lib/scene.test.js +9 -9
  134. package/test/lib/selectionManager.test.js +3 -3
  135. package/test/lib/settings.test.js +5 -6
  136. package/test/lib/snapping.test.js +72 -130
  137. package/test/lib/stateManager.test.js +7 -7
  138. package/test/lib/utils.test.js +36 -2
  139. package/test/properties/entityProperties.test.js +132 -0
  140. package/test/properties/property.test.js +53 -0
  141. package/test/properties/propertyManager.test.js +25 -25
  142. package/test/renderers/canvasRenderer.test.js +317 -0
  143. package/test/renderers/pdfRenderer.test.js +271 -0
  144. package/test/renderers/rendererBase.test.js +213 -0
  145. package/test/renderers/svgRenderer.test.js +326 -0
  146. package/test/tables/block.test.js +92 -13
  147. package/test/tables/endblk.test.js +2 -2
  148. package/test/tables/layer.test.js +30 -0
  149. package/test/tables/layerManager.test.js +2 -2
  150. package/test/test-helpers/hatchGridRenderer.js +180 -0
  151. package/test/test-helpers/test-helpers.js +158 -6
  152. package/test/tools/chamfer.test.js +273 -51
  153. package/test/tools/chamferFilletBase.test.js +125 -36
  154. package/test/tools/copy.test.js +8 -8
  155. package/test/tools/copybase.test.js +2 -2
  156. package/test/tools/copyclip.test.js +2 -2
  157. package/test/tools/cornerEntity.test.js +8 -8
  158. package/test/tools/cutclip.test.js +2 -2
  159. package/test/tools/erase.test.js +6 -6
  160. package/test/tools/explode.test.js +5 -5
  161. package/test/tools/extend.test.js +373 -42
  162. package/test/tools/fillet.test.js +230 -49
  163. package/test/tools/matchProp.test.js +11 -11
  164. package/test/tools/mirror.test.js +39 -39
  165. package/test/tools/move.test.js +7 -7
  166. package/test/tools/offset.test.js +222 -55
  167. package/test/tools/pasteclip.test.js +5 -5
  168. package/test/tools/purge.test.js +4 -4
  169. package/test/tools/rotate.test.js +39 -9
  170. package/test/tools/scale.test.js +33 -33
  171. package/test/tools/trim.test.js +579 -53
  172. package/test/tools/zoom.test.js +12 -12
  173. package/core/lib/selectionWindow.js +0 -83
@@ -1,198 +1,11 @@
1
1
 
2
2
  import { Point } from '../entities/point.js';
3
- import { Colours } from './colours.js';
4
3
  import { Utils } from './utils.js';
5
4
 
5
+ import { SnapPoint } from './auxiliary/snapPoint.js';
6
+ import { TrackingLine } from './auxiliary/trackingLine.js';
6
7
  import { DesignCore } from '../designCore.js';
7
-
8
- /** SnapPoint Class */
9
- export class SnapPoint {
10
- static Type = Object.freeze({
11
- NONE: 'none',
12
- END: 'end',
13
- MID: 'mid',
14
- CENTRE: 'centre',
15
- QUADRANT: 'quadrant',
16
- NEAREST: 'nearest',
17
- TANGENT: 'tangent',
18
- NODE: 'node',
19
- PERPENDICULAR: 'perpendicular',
20
- });
21
-
22
- /**
23
- * Create SnapPoint
24
- * @param {Point} snapPoint
25
- * @param {string} type - snap type from SnapPoint.Type
26
- */
27
- constructor(snapPoint, type = SnapPoint.Type.END) {
28
- this.snapPoint = snapPoint;
29
- this.type = type;
30
- }
31
-
32
- /**
33
- * Draw the entity
34
- * @param {Object} ctx - context
35
- * @param {number} scale
36
- */
37
- draw(ctx, scale) {
38
- const snapColour = DesignCore.Settings.snaptrackingcolour;
39
- const size = 6 / scale;
40
- const x = this.snapPoint.x;
41
- const y = this.snapPoint.y;
42
- const lineWidth = 2.5 / scale;
43
-
44
- try { // HTML Canvas
45
- ctx.strokeStyle = Colours.rgbToString(snapColour);
46
- ctx.lineWidth = lineWidth;
47
- ctx.setLineDash([]);
48
- ctx.beginPath();
49
- } catch { // Cairo
50
- const rgbColour = Colours.rgbToScaledRGB(snapColour);
51
- ctx.setSourceRGB(rgbColour.r, rgbColour.g, rgbColour.b);
52
- ctx.setLineWidth(lineWidth);
53
- }
54
-
55
- switch (this.type) {
56
- case SnapPoint.Type.END: // square
57
- ctx.moveTo(x - size, y - size);
58
- ctx.lineTo(x + size, y - size);
59
- ctx.lineTo(x + size, y + size);
60
- ctx.lineTo(x - size, y + size);
61
- ctx.closePath();
62
- break;
63
- case SnapPoint.Type.MID: // triangle
64
- ctx.moveTo(x, y + size);
65
- ctx.lineTo(x - size, y - size);
66
- ctx.lineTo(x + size, y - size);
67
- ctx.closePath();
68
- break;
69
- case SnapPoint.Type.QUADRANT: // diamond
70
- ctx.moveTo(x, y - size);
71
- ctx.lineTo(x + size, y);
72
- ctx.lineTo(x, y + size);
73
- ctx.lineTo(x - size, y);
74
- ctx.closePath();
75
- break;
76
- case SnapPoint.Type.NEAREST: // hourglass (X with horizontal lines at top and bottom)
77
- ctx.moveTo(x - size, y - size);
78
- ctx.lineTo(x + size, y - size);
79
- ctx.lineTo(x - size, y + size);
80
- ctx.lineTo(x + size, y + size);
81
- ctx.closePath();
82
- break;
83
- case SnapPoint.Type.TANGENT: // circle with a horizontal line over the top
84
- ctx.arc(x, y, size, 0, 6.283);
85
- ctx.moveTo(x - size, y + size * 1.5);
86
- ctx.lineTo(x + size, y + size * 1.5);
87
- break;
88
- case SnapPoint.Type.NODE: // circle with an X inside
89
- ctx.arc(x, y, size, 0, 6.283);
90
- ctx.moveTo(x - size * 0.7, y - size * 0.7);
91
- ctx.lineTo(x + size * 0.7, y + size * 0.7);
92
- ctx.moveTo(x + size * 0.7, y - size * 0.7);
93
- ctx.lineTo(x - size * 0.7, y + size * 0.7);
94
- break;
95
- case SnapPoint.Type.PERPENDICULAR: // right-angle corner (L-shape)
96
- ctx.moveTo(x - size, y + size);
97
- ctx.lineTo(x - size, y - size);
98
- ctx.lineTo(x + size, y - size);
99
- break;
100
- default: // CENTRE - circle
101
- ctx.arc(x, y, size, 0, 6.283);
102
- break;
103
- }
104
-
105
- ctx.stroke();
106
- }
107
- }
108
-
109
- /** TrackingLine Class - dotted tracking line for polar/ortho snap */
110
- class TrackingLine {
111
- /**
112
- * Create TrackingLine
113
- * @param {Point} inputPoint - the previous input point (line passes through this)
114
- * @param {Point} snapPoint - the current snapped mouse point (direction reference)
115
- */
116
- constructor(inputPoint, snapPoint) {
117
- this.inputPoint = inputPoint;
118
- this.snapPoint = snapPoint;
119
- }
120
-
121
- /**
122
- * Draw the tracking line, extended to the visible canvas edges
123
- * @param {Object} ctx - context
124
- * @param {number} scale
125
- */
126
- draw(ctx, scale) {
127
- const from = this.inputPoint;
128
- const dir = this.snapPoint.subtract(from);
129
- const lineWidth = 2 / scale;
130
-
131
- if (dir.x === 0 && dir.y === 0) {
132
- return;
133
- }
134
-
135
- // Compute visible canvas bounds in scene space
136
- const w = DesignCore.Canvas.width;
137
- const h = DesignCore.Canvas.height;
138
- const corner0 = DesignCore.Mouse.transformToScene(new Point(0, 0));
139
- const corner1 = DesignCore.Mouse.transformToScene(new Point(w, h));
140
- const boundsMin = corner0.min(corner1);
141
- const boundsMax = corner0.max(corner1);
142
-
143
- // Clip the infinite line (through 'from' in direction dir) to canvas bounds
144
- let tMin = -Infinity;
145
- let tMax = Infinity;
146
-
147
- if (dir.x !== 0) {
148
- const tx1 = (boundsMin.x - from.x) / dir.x;
149
- const tx2 = (boundsMax.x - from.x) / dir.x;
150
- tMin = Math.max(tMin, Math.min(tx1, tx2));
151
- tMax = Math.min(tMax, Math.max(tx1, tx2));
152
- } else if (from.x < boundsMin.x || from.x > boundsMax.x) {
153
- return;
154
- }
155
-
156
- if (dir.y !== 0) {
157
- const ty1 = (boundsMin.y - from.y) / dir.y;
158
- const ty2 = (boundsMax.y - from.y) / dir.y;
159
- tMin = Math.max(tMin, Math.min(ty1, ty2));
160
- tMax = Math.min(tMax, Math.max(ty1, ty2));
161
- } else if (from.y < boundsMin.y || from.y > boundsMax.y) {
162
- return;
163
- }
164
-
165
- if (tMax < tMin) {
166
- return;
167
- }
168
-
169
- const start = from.add(dir.scale(tMin));
170
- const end = from.add(dir.scale(tMax));
171
-
172
- const lineColour = DesignCore.Settings.snaptrackingcolour;
173
- const dashSize = 4 / scale;
174
-
175
- ctx.save();
176
-
177
- try { // HTML Canvas
178
- ctx.beginPath();
179
- ctx.strokeStyle = Colours.rgbToString(lineColour);
180
- ctx.lineWidth = lineWidth;
181
- ctx.setLineDash([dashSize, dashSize]);
182
- } catch { // Cairo
183
- const rgbColour = Colours.rgbToScaledRGB(lineColour);
184
- ctx.setSourceRGB(rgbColour.r, rgbColour.g, rgbColour.b);
185
- ctx.setLineWidth(lineWidth);
186
- ctx.setDash([dashSize, dashSize], 0);
187
- }
188
-
189
- ctx.moveTo(start.x, start.y);
190
- ctx.lineTo(end.x, end.y);
191
- ctx.stroke();
192
-
193
- ctx.restore();
194
- }
195
- }
8
+ import { Property } from '../properties/property.js';
196
9
 
197
10
  /** Snapping Class */
198
11
  export class Snapping {
@@ -271,7 +84,7 @@ export class Snapping {
271
84
  let delta = snapAperture / DesignCore.Canvas.getScale(); // convert to scene space
272
85
 
273
86
  for (let i = 0; i < DesignCore.Scene.entities.count(); i++) {
274
- const layer = DesignCore.LayerManager.getItemByName(DesignCore.Scene.entities.get(i).layer);
87
+ const layer = DesignCore.LayerManager.getItemByName(DesignCore.Scene.entities.get(i).getProperty(Property.Names.LAYER));
275
88
 
276
89
  if (!layer?.isVisible) {
277
90
  continue;
@@ -200,7 +200,7 @@ class State {
200
200
  // capture previous properties for undo
201
201
  const previousProperties = {};
202
202
  for (const prop of Object.getOwnPropertyNames(stateChange.properties)) {
203
- previousProperties[prop] = stateChange.entity[prop];
203
+ previousProperties[prop] = stateChange.entity.getProperty(prop);
204
204
  }
205
205
  const undoStateChange = new UpdateState(stateChange.entity, previousProperties);
206
206
  this.undoStateChanges.push(undoStateChange);
@@ -208,6 +208,15 @@ class State {
208
208
  this.entityManager.update(index, stateChange.properties);
209
209
  }
210
210
  }
211
+
212
+ if (stateChange instanceof ReplaceState) {
213
+ const index = this.entityManager.indexOf(stateChange.entity);
214
+ if (index !== -1) {
215
+ this.entityManager.replace(index, stateChange.replacement);
216
+ const undoStateChange = new ReplaceState(stateChange.replacement, stateChange.entity);
217
+ this.undoStateChanges.push(undoStateChange);
218
+ }
219
+ }
211
220
  }
212
221
  }
213
222
 
@@ -231,6 +240,13 @@ class State {
231
240
  this.entityManager.update(index, stateChange.properties);
232
241
  }
233
242
  }
243
+
244
+ if (stateChange instanceof ReplaceState) {
245
+ const index = this.entityManager.indexOf(stateChange.entity);
246
+ if (index !== -1) {
247
+ this.entityManager.replace(index, stateChange.replacement);
248
+ }
249
+ }
234
250
  }
235
251
  }
236
252
  }
@@ -286,3 +302,16 @@ export class UpdateState extends StateChange {
286
302
  super(entity, properties);
287
303
  }
288
304
  }
305
+
306
+ /** Replace State Class */
307
+ export class ReplaceState {
308
+ /**
309
+ * Create Replace State
310
+ * @param {object} entity - the entity to be replaced (used to locate it by reference)
311
+ * @param {object} replacement - the entity to put in its place
312
+ */
313
+ constructor(entity, replacement) {
314
+ this.entity = entity;
315
+ this.replacement = replacement;
316
+ }
317
+ }
package/core/lib/utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
2
  import { Logging } from './logging.js';
3
3
  import { Constants } from './constants.js';
4
+ import { Property } from '../properties/property.js';
4
5
 
5
6
  /** Utils Class */
6
7
  export class Utils {
@@ -46,26 +47,26 @@ export class Utils {
46
47
  }
47
48
 
48
49
  /**
49
- * Sort points around an arc center by angular position.
50
+ * Sort points around an arc centre by angular position.
50
51
  * Mutates the points array.
51
52
  *
52
53
  * @param {Array} points - array of points
53
54
  * @param {Point} startPoint - arc start point
54
55
  * @param {Point} endPoint - arc end point
55
- * @param {Point} centerPoint - arc center point
56
+ * @param {Point} centrePoint - arc centre point
56
57
  * @param {number} direction - arc direction (ccw >0, cw <=0)
57
58
  *
58
59
  */
59
- static sortPointsOnArc(points, startPoint, endPoint, centerPoint, direction = 1) {
60
+ static sortPointsOnArc(points, startPoint, endPoint, centrePoint, direction = 1) {
60
61
  if (!Array.isArray(points)) return;
61
62
 
62
- const refAngle = direction > 0 ? centerPoint.angle(startPoint) : centerPoint.angle(endPoint);
63
+ const refAngle = direction > 0 ? centrePoint.angle(startPoint) : centrePoint.angle(endPoint);
63
64
  const twoPi = Math.PI * 2;
64
65
  const normalize = (ang) => ((ang % twoPi) + twoPi) % twoPi;
65
66
 
66
67
  // map points to angles
67
68
  const mapped = points.map((point) => {
68
- const angle = Math.atan2(point.y - centerPoint.y, point.x - centerPoint.x);
69
+ const angle = Math.atan2(point.y - centrePoint.y, point.x - centrePoint.x);
69
70
  const normalizedAngle = normalize(angle - refAngle);
70
71
  return { point, normalizedAngle };
71
72
  });
@@ -114,13 +115,31 @@ export class Utils {
114
115
  }
115
116
 
116
117
  // Clear handle so a fresh unique handle is assigned when added to the scene
117
- if (clone.handle !== undefined) {
118
- clone.handle = undefined;
118
+ if (clone.getProperty?.(Property.Names.HANDLE) !== undefined) {
119
+ clone.setProperty?.(Property.Names.HANDLE, undefined);
119
120
  }
120
121
 
121
122
  return clone;
122
123
  }
123
124
 
125
+ /**
126
+ * Copy display properties from a source entity into a plain data object.
127
+ * Useful when creating a new entity that should inherit the visual properties
128
+ * of an existing one (e.g. the arc produced by Fillet).
129
+ * @param {Entity} entity - source entity
130
+ * @param {Array<string>} properties - property names to copy
131
+ * @return {Object} plain object containing the copied properties
132
+ */
133
+ static cloneProperties(entity, properties = ['colour', 'layer', 'lineType', 'lineWidth', 'height', 'styleName', 'dimensionStyle', 'patternName', 'scale']) {
134
+ const result = {};
135
+ for (const prop of properties) {
136
+ if (entity.properties?.has(prop)) {
137
+ result[prop] = entity.getProperty(prop);
138
+ }
139
+ }
140
+ return result;
141
+ }
142
+
124
143
  /**
125
144
  * Calculate area of triangle given 3 points
126
145
  * @param {Point} p1
@@ -0,0 +1,104 @@
1
+ import { Property } from './property.js';
2
+
3
+ /**
4
+ * EntityProperties — typed property store for an entity.
5
+ *
6
+ * Holds a plain-object store of named Property descriptors.
7
+ * Each descriptor owns its value.
8
+ * Using a plain object (rather than a private Map) ensures the store is deep-cloneable by Utils.cloneObject.
9
+ *
10
+ * Computed properties may supply get(entity) and set(entity, value) functions.
11
+ * These are stateless functions (not bound methods) so they survive cloneObject.
12
+ */
13
+ export class EntityProperties {
14
+ /** @type {Object.<string, Property>} */
15
+ // Store is a plain object to ensure it is deep-cloneable by Utils.cloneObject.
16
+ _store = {};
17
+
18
+ /**
19
+ * Register a property.
20
+ * @param {string} name - property name
21
+ * @param {Object} definition - passed directly to the Property constructor
22
+ * @param {string} definition.type - Property.Type constant
23
+ * @param {any} definition.value - initial value
24
+ * @param {boolean} [definition.readOnly=false]
25
+ * @param {boolean} [definition.visible=true]
26
+ * @param {number} [definition.dxfCode]
27
+ * @param {Function} [definition.get] - computed getter: (entity) => value
28
+ * @param {Function} [definition.set] - computed setter: (entity, value) => void
29
+ */
30
+ add(name, definition) {
31
+ this._store[name] = new Property(definition);
32
+ }
33
+
34
+ /**
35
+ * Get the current value of a property.
36
+ * For computed properties, invokes get(entity).
37
+ * For stored properties, returns prop.value directly.
38
+ * @param {string} name
39
+ * @param {Object} [entity] - passed to the computed getter when prop.get is defined
40
+ * @return {any} value, or undefined if not registered
41
+ */
42
+ get(name, entity) {
43
+ const prop = this._store[name];
44
+ if (!prop) return undefined;
45
+ if (prop.get) return prop.get(entity);
46
+ return prop.value;
47
+ }
48
+
49
+ /**
50
+ * Set the value of a property.
51
+ * For computed properties, invokes set(entity, value).
52
+ * For stored properties, updates prop.value directly.
53
+ * No-op if the property is not registered or is readOnly.
54
+ * @param {string} name
55
+ * @param {any} value
56
+ * @param {Object} [entity] - passed to the computed setter when prop.set is defined
57
+ */
58
+ set(name, value, entity) {
59
+ const prop = this._store[name];
60
+ if (!prop) return;
61
+ if (prop.readOnly) return;
62
+ if (prop.set) {
63
+ prop.set(entity, value);
64
+ } else {
65
+ prop.value = value;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Check whether a property is registered.
71
+ * @param {string} name
72
+ * @return {boolean}
73
+ */
74
+ has(name) {
75
+ return name in this._store;
76
+ }
77
+
78
+ /**
79
+ * Remove a property from the store.
80
+ * @param {string} name
81
+ */
82
+ remove(name) {
83
+ delete this._store[name];
84
+ }
85
+
86
+ /**
87
+ * Return names of all visible properties in registration order.
88
+ * @return {string[]}
89
+ */
90
+ list() {
91
+ return Object.entries(this._store)
92
+ .filter(([, prop]) => prop.visible)
93
+ .map(([name]) => name);
94
+ }
95
+
96
+ /**
97
+ * Return the full Property descriptor for a named property.
98
+ * @param {string} name
99
+ * @return {Property|undefined}
100
+ */
101
+ definition(name) {
102
+ return this._store[name];
103
+ }
104
+ }
@@ -2,8 +2,112 @@ import { Flags } from './flags.js';
2
2
 
3
3
  /** Property Class */
4
4
  export class Property {
5
- /** Create property */
6
- constructor() { }
5
+ /**
6
+ * Create a Property descriptor
7
+ * @param {Object} [options]
8
+ * @param {string} options.type - Property.Type constant describing the value kind
9
+ * @param {any} options.value - initial value
10
+ * @param {boolean} [options.readOnly=false] - when true, set() is a no-op
11
+ * @param {boolean} [options.visible=true] - controls whether the property appears in listings
12
+ * @param {number} [options.dxfCode] - associated DXF group code (informational)
13
+ * @param {Function} [options.get] - computed getter: (entity) => value
14
+ * @param {Function} [options.set] - computed setter: (entity, value) => void
15
+ */
16
+ constructor({ type, value, readOnly = false, visible = true, dxfCode, get, set, options } = {}) {
17
+ this.type = type;
18
+ this._value = value;
19
+ this.readOnly = readOnly;
20
+ this.visible = visible;
21
+ this.dxfCode = dxfCode;
22
+ this.get = get;
23
+ this.set = set;
24
+ this.options = options;
25
+ }
26
+
27
+ /** @return {any} current value */
28
+ get value() {
29
+ return this._value;
30
+ }
31
+
32
+ /** @param {any} val - new value; ignored when readOnly */
33
+ set value(val) {
34
+ if (!this.readOnly) {
35
+ this._value = val;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Property type constants
41
+ * Used to describe the type of a property value for UI rendering and validation.
42
+ * - NUMBER: numeric value (integer or float)
43
+ * - STRING: text value
44
+ * - BOOLEAN: true/false toggle
45
+ * - LIST: selection from a named set (layer, lineType, hatch pattern, etc.)
46
+ * - COLOUR: RGB colour object
47
+ * - LABEL: read-only display value (no editing)
48
+ */
49
+ static Type = {
50
+ NUMBER: 'NUMBER',
51
+ STRING: 'STRING',
52
+ BOOLEAN: 'BOOLEAN',
53
+ LIST: 'LIST',
54
+ COLOUR: 'COLOUR',
55
+ LABEL: 'LABEL',
56
+ ENTITIES: 'ENTITIES',
57
+ };
58
+
59
+ /**
60
+ * Canonical property name constants.
61
+ * Use these instead of raw strings when calling getProperty() / setProperty().
62
+ */
63
+ static Names = Object.freeze({
64
+ // ── Base entity (Entity class) ────────────────────────────────────────────
65
+ HANDLE: 'handle',
66
+ LAYER: 'layer',
67
+ LINETYPE: 'lineType',
68
+ LINEWIDTH: 'lineWidth',
69
+ COLOUR: 'colour',
70
+ POINTS: 'points',
71
+ // ── Shared (multiple entities) ────────────────────────────────────────────
72
+ ANGLE: 'angle',
73
+ BLOCKNAME: 'blockName',
74
+ DIRECTION: 'direction',
75
+ HEIGHT: 'height',
76
+ RADIUS: 'radius',
77
+ ROTATION: 'rotation',
78
+ SCALE: 'scale',
79
+ STRING: 'string',
80
+ STYLENAME: 'styleName',
81
+ // ── PolylineBase ──────────────────────────────────────────────────────────
82
+ CLOSED: 'closed',
83
+ // ── Hatch ─────────────────────────────────────────────────────────────────
84
+ CHILDENTITIES: 'childEntities',
85
+ PATTERNNAME: 'patternName',
86
+ // ── Text ──────────────────────────────────────────────────────────────────
87
+ BACKWARDS: 'backwards',
88
+ HORIZONTALALIGNMENT: 'horizontalAlignment',
89
+ UPSIDEDOWN: 'upsideDown',
90
+ VERTICALALIGNMENT: 'verticalAlignment',
91
+ // ── ArcAlignedText ────────────────────────────────────────────────────────
92
+ ARCSIDE: 'arcSide',
93
+ BOLD: 'bold',
94
+ CHARACTERSPACING: 'characterSpacing',
95
+ FONTNAME: 'fontName',
96
+ ITALIC: 'italic',
97
+ OFFSETFROMARC: 'offsetFromArc',
98
+ OFFSETFROMLEFT: 'offsetFromLeft',
99
+ OFFSETFROMRIGHT: 'offsetFromRight',
100
+ TEXTALIGNMENT: 'textAlignment',
101
+ TEXTORIENTATION: 'textOrientation',
102
+ TEXTREVERSED: 'textReversed',
103
+ UNDERLINE: 'underline',
104
+ WIDTHFACTOR: 'widthFactor',
105
+ // ── Dimensions ────────────────────────────────────────────────────────────
106
+ DIMENSIONSTYLE: 'dimensionStyle',
107
+ LEADERLENGTH: 'leaderLength',
108
+ LINEARDIMANGLE: 'linearDimAngle',
109
+ TEXTOVERRIDE: 'textOverride',
110
+ });
7
111
 
8
112
  /**
9
113
  * Parse the input values and return a value