@design-core/core 0.0.5 → 0.0.7

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 (145) hide show
  1. package/.eslintrc.json +4 -0
  2. package/TODO.md +49 -6
  3. package/core/core/core.js +0 -3
  4. package/core/dimensions/alignedDimension.js +5 -4
  5. package/core/dimensions/angularDimension.js +6 -5
  6. package/core/dimensions/baseCircularDimension.js +3 -2
  7. package/core/dimensions/baseDimension.js +59 -93
  8. package/core/dimensions/baseLinearDimension.js +6 -5
  9. package/core/dimensions/diametricDimension.js +4 -3
  10. package/core/dimensions/dimension.js +21 -14
  11. package/core/dimensions/radialDimension.js +4 -3
  12. package/core/dimensions/rotatedDimension.js +12 -11
  13. package/core/entities/arc.js +112 -145
  14. package/core/entities/arctext.js +212 -174
  15. package/core/entities/basePolyline.js +33 -284
  16. package/core/entities/circle.js +77 -99
  17. package/core/entities/entity.js +114 -93
  18. package/core/entities/hatch.js +322 -249
  19. package/core/entities/insert.js +66 -81
  20. package/core/entities/line.js +21 -142
  21. package/core/entities/lwpolyline.js +16 -11
  22. package/core/entities/polyline.js +4 -4
  23. package/core/entities/rectangle.js +3 -1
  24. package/core/entities/solid.js +6 -15
  25. package/core/entities/text.js +188 -199
  26. package/core/lib/auxiliary/rubberBand.js +32 -0
  27. package/core/lib/auxiliary/selectionWindow.js +63 -0
  28. package/core/lib/auxiliary/snapPoint.js +113 -0
  29. package/core/lib/auxiliary/trackingLine.js +76 -0
  30. package/core/lib/canvas.js +228 -166
  31. package/core/lib/clipboard.js +7 -1
  32. package/core/lib/colour.js +16 -0
  33. package/core/lib/commandLine.js +1 -6
  34. package/core/lib/commandManager.js +18 -8
  35. package/core/lib/constants.js +28 -0
  36. package/core/lib/dxf/dxf.js +4 -3
  37. package/core/lib/entityManager.js +18 -11
  38. package/core/lib/inputManager.js +36 -13
  39. package/core/lib/intersect.js +25 -0
  40. package/core/lib/patterns.js +8 -1
  41. package/core/lib/plotOptions.js +67 -0
  42. package/core/lib/polylineUtils.js +75 -0
  43. package/core/lib/renderers/cairoRenderer.js +371 -0
  44. package/core/lib/renderers/canvasRenderer.js +315 -0
  45. package/core/lib/renderers/pdfRenderer.js +366 -0
  46. package/core/lib/renderers/rendererBase.js +284 -0
  47. package/core/lib/renderers/svgRenderer.js +343 -0
  48. package/core/lib/scene.js +27 -5
  49. package/core/lib/selectionManager.js +2 -2
  50. package/core/lib/settings.js +4 -2
  51. package/core/lib/snapping.js +4 -191
  52. package/core/lib/stateManager.js +30 -1
  53. package/core/lib/utils.js +21 -2
  54. package/core/properties/entityProperties.js +104 -0
  55. package/core/properties/property.js +106 -2
  56. package/core/properties/propertyManager.js +29 -13
  57. package/core/tables/block.js +4 -37
  58. package/core/tables/endblk.js +1 -1
  59. package/core/tables/layer.js +8 -0
  60. package/core/tables/styleManagerBase.js +3 -2
  61. package/core/tables/tableManagerBase.js +8 -3
  62. package/core/tools/chamfer.js +111 -137
  63. package/core/tools/chamferFilletBase.js +135 -34
  64. package/core/tools/copy.js +3 -3
  65. package/core/tools/explode.js +2 -2
  66. package/core/tools/extend.js +132 -19
  67. package/core/tools/fillet.js +86 -183
  68. package/core/tools/matchProp.js +2 -11
  69. package/core/tools/mirror.js +7 -6
  70. package/core/tools/move.js +3 -3
  71. package/core/tools/offset.js +3 -5
  72. package/core/tools/pasteclip.js +1 -1
  73. package/core/tools/rotate.js +6 -3
  74. package/core/tools/scale.js +1 -1
  75. package/core/tools/trim.js +265 -23
  76. package/core/tools/zoom.js +1 -1
  77. package/jest.config.js +6 -0
  78. package/package.json +5 -3
  79. package/test/dimensions/alignedDimension.test.js +14 -7
  80. package/test/dimensions/angularDimension.test.js +6 -6
  81. package/test/dimensions/baseDimension.test.js +69 -8
  82. package/test/dimensions/baseLinearDimension.test.js +62 -0
  83. package/test/dimensions/diametricDimension.test.js +6 -6
  84. package/test/dimensions/radialDimension.test.js +6 -6
  85. package/test/dimensions/rotatedDimension.test.js +14 -7
  86. package/test/entities/arc.test.js +80 -83
  87. package/test/entities/arctext.test.js +180 -53
  88. package/test/entities/basePolyline.test.js +129 -2
  89. package/test/entities/circle.test.js +57 -85
  90. package/test/entities/entity.test.js +42 -8
  91. package/test/entities/hatch.test.js +543 -70
  92. package/test/entities/insert.test.js +96 -1
  93. package/test/entities/line.test.js +15 -41
  94. package/test/entities/lwpolyline.test.js +104 -0
  95. package/test/entities/polyline.test.js +11 -0
  96. package/test/entities/rectangle.test.js +11 -0
  97. package/test/entities/solid.test.js +1 -1
  98. package/test/entities/text.test.js +294 -69
  99. package/test/integration/exportIntegration.reference.dxf +9 -9
  100. package/test/integration/exportIntegration.test.js +2 -2
  101. package/test/integration/intersectionIntegration.test.js +4 -5
  102. package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
  103. package/test/integration/pdfRendererIntegration.test.js +61 -0
  104. package/test/integration/svgRendererIntegration.reference.svg +3343 -0
  105. package/test/integration/svgRendererIntegration.test.js +61 -0
  106. package/test/integration/userInputIntegration.reference.dxf +1165 -105
  107. package/test/integration/userInputIntegration.test.js +239 -132
  108. package/test/lib/canvas.test.js +742 -123
  109. package/test/lib/constants.test.js +27 -0
  110. package/test/lib/entityManager.test.js +4 -4
  111. package/test/lib/inputManager.test.js +80 -19
  112. package/test/lib/intersect.test.js +83 -0
  113. package/test/lib/plotOptions.test.js +179 -0
  114. package/test/lib/rubberBand.test.js +84 -0
  115. package/test/lib/settings.test.js +5 -6
  116. package/test/lib/snapping.test.js +71 -129
  117. package/test/lib/stateManager.test.js +7 -7
  118. package/test/lib/utils.test.js +36 -2
  119. package/test/properties/entityProperties.test.js +132 -0
  120. package/test/properties/property.test.js +53 -0
  121. package/test/properties/propertyManager.test.js +3 -3
  122. package/test/renderers/canvasRenderer.test.js +317 -0
  123. package/test/renderers/pdfRenderer.test.js +271 -0
  124. package/test/renderers/rendererBase.test.js +213 -0
  125. package/test/renderers/svgRenderer.test.js +326 -0
  126. package/test/tables/block.test.js +31 -1
  127. package/test/tables/endblk.test.js +2 -2
  128. package/test/tables/layer.test.js +30 -0
  129. package/test/test-helpers/hatchGridRenderer.js +180 -0
  130. package/test/test-helpers/test-helpers.js +152 -0
  131. package/test/tools/chamfer.test.js +227 -5
  132. package/test/tools/chamferFilletBase.test.js +98 -9
  133. package/test/tools/copy.test.js +2 -2
  134. package/test/tools/explode.test.js +3 -3
  135. package/test/tools/extend.test.js +331 -0
  136. package/test/tools/fillet.test.js +296 -0
  137. package/test/tools/matchProp.test.js +5 -5
  138. package/test/tools/mirror.test.js +13 -13
  139. package/test/tools/offset.test.js +170 -3
  140. package/test/tools/pasteclip.test.js +5 -5
  141. package/test/tools/rotate.test.js +32 -2
  142. package/test/tools/scale.test.js +5 -5
  143. package/test/tools/trim.test.js +526 -0
  144. package/test/tools/zoom.test.js +7 -7
  145. package/core/lib/selectionWindow.js +0 -83
@@ -11,7 +11,7 @@ import { Property } from '../properties/property.js';
11
11
  import { Text } from './text.js';
12
12
 
13
13
  import { DesignCore } from '../designCore.js';
14
- import { SnapPoint } from '../lib/snapping.js';
14
+ import { SnapPoint } from '../lib/auxiliary/snapPoint.js';
15
15
 
16
16
  /**
17
17
  * Arc Aligned Character Class
@@ -21,18 +21,21 @@ export class ArcAlignedCharacter {
21
21
  #position;
22
22
  #angle;
23
23
  #height;
24
+ #width;
24
25
 
25
26
  /**
26
27
  * @param {string} character - text character
27
28
  * @param {Point} position - center mid point of character
28
29
  * @param {number} angle - in radians
29
30
  * @param {number} height - character height
31
+ * @param {number} width - character width
30
32
  */
31
- constructor(character, position, angle, height = 1) {
33
+ constructor(character, position, angle, height = 1, width = 1) {
32
34
  this.#character = character;
33
35
  this.#position = position;
34
36
  this.#angle = angle;
35
37
  this.#height = height;
38
+ this.#width = width;
36
39
  }
37
40
 
38
41
  /**
@@ -67,6 +70,14 @@ export class ArcAlignedCharacter {
67
70
  return this.#height;
68
71
  }
69
72
 
73
+ /**
74
+ * Get width
75
+ * @return {number}
76
+ */
77
+ get width() {
78
+ return this.#width;
79
+ }
80
+
70
81
  /**
71
82
  * Get baseline point
72
83
  * @return {Point}
@@ -83,7 +94,7 @@ export class ArcAlignedCharacter {
83
94
  */
84
95
  get boundingBox() {
85
96
  const halfHeight = this.#height / 2;
86
- const halfWidth = halfHeight * 0.6; // approximate width as half height
97
+ const halfWidth = this.#width / 2;
87
98
  const pt1 = new Point(this.position.x - halfWidth, this.position.y - halfHeight);
88
99
  const pt2 = new Point(this.position.x + halfWidth, this.position.y + halfHeight);
89
100
  return new BoundingBox(pt1, pt2);
@@ -105,53 +116,81 @@ export class ArcAlignedText extends Entity {
105
116
  constructor(data) {
106
117
  super(data);
107
118
 
108
- // hide inherited properties
109
- // needs to be enumerable=false to not appear in the object props
110
- Object.defineProperty(this, 'lineType', {
111
- enumerable: false,
112
- });
113
-
114
- Object.defineProperty(this, 'lineWidth', {
115
- enumerable: false,
116
- });
119
+ // lineType and lineWidth not applicable to arc-aligned text
120
+ this.properties.remove(Property.Names.LINETYPE);
121
+ this.properties.remove(Property.Names.LINEWIDTH);
117
122
 
118
123
  // DXF Groupcode 1 - Text String
119
- this.string = (Property.loadValue([data?.string, data?.[1]], ''));
124
+ this.properties.add(Property.Names.STRING, {
125
+ type: Property.Type.STRING,
126
+ value: Property.loadValue([data?.string, data?.[1]], ''),
127
+ dxfCode: 1,
128
+ });
120
129
 
121
- // DXF Groupcode 2 - Font name
122
- // not implemented - enumerable=false to not appear in the object props
123
- // get font from selected style: this.styleName.font
124
- Object.defineProperty(this, 'fontName', {
130
+ // DXF Groupcode 2 - Font name (derived from style, not user-editable)
131
+ this.properties.add(Property.Names.FONTNAME, {
132
+ type: Property.Type.STRING,
125
133
  value: Property.loadValue([data?.fontName, data?.[2]], 'Arial'),
126
- writable: true,
134
+ dxfCode: 2,
135
+ visible: false,
127
136
  });
128
137
 
129
138
  // DXF Groupcode 3
130
139
  // not implemented - always empty in acad output
131
140
 
132
141
  // DXF Groupcode 7 - Text style name
133
- this.styleName = (Property.loadValue([data?.styleName, data?.[7]], 'STANDARD'));
142
+ this.properties.add(Property.Names.STYLENAME, {
143
+ type: Property.Type.LIST,
144
+ value: Property.loadValue([data?.styleName, data?.[7]], 'STANDARD'),
145
+ dxfCode: 7,
146
+ options: () => DesignCore.StyleManager.getItems().map((s) => ({ display: s.name, value: s.name })),
147
+ });
134
148
 
135
149
  // DXF Groupcode 40 - Radius
136
- this.radius = Property.loadValue([data?.radius, data?.[40]], 10);
150
+ this.properties.add(Property.Names.RADIUS, {
151
+ type: Property.Type.NUMBER,
152
+ value: Property.loadValue([data?.radius, data?.[40]], 10),
153
+ dxfCode: 40,
154
+ });
137
155
 
138
156
  // DXF Groupcode 41 - Width Factor
139
- // not implemented - enumerable=false to not appear in the object props
140
- Object.defineProperty(this, 'widthFactor', {
157
+ this.properties.add(Property.Names.WIDTHFACTOR, {
158
+ type: Property.Type.NUMBER,
141
159
  value: Property.loadValue([data?.widthFactor, data?.[41]], 1),
142
- writable: true,
160
+ dxfCode: 41,
161
+ visible: false,
143
162
  });
144
163
 
145
164
  // DXF Groupcode 42 - Text Height
146
- this.height = Property.loadValue([data?.height, data?.[42]], 2.5);
165
+ this.properties.add(Property.Names.HEIGHT, {
166
+ type: Property.Type.NUMBER,
167
+ value: Property.loadValue([data?.height, data?.[42]], 2.5),
168
+ dxfCode: 42,
169
+ });
147
170
  // DXF Groupcode 43 - Character Spacing
148
- this.characterSpacing = Property.loadValue([data?.characterSpacing, data?.[43]], 0.095);
171
+ this.properties.add(Property.Names.CHARACTERSPACING, {
172
+ type: Property.Type.NUMBER,
173
+ value: Property.loadValue([data?.characterSpacing, data?.[43]], 0.095),
174
+ dxfCode: 43,
175
+ });
149
176
  // DXF Groupcode 44 - Offset from Arc
150
- this.offsetFromArc = Property.loadValue([data?.offsetFromArc, data?.[44]], 0);
177
+ this.properties.add(Property.Names.OFFSETFROMARC, {
178
+ type: Property.Type.NUMBER,
179
+ value: Property.loadValue([data?.offsetFromArc, data?.[44]], 0),
180
+ dxfCode: 44,
181
+ });
151
182
  // DXF Groupcode 45 - Offset from right
152
- this.offsetFromRight = Property.loadValue([data?.offsetFromRight, data?.[45]], 0);
183
+ this.properties.add(Property.Names.OFFSETFROMRIGHT, {
184
+ type: Property.Type.NUMBER,
185
+ value: Property.loadValue([data?.offsetFromRight, data?.[45]], 0),
186
+ dxfCode: 45,
187
+ });
153
188
  // DXF Groupcode 46 - Offset from left
154
- this.offsetFromLeft = Property.loadValue([data?.offsetFromLeft, data?.[46]], 0);
189
+ this.properties.add(Property.Names.OFFSETFROMLEFT, {
190
+ type: Property.Type.NUMBER,
191
+ value: Property.loadValue([data?.offsetFromLeft, data?.[46]], 0),
192
+ dxfCode: 46,
193
+ });
155
194
 
156
195
  // ensure points array has at least one point - the arc center
157
196
  if (!this.points.length) {
@@ -162,48 +201,67 @@ export class ArcAlignedText extends Entity {
162
201
  // DXF Groupcode 50 - Start Angle in degrees
163
202
  const startAngle = Utils.round(Property.loadValue([data?.startAngle, data?.[50]], 0));
164
203
  if (this.points[1] === undefined) {
165
- this.points[1] = this.points[0].project(Utils.degrees2radians(startAngle), this.radius);
204
+ this.points[1] = this.points[0].project(Utils.degrees2radians(startAngle), this.getProperty(Property.Names.RADIUS));
166
205
  }
167
206
 
168
207
  // DXF Groupcode 51 - End Angle in degrees
169
208
  const endAngle = Utils.round(Property.loadValue([data?.endAngle, data?.[51]], 180));
170
209
  if (this.points[2] === undefined) {
171
- this.points[2] = this.points[0].project(Utils.degrees2radians(endAngle), this.radius);
210
+ this.points[2] = this.points[0].project(Utils.degrees2radians(endAngle), this.getProperty(Property.Names.RADIUS));
172
211
  }
173
212
 
174
213
  // DXF Groupcode 70 - Text Direction 0 = forward, 1 = reversed
175
- // not implemented - enumerable=false to not appear in the object props
176
- Object.defineProperty(this, 'textReversed', {
214
+ this.properties.add(Property.Names.TEXTREVERSED, {
215
+ type: Property.Type.BOOLEAN,
177
216
  value: Property.loadValue([data?.textReversed, Boolean(parseInt(data?.[70]))], false),
178
- writable: true,
217
+ dxfCode: 70,
218
+ visible: false,
179
219
  });
180
220
 
181
221
  // DXF Groupcode 71 - 1 = outward, 2 = inward
182
- this.textOrientation = Property.loadValue([data?.textOrientation, data?.[71]], 1);
222
+ this.properties.add(Property.Names.TEXTORIENTATION, {
223
+ type: Property.Type.LIST,
224
+ value: Property.loadValue([data?.textOrientation, data?.[71]], 1),
225
+ dxfCode: 71,
226
+ options: () => [{ display: 'Outward', value: 1 }, { display: 'Inward', value: 2 }],
227
+ });
183
228
  // DXF Groupcode 72 - 1 = fit to arc, 2 = left align, 3 = right align, 4 = center
184
- this.textAlignment = Property.loadValue([data?.textAlignment, data?.[72]], 4);
229
+ this.properties.add(Property.Names.TEXTALIGNMENT, {
230
+ type: Property.Type.LIST,
231
+ value: Property.loadValue([data?.textAlignment, data?.[72]], 4),
232
+ dxfCode: 72,
233
+ options: () => [{ display: 'Fit', value: 1 }, { display: 'Left', value: 2 }, { display: 'Right', value: 3 }, { display: 'Center', value: 4 }],
234
+ });
185
235
  // DXF Groupcode 73 - Arc Side: convex = 1, concave = 2
186
- this.arcSide = Property.loadValue([data?.arcSide, data?.[73]], 1);
236
+ this.properties.add(Property.Names.ARCSIDE, {
237
+ type: Property.Type.LIST,
238
+ value: Property.loadValue([data?.arcSide, data?.[73]], 1),
239
+ dxfCode: 73,
240
+ options: () => [{ display: 'Convex', value: 1 }, { display: 'Concave', value: 2 }],
241
+ });
187
242
 
188
243
  // DXF Groupcode 74 - Bold 0 = off, 1 = on
189
- // not implemented - enumerable=false to not appear in the object props
190
- Object.defineProperty(this, 'bold', {
244
+ this.properties.add(Property.Names.BOLD, {
245
+ type: Property.Type.BOOLEAN,
191
246
  value: Property.loadValue([data?.bold, Boolean(parseInt(data?.[74]))], false),
192
- writable: true,
247
+ dxfCode: 74,
248
+ visible: false,
193
249
  });
194
250
 
195
251
  // DXF Groupcode 75 - Italic 0 = off, 1 = on
196
- // not implemented - enumerable=false to not appear in the object props
197
- Object.defineProperty(this, 'italic', {
252
+ this.properties.add(Property.Names.ITALIC, {
253
+ type: Property.Type.BOOLEAN,
198
254
  value: Property.loadValue([data?.italic, Boolean(parseInt(data?.[75]))], false),
199
- writable: true,
255
+ dxfCode: 75,
256
+ visible: false,
200
257
  });
201
258
 
202
259
  // DXF Groupcode 76 - Underline 0 = off, 1 = on
203
- // not implemented - enumerable=false to not appear in the object props
204
- Object.defineProperty(this, 'underline', {
260
+ this.properties.add(Property.Names.UNDERLINE, {
261
+ type: Property.Type.BOOLEAN,
205
262
  value: Property.loadValue([data?.underline, Boolean(parseInt(data?.[76]))], false),
206
- writable: true,
263
+ dxfCode: 76,
264
+ visible: false,
207
265
  });
208
266
 
209
267
  // Not Implemented:
@@ -254,9 +312,9 @@ export class ArcAlignedText extends Entity {
254
312
 
255
313
  // Get the arc properties
256
314
  // direction: - ccw > 0, cw <= 0
257
- this.radius = selectedArc.radius;
258
- const startPoint = selectedArc.direction > 0 ? selectedArc.points[1] : selectedArc.points[2];
259
- const endPoint = selectedArc.direction > 0 ? selectedArc.points[2] : selectedArc.points[1];
315
+ this.setProperty(Property.Names.RADIUS, selectedArc.getProperty(Property.Names.RADIUS));
316
+ const startPoint = selectedArc.getProperty(Property.Names.DIRECTION) > 0 ? selectedArc.points[1] : selectedArc.points[2];
317
+ const endPoint = selectedArc.getProperty(Property.Names.DIRECTION) > 0 ? selectedArc.points[2] : selectedArc.points[1];
260
318
 
261
319
  // set the points
262
320
  this.points[0] = new Point(selectedArc.points[0].x, selectedArc.points[0].y);
@@ -265,26 +323,26 @@ export class ArcAlignedText extends Entity {
265
323
 
266
324
  // set the text style to the current style
267
325
  const currentStyle = DesignCore.StyleManager.getCstyle();
268
- this.styleName = currentStyle;
326
+ this.setProperty(Property.Names.STYLENAME, currentStyle);
269
327
 
270
328
  // get properties from style
271
- const style = DesignCore.StyleManager.getItemByName(this.styleName);
329
+ const style = DesignCore.StyleManager.getItemByName(this.getProperty(Property.Names.STYLENAME));
272
330
  if (style?.textHeight) {
273
- this.height = style.textHeight;
331
+ this.setProperty(Property.Names.HEIGHT, style.textHeight);
274
332
  }
275
333
 
276
334
  // Get the font size when standard style is used
277
- if (this.styleName.toUpperCase() === 'STANDARD') {
278
- const op2 = new PromptOptions(Strings.Input.HEIGHT, [Input.Type.NUMBER], [], this.height);
335
+ if (this.getProperty(Property.Names.STYLENAME).toUpperCase() === 'STANDARD') {
336
+ const op2 = new PromptOptions(Strings.Input.HEIGHT, [Input.Type.NUMBER], [], this.getProperty(Property.Names.HEIGHT));
279
337
  const height = await DesignCore.Scene.inputManager.requestInput(op2);
280
338
  if (height === undefined) return;
281
- this.height = height;
339
+ this.setProperty(Property.Names.HEIGHT, height);
282
340
  }
283
341
 
284
342
  const op4 = new PromptOptions(Strings.Input.STRING, [Input.Type.STRING, Input.Type.NUMBER]);
285
343
  const string = await DesignCore.Scene.inputManager.requestInput(op4);
286
344
  if (string === undefined) return;
287
- this.string = String(string);
345
+ this.setProperty(Property.Names.STRING, String(string));
288
346
 
289
347
  DesignCore.Scene.inputManager.executeCommand(this);
290
348
  } catch (err) {
@@ -300,15 +358,15 @@ export class ArcAlignedText extends Entity {
300
358
  if (DesignCore.Scene.inputManager.promptOption.types.includes(Input.Type.STRING)) {
301
359
  const data = {
302
360
  points: this.points,
303
- height: this.height,
361
+ height: this.getProperty(Property.Names.HEIGHT),
304
362
  rotation: this.rotation,
305
363
  startAngle: Utils.radians2degrees(this.startAngle()),
306
364
  endAngle: Utils.radians2degrees(this.endAngle()),
307
- radius: this.radius,
365
+ radius: this.getProperty(Property.Names.RADIUS),
308
366
  string: DesignCore.CommandLine.command,
309
367
  };
310
368
 
311
- DesignCore.Scene.tempEntities.create(this.type, data);
369
+ DesignCore.Scene.previewEntities.create(this.type, data);
312
370
  }
313
371
  }
314
372
  }
@@ -375,79 +433,96 @@ export class ArcAlignedText extends Entity {
375
433
  // character positions on arc
376
434
  const arcAlignedCharacters = [];
377
435
 
378
- if (this.string.length === 0) {
436
+ const str = this.getProperty(Property.Names.STRING) ?? '';
437
+ const arcSide = this.getProperty(Property.Names.ARCSIDE);
438
+ const radius = this.getProperty(Property.Names.RADIUS);
439
+ const offsetFromArc = this.getProperty(Property.Names.OFFSETFROMARC);
440
+ const height = this.getProperty(Property.Names.HEIGHT);
441
+ const offsetFromRight = this.getProperty(Property.Names.OFFSETFROMRIGHT);
442
+ const offsetFromLeft = this.getProperty(Property.Names.OFFSETFROMLEFT);
443
+ const textAlignment = this.getProperty(Property.Names.TEXTALIGNMENT);
444
+ const textOrientation = this.getProperty(Property.Names.TEXTORIENTATION);
445
+ const characterSpacing = this.getProperty(Property.Names.CHARACTERSPACING);
446
+
447
+ if (str.length === 0) {
379
448
  return arcAlignedCharacters;
380
449
  }
381
450
 
382
451
  // calculate the radial distance - Arc Side: convex = 1, concave = 2
383
- const radialDistance = this.arcSide === 2 ? this.radius - this.offsetFromArc - this.height * 0.5 : this.radius + this.offsetFromArc + this.height * 0.5;
384
- // calculate character width
385
- const charWidth = Text.getApproximateWidth(this.string[0], this.height);
386
- // calculate total char width including additional spacing
387
- const totalCharWidth = charWidth + this.characterSpacing;
388
- // convert the linear width of a character as an angle on the arc
389
- const charWidthAsAngle = this.linearToAngular(charWidth*0.5, radialDistance);
390
- // get total charwidth as angle
391
- let totalCharWidthAsAngle = this.linearToAngular(totalCharWidth*0.5, radialDistance);
392
-
393
- // calculate start and end offsets
394
- const startOffsetAngle = this.linearToAngular(this.offsetFromRight, radialDistance) + charWidthAsAngle * 0.5;
395
- const endOffsetAngle = this.linearToAngular(this.offsetFromLeft, radialDistance) + charWidthAsAngle * 0.5;
396
-
397
- // total arc angle
398
- const totalArcAngle = Math.abs(this.endAngle() - this.startAngle()) - startOffsetAngle - endOffsetAngle;
399
-
400
- // defined positions - Start and end angles +/- half char width
401
- const startPosition = this.points[0].project(this.startAngle() + startOffsetAngle, radialDistance);
402
- const endPosition = this.points[0].project(this.endAngle() - endOffsetAngle, radialDistance);
452
+ const radialDistance = arcSide === 2 ? radius - offsetFromArc - height * 0.5 : radius + offsetFromArc + height * 0.5;
453
+
454
+ // calculate start and end offsets using the first and last character widths of the original string
455
+ const firstCharWidth = Text.getApproximateWidth(str[0], height);
456
+ const lastCharWidth = Text.getApproximateWidth(str.at(-1), height);
457
+ const startOffsetAngle = this.linearToAngular(offsetFromRight, radialDistance) + this.linearToAngular(firstCharWidth * 0.5, radialDistance);
458
+ const endOffsetAngle = this.linearToAngular(offsetFromLeft, radialDistance) + this.linearToAngular(lastCharWidth * 0.5, radialDistance);
403
459
 
404
460
  // default to the arc end position as the string start
405
- let stringStartPoint = endPosition;
461
+ let stringStartPoint = this.points[0].project(this.endAngle() - endOffsetAngle, radialDistance);
406
462
  // direction: - ccw > 0, cw <= 0
407
463
  let direction = 1; // default to ccw
408
464
 
409
- let string = this.string.slice(); // make a copy of the string
465
+ let string = str.slice(); // make a copy of the string
410
466
 
411
467
  // 1 = fit to arc, 2 = left align, 3 = right align, 4 = center
412
- if (this.textAlignment === 1) { // fit to arc
413
- totalCharWidthAsAngle = totalArcAngle / (this.string.length - 1);
414
- }
415
- if (this.textAlignment === 2) { // left align
416
- // default
417
- }
418
- if (this.textAlignment === 3) { // right align
468
+ if (textAlignment === 3) { // right align
419
469
  // start at the arc start position and create the text cw around the arc
420
- stringStartPoint = startPosition;
470
+ stringStartPoint = this.points[0].project(this.startAngle() + startOffsetAngle, radialDistance);
421
471
  direction = -1;
422
- string = string.split('').reverse().join('');
423
- }
424
- if (this.textAlignment === 4) { // center
425
- const arcMidPoint = this.points[0].project(this.arcMidAngle(this.startAngle(), this.endAngle()), radialDistance);
426
- stringStartPoint = arcMidPoint.rotate(this.points[0], 0.5 * totalCharWidthAsAngle * (string.length - 1));
472
+ string = [...string].reverse().join('');
427
473
  }
428
474
 
429
475
  // calculate the text rotation angle: 1 = outward, 2 = inward
430
- const textRotationAngle = this.textOrientation === 2 ? -Math.PI*0.5 : Math.PI*0.5;
476
+ const textRotationAngle = textOrientation === 2 ? -Math.PI*0.5 : Math.PI*0.5;
431
477
 
432
- if (this.textOrientation === 2) {
478
+ if (textOrientation === 2) {
433
479
  // direction = -direction;
434
- string = string.split('').reverse().join('');
480
+ string = [...string].reverse().join('');
435
481
  }
436
482
 
437
483
  /*
438
484
  if (this.textReversed === 1) {
439
485
  // reverse the string
440
- string = string.split('').reverse().join('');
486
+ string = [...string].reverse().join('');
441
487
  // reverse the direction
442
488
  direction = -direction;
443
489
  }
444
490
  */
445
491
 
492
+ // build per-character widths for the final (possibly reversed) string
493
+ const charWidths = [...string].map((ch) => Text.getApproximateWidth(ch, height));
494
+
495
+ // build cumulative arc angle offsets from the string start position to each character center
496
+ // step between adjacent centers is the average of their two half-widths plus spacing
497
+ const charOffsetAngles = [0];
498
+ if (textAlignment === 1) { // fit to arc
499
+ if (string.length === 1) {
500
+ // single character: place at arc midpoint — division by zero otherwise
501
+ stringStartPoint = this.points[0].project(this.arcMidAngle(this.startAngle(), this.endAngle()), radialDistance);
502
+ } else {
503
+ const totalArcAngle = Math.abs(this.endAngle() - this.startAngle()) - startOffsetAngle - endOffsetAngle;
504
+ const fitStep = totalArcAngle / (string.length - 1);
505
+ for (let i = 1; i < string.length; i++) {
506
+ charOffsetAngles.push(fitStep * i);
507
+ }
508
+ }
509
+ } else {
510
+ for (let i = 0; i < string.length - 1; i++) {
511
+ const step = (charWidths[i] + charWidths[i + 1]) / 2 + characterSpacing;
512
+ charOffsetAngles.push(charOffsetAngles[i] + this.linearToAngular(step * 0.5, radialDistance));
513
+ }
514
+
515
+ if (textAlignment === 4) { // center
516
+ const arcMidPoint = this.points[0].project(this.arcMidAngle(this.startAngle(), this.endAngle()), radialDistance);
517
+ stringStartPoint = arcMidPoint.rotate(this.points[0], charOffsetAngles.at(-1) * 0.5);
518
+ }
519
+ }
520
+
446
521
  // loop through string and calculate position and angle of each character
447
522
  for (let index = 0; index < string.length; index++) {
448
- const charPosition = stringStartPoint.rotate(this.points[0], -totalCharWidthAsAngle*((index) * direction));
523
+ const charPosition = stringStartPoint.rotate(this.points[0], -charOffsetAngles[index] * direction);
449
524
  const charAngle = this.points[0].angle(charPosition) - textRotationAngle;
450
- const arcChar = new ArcAlignedCharacter(string[index], charPosition, charAngle, this.height);
525
+ const arcChar = new ArcAlignedCharacter(string[index], charPosition, charAngle, height, charWidths[index]);
451
526
  arcAlignedCharacters.push(arcChar);
452
527
  }
453
528
 
@@ -455,63 +530,26 @@ export class ArcAlignedText extends Entity {
455
530
  }
456
531
 
457
532
  /**
458
- * Draw the entity
459
- * @param {Object} ctx - context
460
- * @param {number} scale
533
+ * Return a character descriptor array for use with renderer.drawText().
534
+ * Coordinates are in scene space (Y-up). The renderer handles the Y-flip.
535
+ * @return {Array}
461
536
  */
462
- draw(ctx, scale) {
463
- ctx.save(); // save current context before scale and translate
464
- ctx.scale(1, -1);
465
-
466
- const style = DesignCore.StyleManager.getItemByName(this.styleName);
467
-
468
- // Cairo
469
- ctx.setFontSize(this.height);
470
- ctx.selectFontFace(style?.font, null, null); // (FontName, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL);
471
-
472
- const ArcAlignedCharacters = this.getArcAlignedCharacters();
473
-
474
- for (const arcAlignedChar of ArcAlignedCharacters) {
475
- ctx.save(); // save current context
476
- ctx.translate(arcAlignedChar.baseline.x, -arcAlignedChar.baseline.y);
477
- ctx.rotate(-arcAlignedChar.angle);
478
- ctx.showText(String(arcAlignedChar.character));
479
-
480
- ctx.stroke();
481
- ctx.restore(); // restore context
482
-
483
- /*
484
- // debug draw character bounding box
485
- const bb = arcAlignedChar.boundingBox;
486
- const center = arcAlignedChar.position;
487
-
488
- const posOne = new Point(center.x - bb.xLength*0.5, center.y - bb.yLength*0.5).rotate(arcAlignedChar.position, arcAlignedChar.angle);
489
- const posTwo = new Point(center.x + bb.xLength*0.5, center.y - bb.yLength*0.5).rotate(arcAlignedChar.position, arcAlignedChar.angle);
490
- const posThree = new Point(center.x + bb.xLength*0.5, center.y + bb.yLength*0.5).rotate(arcAlignedChar.position, arcAlignedChar.angle);
491
- const posFour = new Point(center.x - bb.xLength*0.5, center.y + bb.yLength*0.5).rotate(arcAlignedChar.position, arcAlignedChar.angle);
492
-
493
- ctx.moveTo(posOne.x, -posOne.y);
494
- ctx.lineTo(posTwo.x, -posTwo.y);
495
- ctx.lineTo(posThree.x, -posThree.y);
496
- ctx.lineTo(posFour.x, -posFour.y);
497
- ctx.lineTo(posOne.x, -posOne.y);
498
- ctx.stroke();
499
- */
500
-
501
- /*
502
- // debug draw the arcText bounding box
503
- const bb = this.boundingBox();
504
- ctx.moveTo(bb.xMin, -bb.yMin);
505
- ctx.lineTo(bb.xMax, -bb.yMin);
506
- ctx.lineTo(bb.xMax, -bb.yMax);
507
- ctx.lineTo(bb.xMin, -bb.yMax);
508
- ctx.lineTo(bb.xMin, -bb.yMin);
509
- ctx.stroke();
510
- */
511
- }
537
+ toCharacters() {
538
+ return this.getArcAlignedCharacters().map((ch) => ({
539
+ x: ch.baseline.x,
540
+ y: ch.baseline.y,
541
+ rotation: ch.angle,
542
+ char: ch.character,
543
+ }));
544
+ }
512
545
 
513
- ctx.stroke();
514
- ctx.restore(); // restore context before scale and translate
546
+ /**
547
+ * Draw the entity
548
+ * @param {Object} renderer
549
+ */
550
+ draw(renderer) {
551
+ const style = DesignCore.StyleManager.getItemByName(this.getProperty(Property.Names.STYLENAME));
552
+ renderer.drawText(this.toCharacters(), style?.font, this.getProperty(Property.Names.HEIGHT));
515
553
  }
516
554
 
517
555
  /**
@@ -519,36 +557,36 @@ export class ArcAlignedText extends Entity {
519
557
  * @param {DXFFile} file
520
558
  */
521
559
  dxf(file) {
522
- const style = DesignCore.StyleManager.getItemByName(this.styleName);
560
+ const style = DesignCore.StyleManager.getItemByName(this.getProperty(Property.Names.STYLENAME));
523
561
 
524
562
  file.writeGroupCode('0', 'ARCALIGNEDTEXT');
525
- file.writeGroupCode('5', this.handle, DXFFile.Version.R2000); // Handle
563
+ file.writeGroupCode('5', this.getProperty(Property.Names.HANDLE), DXFFile.Version.R2000); // Handle
526
564
  file.writeGroupCode('100', 'AcDbEntity', DXFFile.Version.R2000);
527
- file.writeGroupCode('8', this.layer);
565
+ file.writeGroupCode('8', this.getProperty(Property.Names.LAYER));
528
566
  file.writeGroupCode('100', 'AcDbArcAlignedText', DXFFile.Version.R2000);
529
- file.writeGroupCode('1', this.string);
567
+ file.writeGroupCode('1', this.getProperty(Property.Names.STRING));
530
568
  file.writeGroupCode('2', style?.font);
531
569
  file.writeGroupCode('3', '');
532
- file.writeGroupCode('7', this.styleName); // Test style name
570
+ file.writeGroupCode('7', this.getProperty(Property.Names.STYLENAME)); // Test style name
533
571
  file.writeGroupCode('10', this.points[0].x); // x of arc center
534
572
  file.writeGroupCode('20', this.points[0].y); // y of arc center
535
573
  file.writeGroupCode('30', '0.0'); // z of arc center
536
- file.writeGroupCode('40', this.radius);
537
- file.writeGroupCode('41', this.widthFactor);
538
- file.writeGroupCode('42', this.height);
539
- file.writeGroupCode('43', this.characterSpacing);
540
- file.writeGroupCode('44', this.offsetFromArc);
541
- file.writeGroupCode('45', this.offsetFromRight);
542
- file.writeGroupCode('46', this.offsetFromLeft);
574
+ file.writeGroupCode('40', this.getProperty(Property.Names.RADIUS));
575
+ file.writeGroupCode('41', this.getProperty(Property.Names.WIDTHFACTOR));
576
+ file.writeGroupCode('42', this.getProperty(Property.Names.HEIGHT));
577
+ file.writeGroupCode('43', this.getProperty(Property.Names.CHARACTERSPACING));
578
+ file.writeGroupCode('44', this.getProperty(Property.Names.OFFSETFROMARC));
579
+ file.writeGroupCode('45', this.getProperty(Property.Names.OFFSETFROMRIGHT));
580
+ file.writeGroupCode('46', this.getProperty(Property.Names.OFFSETFROMLEFT));
543
581
  file.writeGroupCode('50', Utils.radians2degrees(this.startAngle()));
544
582
  file.writeGroupCode('51', Utils.radians2degrees(this.endAngle()));
545
- file.writeGroupCode('70', this.textReversed ? 1 : 0); // Text direction
546
- file.writeGroupCode('71', this.textOrientation); // Text orientation
547
- file.writeGroupCode('72', this.textAlignment); // Text alignment
548
- file.writeGroupCode('73', this.arcSide); // Arc side
549
- file.writeGroupCode('74', this.bold ? 1 : 0);
550
- file.writeGroupCode('75', this.italic ? 1 : 0);
551
- file.writeGroupCode('76', this.underline ? 1 : 0);
583
+ file.writeGroupCode('70', this.getProperty(Property.Names.TEXTREVERSED) ? 1 : 0); // Text direction
584
+ file.writeGroupCode('71', this.getProperty(Property.Names.TEXTORIENTATION)); // Text orientation
585
+ file.writeGroupCode('72', this.getProperty(Property.Names.TEXTALIGNMENT)); // Text alignment
586
+ file.writeGroupCode('73', this.getProperty(Property.Names.ARCSIDE)); // Arc side
587
+ file.writeGroupCode('74', this.getProperty(Property.Names.BOLD) ? 1 : 0);
588
+ file.writeGroupCode('75', this.getProperty(Property.Names.ITALIC) ? 1 : 0);
589
+ file.writeGroupCode('76', this.getProperty(Property.Names.UNDERLINE) ? 1 : 0);
552
590
  file.writeGroupCode('77', 0);
553
591
  file.writeGroupCode('78', 34);
554
592
  file.writeGroupCode('79', 0);