@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
@@ -1,15 +1,25 @@
1
- import {Point} from './point.js';
2
- import {Utils} from '../lib/utils.js';
3
- import {Strings} from '../lib/strings.js';
4
- import {Entity} from './entity.js';
5
- import {Input, PromptOptions} from '../lib/inputManager.js';
6
- import {Logging} from '../lib/logging.js';
7
- import {DXFFile} from '../lib/dxf/dxfFile.js';
8
- import {BoundingBox} from '../lib/boundingBox.js';
9
-
10
- import {DesignCore} from '../designCore.js';
11
-
1
+ import { Point } from './point.js';
2
+ import { Utils } from '../lib/utils.js';
3
+ import { Strings } from '../lib/strings.js';
4
+ import { Entity } from './entity.js';
5
+ import { Input, PromptOptions } from '../lib/inputManager.js';
6
+ import { Logging } from '../lib/logging.js';
7
+ import { DXFFile } from '../lib/dxf/dxfFile.js';
8
+ import { BoundingBox } from '../lib/boundingBox.js';
9
+ import { Flags } from '../properties/flags.js';
10
+ import { Property } from '../properties/property.js';
11
+
12
+ import { DesignCore } from '../designCore.js';
13
+
14
+ /**
15
+ * Text Entity Class
16
+ * @extends Entity
17
+ */
12
18
  export class Text extends Entity {
19
+ /**
20
+ * Create a Text Entity
21
+ * @param {Array} data
22
+ */
13
23
  constructor(data) {
14
24
  super(data);
15
25
  this.string = '';
@@ -18,8 +28,7 @@ export class Text extends Entity {
18
28
  this.verticalAlignment = 0;
19
29
  this.styleName = 'STANDARD';
20
30
 
21
-
22
- // hide inherited propertys
31
+ // hide inherited properties
23
32
  // needs to be enumerable=false to not appear in the object props
24
33
  Object.defineProperty(this, 'lineType', {
25
34
  enumerable: false,
@@ -39,13 +48,13 @@ export class Text extends Entity {
39
48
 
40
49
  // needs to be non-enumerable as to not appear in the object props
41
50
  Object.defineProperty(this, 'boundingRect', {
42
- value: {width: 10, height: 10},
51
+ value: { width: 10, height: 10 },
43
52
  writable: true,
44
53
  });
45
54
 
46
55
  // needs to be non-enumerable as to not appear in the object props
47
56
  Object.defineProperty(this, 'flags', {
48
- value: 0,
57
+ value: new Flags(),
49
58
  writable: true,
50
59
  });
51
60
 
@@ -67,67 +76,94 @@ export class Text extends Entity {
67
76
  });
68
77
 
69
78
  if (data) {
70
- if (data.string || data[1]) {
71
- // DXF Groupcode 1 - Default Value
72
- // The string of the text entity
73
- this.string = String(data.string || data[1]);
74
- }
75
-
76
- if (data.styleName || data[7]) {
77
- // DXF Groupcode 7 - Text Style Name
78
- this.styleName = data.styleName || data[7];
79
+ // DXF Groupcode 11,21,31 - Second alignment point (optional, used for aligned, middle, or fit text)
80
+ if (data?.points?.length > 1) {
81
+ if (data.points[1].sequence == 11) {
82
+ this.points = [];
83
+ this.points.push(new Point(data.points[1].x, data.points[1].y));
84
+ }
79
85
  }
86
+ }
80
87
 
81
- if (data.height || data[40]) {
82
- // DXF Groupcode 40 - Text Height
83
- this.height = data.height || data[40];
84
- }
88
+ // DXF Groupcode 1 - Text String
89
+ this.string = Property.loadValue([data?.string, data?.[1]], '');
90
+ // DXF Groupcode 7 - Text Style Name
91
+ this.styleName = Property.loadValue([data?.styleName, data?.[7]], 'STANDARD');
92
+ // DXF Groupcode 40 - Text Height
93
+ this.height = Property.loadValue([data?.height, data?.[40]], 2.5);
85
94
 
86
- if (data.rotation || data[50]) {
95
+ if (data) {
96
+ if (data.hasOwnProperty('rotation') || data.hasOwnProperty('50')) {
87
97
  // DXF Groupcode 50 - Text Rotation, angle in degrees
88
98
  // if we get rotation data store this as a point[1] at an angle from point[0]
89
99
  // this allows all the entities to be rotated by rotating the points i.e. not all entities have a rotation property
90
-
91
- const rotation = data.rotation || data[50];
92
- this.setRotation(rotation);
100
+ this.setRotation(Property.loadValue([data.rotation, data[50]], 0));
93
101
  } else {
94
102
  // create points[1] used to determine the text rotation
95
- this.points[1] = data.points[0].add(new Point(this.height, 0));
103
+ if (this.points.length && this.height !== undefined) {
104
+ this.points[1] = this.points[0].add(new Point(this.height, 0));
105
+ }
96
106
  }
107
+ }
97
108
 
98
- if (data.horizontalAlignment || data[72]) {
99
- // DXF Groupcode 72 - Horizontal Alignment
100
- // 0 = Left; 1= Center; 2 = Right
101
- // 3 = Aligned (if vertical alignment = 0)
102
- // 4 = Middle (if vertical alignment = 0)
103
- // 5 = Fit (if vertical alignment = 0)
104
- this.horizontalAlignment = data.horizontalAlignment || data[72];
105
- }
109
+ // DXF Groupcode 72 - Horizontal Alignment
110
+ // 0 = Left
111
+ // 1 = Center
112
+ // 2 = Right
113
+ // 3 = Aligned (if vertical alignment = 0) not supported, treated as center aligned
114
+ // 4 = Middle (if vertical alignment = 0) not supported, treated as center aligned
115
+ // 5 = Fit (if vertical alignment = 0) not supported, treated as center aligned
116
+ this.horizontalAlignment = Property.loadValue([data?.horizontalAlignment, data?.[72]], 0);
117
+
118
+ if (this.horizontalAlignment > 2) {
119
+ this.horizontalAlignment = 1; // unsupported alignment types treated as center aligned
120
+ }
106
121
 
107
- if (data.verticalAlignment || data[73]) {
108
- // DXF Groupcode 73 - Vertical Alignment
109
- // 0 = Baseline; 1 = Bottom; 2 = Middle; 3 = Top
110
- this.verticalAlignment = data.verticalAlignment || data[73];
111
- }
122
+ // DXF Groupcode 73 - Vertical Alignment
123
+ // 0 = Baseline; 1 = Bottom; 2 = Middle; 3 = Top
124
+ this.verticalAlignment = Property.loadValue([data?.verticalAlignment, data?.[73]], 0);
112
125
 
113
- if (data.flags || data[71]) {
114
- // DXF Groupcode 71 - flags (bit-coded values):
115
- // 2 = Text is backward (mirrored in X).
116
- // 4 = Text is upside down (mirrored in Y).
117
- this.flags = data.flags || data[71];
118
- }
119
- }
126
+ // DXF Groupcode 71 - flags (bit-coded values):
127
+ // 2 = Text is backward (mirrored in X).
128
+ // 4 = Text is upside down (mirrored in Y).
129
+ this.flags.setFlagValue(Property.loadValue([data?.flags, data?.[71]], 0));
120
130
  }
121
131
 
132
+ /**
133
+ * Register the command
134
+ * @return {Object}
135
+ * command = name of the command
136
+ * shortcut = shortcut for the command
137
+ * type = type to group command in toolbars (omitted if not shown)
138
+ */
122
139
  static register() {
123
- const command = {command: 'Text', shortcut: 'DT', type: 'Entity'};
140
+ const command = { command: 'Text', shortcut: 'DT', type: 'Entity' };
124
141
  return command;
125
142
  }
126
143
 
144
+ /**
145
+ * Get the approximate width of the text
146
+ * @param {string} string - text string
147
+ * @param {number} textHeight - text height
148
+ * @return {number} - approximate width of the text
149
+ * This is a rough estimate based on the string length and height
150
+ * Actual width depends on the font and style used
151
+ */
152
+ static getApproximateWidth(string, textHeight) {
153
+ // Approximate width of the text based on the string length and height
154
+ // This is a rough estimate, as actual width depends on the font and style
155
+ return string.length * textHeight * 0.6; // 0.6 is an approximation factor for average character width
156
+ }
157
+
158
+ /**
159
+ * Execute method
160
+ * executes the workflow, requesting input required to create an entity
161
+ */
127
162
  async execute() {
128
163
  try {
129
164
  const op = new PromptOptions(Strings.Input.START, [Input.Type.POINT]);
130
165
  const pt1 = await DesignCore.Scene.inputManager.requestInput(op);
166
+ if (pt1 === undefined) return;
131
167
  this.points.push(pt1);
132
168
 
133
169
  // set the text style to the current style
@@ -135,27 +171,41 @@ export class Text extends Entity {
135
171
  this.styleName = currentStyle;
136
172
 
137
173
  // get properties from style
138
- const style = DesignCore.StyleManager.getStyleByName(this.styleName);
139
- if (style.textHeight) {
174
+ const style = DesignCore.StyleManager.getItemByName(this.styleName);
175
+ if (style?.textHeight) {
140
176
  this.height = style.textHeight;
141
177
  }
142
178
 
143
179
  this.backwards = style.backwards;
144
180
  this.upsideDown = style.upsideDown;
145
181
 
146
- // Get the font size when standard style is used
147
- if (this.styleName.toUpperCase() === 'STANDARD') {
148
- const op2 = new PromptOptions(`${Strings.Input.HEIGHT} <${this.height}>`, [Input.Type.NUMBER]);
149
- const height = await DesignCore.Scene.inputManager.requestInput(op2);
150
- this.height = height;
182
+ // Get the font size when the style has a variable height (textHeight === 0)
183
+ if (!style?.textHeight) {
184
+ const op2 = new PromptOptions(Strings.Input.HEIGHT, [Input.Type.NUMBER], [], this.height);
185
+ while (true) {
186
+ const height = await DesignCore.Scene.inputManager.requestInput(op2);
187
+ if (height === undefined) return;
188
+ if (height <= 0) {
189
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.NONZERO}`);
190
+ continue;
191
+ }
192
+ this.height = height;
193
+ break;
194
+ }
151
195
  }
152
196
 
153
- const op3 = new PromptOptions(`${Strings.Input.ROTATION} <0>`, [Input.Type.NUMBER]);
197
+ const op3 = new PromptOptions(Strings.Input.ROTATION, [Input.Type.NUMBER], [], 0);
154
198
  const rotation = await DesignCore.Scene.inputManager.requestInput(op3);
199
+ if (rotation === undefined) return;
155
200
  this.setRotation(rotation);
156
201
 
157
202
  const op4 = new PromptOptions(Strings.Input.STRING, [Input.Type.STRING, Input.Type.NUMBER]);
158
203
  const string = await DesignCore.Scene.inputManager.requestInput(op4);
204
+ if (string === undefined) return;
205
+ if (String(string).trim().length === 0) {
206
+ DesignCore.Scene.inputManager.reset();
207
+ return;
208
+ }
159
209
  this.string = String(string);
160
210
 
161
211
  DesignCore.Scene.inputManager.executeCommand(this);
@@ -164,6 +214,9 @@ export class Text extends Entity {
164
214
  }
165
215
  }
166
216
 
217
+ /**
218
+ * Preview the entity during creation
219
+ */
167
220
  preview() {
168
221
  if (this.points.length >= 1) {
169
222
  if (DesignCore.Scene.inputManager.promptOption.types.includes(Input.Type.STRING)) {
@@ -171,14 +224,15 @@ export class Text extends Entity {
171
224
  points: this.points,
172
225
  height: this.height,
173
226
  rotation: this.rotation,
227
+ styleName: this.styleName,
174
228
  string: DesignCore.CommandLine.command,
175
229
  };
176
230
 
177
- DesignCore.Scene.createTempItem(this.type, data);
231
+ DesignCore.Scene.tempEntities.create(this.type, data);
178
232
  } else {
179
233
  const mousePoint = DesignCore.Mouse.pointOnScene();
180
234
  const points = [this.points.at(-1), mousePoint];
181
- DesignCore.Scene.createTempItem('Line', {points: points});
235
+ DesignCore.Scene.tempEntities.create('Line', { points: points });
182
236
  }
183
237
  }
184
238
  }
@@ -194,7 +248,7 @@ export class Text extends Entity {
194
248
  return;
195
249
  }
196
250
 
197
- if (this.height > 0 && angle !== 0) {
251
+ if (this.height > 0) {
198
252
  this.points[1] = this.points[0].project(Utils.degrees2radians(angle), this.height);
199
253
  }
200
254
  }
@@ -214,11 +268,11 @@ export class Text extends Entity {
214
268
 
215
269
  /**
216
270
  * Get the backwards value
217
- * @returns {boolean} true if the text is flipped horizontally
271
+ * @return {boolean} true if the text is flipped horizontally
218
272
  */
219
273
  getBackwards() {
220
274
  // Backwards value is bitmasked in flags as value 2
221
- return Boolean(this.flags & 2);
275
+ return this.flags.hasFlag(2);
222
276
  }
223
277
 
224
278
  /**
@@ -228,20 +282,20 @@ export class Text extends Entity {
228
282
  setBackwards(bool) {
229
283
  if (bool) {
230
284
  // Add flag
231
- this.flags = (this.flags | 2);
285
+ this.flags.addValue(2);
232
286
  } else {
233
287
  // remove flag
234
- this.flags = (this.flags ^ (this.flags & 2));
288
+ this.flags.removeValue(2);
235
289
  }
236
290
  }
237
291
 
238
292
  /**
239
293
  * Get the upside down value
240
- * @returns {boolean} true if the text is flipped vertically
294
+ * @return {boolean} true if the text is flipped vertically
241
295
  */
242
296
  getUpsideDown() {
243
297
  // Upside down value is bitmasked in flags as value 4
244
- return Boolean(this.flags & 4);
298
+ return this.flags.hasFlag(4);
245
299
  }
246
300
 
247
301
  /**
@@ -251,16 +305,16 @@ export class Text extends Entity {
251
305
  setUpsideDown(bool) {
252
306
  if (bool) {
253
307
  // Add flag
254
- this.flags = (this.flags | 4);
308
+ this.flags.addValue(4);
255
309
  } else {
256
310
  // remove flag
257
- this.flags = (this.flags ^ (this.flags & 4));
311
+ this.flags.removeValue(4);
258
312
  }
259
313
  }
260
314
 
261
315
  /**
262
316
  * Get a string describing the horizontal text alignment
263
- * @returns {string}
317
+ * @return {string}
264
318
  */
265
319
  getHorizontalAlignment() {
266
320
  /* DXF Data
@@ -290,7 +344,7 @@ export class Text extends Entity {
290
344
 
291
345
  /**
292
346
  * Get a string describing the vertical text alignment
293
- * @returns {string}
347
+ * @return {string}
294
348
  */
295
349
  getVerticalAlignment() {
296
350
  /* DXF Data
@@ -313,177 +367,266 @@ export class Text extends Entity {
313
367
  }
314
368
  }
315
369
 
370
+ /**
371
+ * Get the texts bounding rectangle
372
+ * @return {Object}
373
+ */
316
374
  getBoundingRect() {
317
- const rect = {width: Number(this.boundingRect.width), height: Number(this.boundingRect.height), x: this.points[0].x, y: this.points[0].y};
375
+ const rect = { width: Number(this.boundingRect.width), height: Number(this.boundingRect.height), x: this.points[0].x, y: this.points[0].y };
318
376
  return rect;
319
377
  }
320
378
 
379
+ /**
380
+ * Draw the entity
381
+ * @param {Object} ctx - context
382
+ * @param {number} scale
383
+ */
321
384
  draw(ctx, scale) {
322
- ctx.save();
385
+ if (this.string.length === 0) {
386
+ return;
387
+ }
388
+
389
+ ctx.save(); // save current context before scale and translate
323
390
  ctx.scale(1, -1);
324
- ctx.translate(this.points[0].x, -this.points[0].y);
391
+ const corners = this.getTextFrameCorners();
392
+ const bottomLeft = corners[0];
393
+ ctx.translate(bottomLeft.x, -bottomLeft.y);
325
394
 
326
- const style = DesignCore.StyleManager.getStyleByName(this.styleName);
327
- // style.textHeight
395
+ const style = DesignCore.StyleManager.getItemByName(this.styleName);
396
+ const rotation = Utils.degrees2radians(this.rotation);
397
+ ctx.rotate(-rotation);
328
398
 
329
399
  if (this.upsideDown) {
400
+ const upsideDownOffset = corners[0].distance(corners[3]);
401
+ ctx.translate(0, -upsideDownOffset);
330
402
  ctx.scale(1, -1);
331
403
  }
332
404
 
333
405
  if (this.backwards) {
406
+ const backwardsOffset = corners[0].distance(corners[1]);
407
+ ctx.translate(backwardsOffset, 0);
334
408
  ctx.scale(-1, 1);
335
409
  }
336
410
 
337
- const rotation = Utils.degrees2radians(this.rotation);
338
-
339
- if (this.backwards || this.upsideDown) {
340
- ctx.rotate(rotation);
341
- } else {
342
- ctx.rotate(-rotation);
343
- }
344
-
345
411
  try { // HTML
346
- ctx.textAlign = this.getHorizontalAlignment();
347
- ctx.textBaseline = this.getVerticalAlignment();
348
- ctx.font = this.height + 'pt Arial'; // +DesignCore.StyleManager.getStyleByName(this.styleName).font.toString();
412
+ ctx.font = this.height + 'pt ' + style.font;
349
413
  ctx.fillText(this.string, 0, 0);
350
- this.boundingRect = ctx.measureText(String(this.string));
351
414
  // TODO: find a better way to define the boundingRect
352
- this.boundingRect.height = this.height;
415
+ const metrics = ctx.measureText(String(this.string));
416
+ this.boundingRect = { width: metrics.width, height: this.height };
353
417
  } catch { // Cairo
354
- ctx.setFontSize(this.height);
355
418
  ctx.selectFontFace(style.font, null, null); // (FontName, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL);
356
- this.boundingRect = ctx.textExtents(String(this.string));
357
-
358
- // console.log(this.boundingRect);
359
-
360
- let x = 0;
361
- let y = 0;
362
- switch (this.horizontalAlignment) {
363
- case 0: // 0 = Left;
364
- x = -this.boundingRect.xBearing;
365
- break;
366
- case 1: // 1= Center;
367
- x = -this.boundingRect.xBearing-this.boundingRect.width / 2;
368
- break;
369
- case 2: // 2 = Right
370
- x = -this.boundingRect.xBearing-this.boundingRect.width;
371
- break;
372
- }
373
-
374
- switch (this.verticalAlignment) {
375
- case 0: // 0 = Baseline;
376
- y = 0;
377
- break;
378
- case 1: // 1 = Bottom;
379
- y = -this.boundingRect.yBearing - this.boundingRect.height;
380
- break;
381
- case 2: // 2 = Middle
382
- y = -this.boundingRect.yBearing - this.boundingRect.height / 2;
383
- break;
384
- case 3: // 3 = Top
385
- y = -this.boundingRect.yBearing;
386
- break;
387
- }
388
419
 
420
+ // Hack to test the text height vs bounding box height to find a scale factor to make the drawn text match the specified height.
421
+ // This is needed because Cairo's font size is not the same as the actual drawn text height, and can vary based on the font used.
422
+ // This is a rough approximation and may not be accurate for all fonts or sizes.
423
+ ctx.setFontSize(this.height);
424
+ const drawTextRect = ctx.textExtents(String(this.string));
425
+
426
+ /* GJS Cairo needs additional support for font handling
427
+ https://www.cairographics.org/tutorial/#L1understandingtext
428
+ https://www.cairographics.org/manual/cairo-cairo-scaled-font-t.html#cairo-font-extents-t
429
+
430
+ This needs to be implemented in GJS:
431
+ https://gitlab.gnome.org/GNOME/gjs/-/blob/master/modules/cairo-context.cpp?ref_type=heads#L773
432
+ https://gitlab.gnome.org/GNOME/gjs/-/blob/master/modules/cairo-context.cpp?ref_type=heads#L780
433
+ */
434
+ // Adjust the font size by the ratio of the desired height to the drawn height to get closer to the desired text height.
435
+ ctx.setFontSize(this.height * this.height / drawTextRect.height);
436
+ this.boundingRect = ctx.textExtents(String(this.string));
389
437
 
390
- ctx.moveTo(x, y);
391
438
  ctx.showText(String(this.string));
392
439
  }
440
+
393
441
  ctx.stroke();
394
- ctx.restore();
442
+ ctx.restore(); // restore context before scale and translate
395
443
 
396
- // Draw Bounding Box to test the getBoundingRect()
397
444
  /*
398
- ctx.strokeStyle = colour;
399
- ctx.lineWidth = 1 / scale;
400
- ctx.beginPath()
401
- ctx.moveTo(rect.x, rect.y);
402
- ctx.lineTo(rect.x + rect.width, rect.y);
403
- ctx.lineTo(rect.x + rect.width, rect.y + rect.height);
404
- ctx.lineTo(rect.x, rect.y + rect.height);
405
- ctx.lineTo(rect.x, rect.y);
406
- ctx.stroke()
407
- */
445
+ // debug draw the bounding box
446
+ const bb = this.boundingBox();
447
+ ctx.moveTo(bb.xMin, bb.yMin);
448
+ ctx.lineTo(bb.xMax, bb.yMin);
449
+ ctx.lineTo(bb.xMax, bb.yMax);
450
+ ctx.lineTo(bb.xMin, bb.yMax);
451
+ ctx.lineTo(bb.xMin, bb.yMin);
452
+ ctx.stroke();
453
+
454
+ // debug draw the text frame
455
+ const frameCorners = this.getTextFrameCorners();
456
+ ctx.moveTo(frameCorners[0].x, frameCorners[0].y);
457
+ ctx.lineTo(frameCorners[1].x, frameCorners[1].y);
458
+ ctx.lineTo(frameCorners[2].x, frameCorners[2].y);
459
+ ctx.lineTo(frameCorners[3].x, frameCorners[3].y);
460
+ ctx.lineTo(frameCorners[0].x, frameCorners[0].y);
461
+ ctx.stroke();
462
+ */
408
463
  }
409
464
 
465
+ /**
466
+ * Write the entity to file in the dxf format
467
+ * @param {DXFFile} file
468
+ */
410
469
  dxf(file) {
411
470
  file.writeGroupCode('0', 'TEXT');
412
- file.writeGroupCode('5', file.nextHandle(), DXFFile.Version.R2000); // Handle
471
+ file.writeGroupCode('5', this.handle, DXFFile.Version.R2000); // Handle
413
472
  file.writeGroupCode('100', 'AcDbEntity', DXFFile.Version.R2000);
414
473
  file.writeGroupCode('8', this.layer);
415
474
  file.writeGroupCode('100', 'AcDbText', DXFFile.Version.R2000);
416
- file.writeGroupCode('10', this.points[0].x);
417
- file.writeGroupCode('20', this.points[0].y);
475
+
476
+ const frameCorners = this.getTextFrameCorners();
477
+ const bottomLeft = frameCorners[0];
478
+
479
+ file.writeGroupCode('10', bottomLeft.x);
480
+ file.writeGroupCode('20', bottomLeft.y);
418
481
  file.writeGroupCode('30', '0.0');
482
+
419
483
  file.writeGroupCode('40', this.height);
420
484
  file.writeGroupCode('1', this.string);
421
485
  file.writeGroupCode('50', this.rotation);
422
486
  // file.writeGroupCode('7', 'STANDARD'); // TEXT STYLE
423
- file.writeGroupCode('71', this.flags); // Text generation flags
487
+ file.writeGroupCode('71', this.flags.getFlagValue()); // Text generation flags
424
488
  file.writeGroupCode('72', this.horizontalAlignment); // Horizontal alignment
489
+
490
+ if (this.horizontalAlignment > 0 || this.verticalAlignment > 0) {
491
+ file.writeGroupCode('11', this.points[0].x);
492
+ file.writeGroupCode('21', this.points[0].y);
493
+ file.writeGroupCode('31', '0.0');
494
+ }
495
+
425
496
  file.writeGroupCode('100', 'AcDbText', DXFFile.Version.R2000);
426
- file.writeGroupCode('73', this.verticalAlignment); // Vertical alignment
497
+ if (this.verticalAlignment !== 0) {
498
+ file.writeGroupCode('73', this.verticalAlignment); // Vertical alignment
499
+ }
427
500
  }
428
501
 
502
+ /**
503
+ * Get snap points
504
+ * @param {Point} mousePoint
505
+ * @param {number} delta
506
+ * @return {Array} - array of snap points
507
+ */
429
508
  snaps(mousePoint, delta) {
430
- const rect = this.getBoundingRect();
431
-
432
- const botLeft = new Point(rect.x, rect.y);
433
- const botRight = new Point(rect.x + rect.width, rect.y);
434
- const topLeft = new Point(rect.x, rect.y + rect.height);
435
- const topRight = new Point(rect.x + rect.width, rect.y + rect.height);
436
- const mid = new Point(rect.x + rect.width / 2, rect.y + rect.height / 2);
437
-
438
- const snaps = [botLeft, botRight, topLeft, topRight, mid];
509
+ const frameCorners = this.getTextFrameCorners();
510
+ const mid = frameCorners[0].midPoint(frameCorners[2]);
511
+ const snaps = [...frameCorners, mid];
512
+
513
+ // add insertion point if not already in snaps
514
+ // this adds an extra snap point when alignment != 0.
515
+ if (!snaps.some((point) => point.isSame(this.points[0]))) {
516
+ snaps.push(this.points[0]);
517
+ }
439
518
 
440
519
  return snaps;
441
520
  }
442
521
 
522
+ /**
523
+ * Get closest point on entity
524
+ * @param {Point} P
525
+ * @return {Array} - [Point, distance]
526
+ */
443
527
  closestPoint(P) {
444
- // TODO: Support rotation
445
- const rect = this.getBoundingRect();
446
- const botLeft = new Point(rect.x, rect.y);
447
- const topRight = new Point(rect.x + rect.width, rect.y + rect.height);
448
- const mid = new Point(rect.x + rect.width / 2, rect.y + rect.height / 2);
528
+ const frameCorners = this.getTextFrameCorners();
529
+ const A = frameCorners[0];
530
+ const B = frameCorners[1];
531
+ const C = frameCorners[2];
532
+ const D = frameCorners[3];
449
533
 
534
+ const mid = A.midPoint(C);
450
535
  let distance = P.distance(mid);
451
536
 
452
- // if P is inside the bounding box return distance 0
453
- if (P.x > botLeft.x &&
454
- P.x < topRight.x &&
455
- P.y > botLeft.y &&
456
- P.y < topRight.y
457
- ) {
537
+ if (P.isInRectangle(A, B, C, D)) {
458
538
  distance = 0;
459
539
  }
460
540
 
461
541
  return [mid, distance];
462
542
  }
463
543
 
464
- boundingBox() {
544
+ /**
545
+ * Get the corner points of a frame around the text (Tight bounding box)
546
+ * @return {Array} - array of 4 Points defining the text frame corners. Ordered: [bottomLeft, bottomRight, topRight, topLeft]
547
+ */
548
+ getTextFrameCorners() {
465
549
  const rect = this.getBoundingRect();
466
550
 
467
- const xmin = rect.x;
468
- const xmax = rect.x + rect.width;
469
- const ymin = rect.y;
470
- const ymax = rect.y + rect.height;
551
+ let offsetX = 0;
552
+ switch (this.horizontalAlignment) {
553
+ case 0: // left
554
+ offsetX = 0;
555
+ break;
556
+ case 1: // center
557
+ offsetX = -rect.width / 2;
558
+ break;
559
+ case 2: // right
560
+ offsetX = -rect.width;
561
+ break;
562
+ case 3: // aligned - not supported
563
+ case 4: // middle - not supported
564
+ case 5: // fit - not supported
565
+ default:
566
+ offsetX = 0;
567
+ }
471
568
 
472
- const topLeft = new Point(xmin, ymax);
473
- const bottomRight = new Point(xmax, ymin);
569
+ let offsetY = 0;
570
+ switch (this.verticalAlignment) {
571
+ case 0: // baseline
572
+ offsetY = 0; // see comments in draw method
573
+ break;
574
+ case 1: // bottom
575
+ offsetY = 0;
576
+ break;
577
+ case 2: // middle
578
+ offsetY = -rect.height / 2;
579
+ break;
580
+ case 3: // top
581
+ offsetY = -rect.height;
582
+ break;
583
+ default:
584
+ offsetY = 0;
585
+ }
474
586
 
475
- return new BoundingBox(topLeft, bottomRight);
476
- }
587
+ // apply offsets to insertion point
588
+ const x0 = rect.x + offsetX;
589
+ const y0 = rect.y + offsetY;
590
+
591
+ // compute min/max depending on backwards/upsideDown (text direction)
592
+ const xmin = Math.min(x0, this.backwards ? x0 - rect.width : x0 + rect.width);
593
+ const xmax = Math.max(x0, this.backwards ? x0 - rect.width : x0 + rect.width);
594
+ const ymin = Math.min(y0, this.upsideDown ? y0 - rect.height : y0 + rect.height);
595
+ const ymax = Math.max(y0, this.upsideDown ? y0 - rect.height : y0 + rect.height);
596
+
597
+ let bottomLeft = new Point(xmin, ymin);
598
+ let bottomRight = new Point(xmax, ymin);
599
+ let topLeft = new Point(xmin, ymax);
600
+ let topRight = new Point(xmax, ymax);
601
+
602
+ if (this.rotation !== 0) {
603
+ const angle = Utils.degrees2radians(this.rotation);
604
+ bottomLeft = bottomLeft.rotate(this.points[0], angle);
605
+ bottomRight = bottomRight.rotate(this.points[0], angle);
606
+ topLeft = topLeft.rotate(this.points[0], angle);
607
+ topRight = topRight.rotate(this.points[0], angle);
608
+ }
477
609
 
478
- intersectPoints() {
479
- const rect = this.getBoundingRect();
610
+ return [bottomLeft, bottomRight, topRight, topLeft];
611
+ }
480
612
 
481
- const botLeft = new Point(rect.x, rect.y);
482
- const topRight = new Point(rect.x + rect.width, rect.y + rect.height);
613
+ /**
614
+ * Return boundingbox for entity
615
+ * @return {BoundingBox}
616
+ */
617
+ boundingBox() {
618
+ const bb = BoundingBox.fromPoints(this.getTextFrameCorners());
619
+ return bb;
620
+ }
483
621
 
484
- return {
485
- start: botLeft,
486
- end: topRight,
487
- };
622
+ /**
623
+ * Return a list of points representing a polyline version of this entity
624
+ * @return {Array}
625
+ */
626
+ toPolylinePoints() {
627
+ const corners = this.getTextFrameCorners();
628
+ // Close the rectangle
629
+ corners.push(corners[0].clone());
630
+ return corners;
488
631
  }
489
632
  }