kicadts 0.0.1

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 (188) hide show
  1. package/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc +111 -0
  2. package/.vscode/settings.json +16 -0
  3. package/AGENTS.md +30 -0
  4. package/README.md +206 -0
  5. package/biome.json +93 -0
  6. package/bun.lock +48 -0
  7. package/bunfig.toml +5 -0
  8. package/lib/index.ts +1 -0
  9. package/lib/sexpr/base-classes/SxClass.ts +164 -0
  10. package/lib/sexpr/base-classes/SxPrimitiveBoolean.ts +35 -0
  11. package/lib/sexpr/base-classes/SxPrimitiveNumber.ts +26 -0
  12. package/lib/sexpr/base-classes/SxPrimitiveString.ts +26 -0
  13. package/lib/sexpr/classes/At.ts +38 -0
  14. package/lib/sexpr/classes/Bus.ts +83 -0
  15. package/lib/sexpr/classes/BusEntry.ts +142 -0
  16. package/lib/sexpr/classes/Color.ts +29 -0
  17. package/lib/sexpr/classes/Dnp.ts +8 -0
  18. package/lib/sexpr/classes/EmbeddedFonts.ts +70 -0
  19. package/lib/sexpr/classes/ExcludeFromSim.ts +8 -0
  20. package/lib/sexpr/classes/FieldsAutoplaced.ts +8 -0
  21. package/lib/sexpr/classes/Footprint.ts +719 -0
  22. package/lib/sexpr/classes/FootprintAttr.ts +102 -0
  23. package/lib/sexpr/classes/FootprintAutoplaceCost180.ts +9 -0
  24. package/lib/sexpr/classes/FootprintAutoplaceCost90.ts +9 -0
  25. package/lib/sexpr/classes/FootprintClearance.ts +9 -0
  26. package/lib/sexpr/classes/FootprintDescr.ts +44 -0
  27. package/lib/sexpr/classes/FootprintLocked.ts +32 -0
  28. package/lib/sexpr/classes/FootprintModel.ts +145 -0
  29. package/lib/sexpr/classes/FootprintNetTiePadGroups.ts +50 -0
  30. package/lib/sexpr/classes/FootprintPad.ts +705 -0
  31. package/lib/sexpr/classes/FootprintPath.ts +44 -0
  32. package/lib/sexpr/classes/FootprintPlaced.ts +32 -0
  33. package/lib/sexpr/classes/FootprintPrivateLayers.ts +56 -0
  34. package/lib/sexpr/classes/FootprintSheetfile.ts +44 -0
  35. package/lib/sexpr/classes/FootprintSheetname.ts +44 -0
  36. package/lib/sexpr/classes/FootprintSolderMaskMargin.ts +9 -0
  37. package/lib/sexpr/classes/FootprintSolderPasteMargin.ts +9 -0
  38. package/lib/sexpr/classes/FootprintSolderPasteRatio.ts +9 -0
  39. package/lib/sexpr/classes/FootprintTags.ts +44 -0
  40. package/lib/sexpr/classes/FootprintTedit.ts +21 -0
  41. package/lib/sexpr/classes/FootprintThermalGap.ts +9 -0
  42. package/lib/sexpr/classes/FootprintThermalWidth.ts +9 -0
  43. package/lib/sexpr/classes/FootprintZoneConnect.ts +9 -0
  44. package/lib/sexpr/classes/FpArc.ts +289 -0
  45. package/lib/sexpr/classes/FpCircle.ts +293 -0
  46. package/lib/sexpr/classes/FpLine.ts +288 -0
  47. package/lib/sexpr/classes/FpPoly.ts +266 -0
  48. package/lib/sexpr/classes/FpPolyFill.ts +48 -0
  49. package/lib/sexpr/classes/FpPolyLocked.ts +40 -0
  50. package/lib/sexpr/classes/FpRect.ts +293 -0
  51. package/lib/sexpr/classes/FpText.ts +341 -0
  52. package/lib/sexpr/classes/FpTextBox.ts +412 -0
  53. package/lib/sexpr/classes/GrLine.ts +245 -0
  54. package/lib/sexpr/classes/GrLineAngle.ts +32 -0
  55. package/lib/sexpr/classes/GrLineEnd.ts +61 -0
  56. package/lib/sexpr/classes/GrLineLocked.ts +40 -0
  57. package/lib/sexpr/classes/GrLineStart.ts +61 -0
  58. package/lib/sexpr/classes/GrText.ts +202 -0
  59. package/lib/sexpr/classes/Image.ts +256 -0
  60. package/lib/sexpr/classes/InBom.ts +8 -0
  61. package/lib/sexpr/classes/Junction.ts +134 -0
  62. package/lib/sexpr/classes/KicadPcb.ts +313 -0
  63. package/lib/sexpr/classes/KicadSch.ts +303 -0
  64. package/lib/sexpr/classes/KicadSchGenerator.ts +32 -0
  65. package/lib/sexpr/classes/KicadSchGeneratorVersion.ts +30 -0
  66. package/lib/sexpr/classes/KicadSchVersion.ts +22 -0
  67. package/lib/sexpr/classes/Label.ts +136 -0
  68. package/lib/sexpr/classes/Layer.ts +51 -0
  69. package/lib/sexpr/classes/Layers.ts +47 -0
  70. package/lib/sexpr/classes/LibSymbols.ts +61 -0
  71. package/lib/sexpr/classes/NoConnect.ts +73 -0
  72. package/lib/sexpr/classes/OnBoard.ts +8 -0
  73. package/lib/sexpr/classes/PadChamfer.ts +50 -0
  74. package/lib/sexpr/classes/PadChamferRatio.ts +9 -0
  75. package/lib/sexpr/classes/PadClearance.ts +9 -0
  76. package/lib/sexpr/classes/PadDieLength.ts +9 -0
  77. package/lib/sexpr/classes/PadDrill.ts +145 -0
  78. package/lib/sexpr/classes/PadDrillOffset.ts +54 -0
  79. package/lib/sexpr/classes/PadLayers.ts +59 -0
  80. package/lib/sexpr/classes/PadNet.ts +56 -0
  81. package/lib/sexpr/classes/PadOptions.ts +182 -0
  82. package/lib/sexpr/classes/PadPinFunction.ts +9 -0
  83. package/lib/sexpr/classes/PadPinType.ts +9 -0
  84. package/lib/sexpr/classes/PadPrimitiveGrArc.ts +254 -0
  85. package/lib/sexpr/classes/PadPrimitiveGrCircle.ts +279 -0
  86. package/lib/sexpr/classes/PadPrimitiveGrLine.ts +126 -0
  87. package/lib/sexpr/classes/PadPrimitives.ts +289 -0
  88. package/lib/sexpr/classes/PadRectDelta.ts +57 -0
  89. package/lib/sexpr/classes/PadRoundrectRratio.ts +9 -0
  90. package/lib/sexpr/classes/PadSize.ts +54 -0
  91. package/lib/sexpr/classes/PadSolderMaskMargin.ts +9 -0
  92. package/lib/sexpr/classes/PadSolderPasteMargin.ts +9 -0
  93. package/lib/sexpr/classes/PadSolderPasteMarginRatio.ts +9 -0
  94. package/lib/sexpr/classes/PadTeardrops.ts +208 -0
  95. package/lib/sexpr/classes/PadThermalBridgeAngle.ts +9 -0
  96. package/lib/sexpr/classes/PadThermalGap.ts +9 -0
  97. package/lib/sexpr/classes/PadThermalWidth.ts +9 -0
  98. package/lib/sexpr/classes/PadZoneConnect.ts +9 -0
  99. package/lib/sexpr/classes/Paper.ts +119 -0
  100. package/lib/sexpr/classes/PcbGeneral.ts +75 -0
  101. package/lib/sexpr/classes/PcbGeneralLegacyTeardrops.ts +44 -0
  102. package/lib/sexpr/classes/PcbGeneralThickness.ts +9 -0
  103. package/lib/sexpr/classes/PcbGenerator.ts +16 -0
  104. package/lib/sexpr/classes/PcbGeneratorVersion.ts +16 -0
  105. package/lib/sexpr/classes/PcbLayerDefinition.ts +102 -0
  106. package/lib/sexpr/classes/PcbLayers.ts +34 -0
  107. package/lib/sexpr/classes/PcbNet.ts +56 -0
  108. package/lib/sexpr/classes/PcbVersion.ts +9 -0
  109. package/lib/sexpr/classes/Property.ts +246 -0
  110. package/lib/sexpr/classes/PropertyHide.ts +9 -0
  111. package/lib/sexpr/classes/PropertyUnlocked.ts +9 -0
  112. package/lib/sexpr/classes/Pts.ts +65 -0
  113. package/lib/sexpr/classes/RenderCache.ts +221 -0
  114. package/lib/sexpr/classes/SchematicText.ts +141 -0
  115. package/lib/sexpr/classes/Segment.ts +222 -0
  116. package/lib/sexpr/classes/SegmentEnd.ts +59 -0
  117. package/lib/sexpr/classes/SegmentLocked.ts +33 -0
  118. package/lib/sexpr/classes/SegmentNet.ts +62 -0
  119. package/lib/sexpr/classes/SegmentStart.ts +59 -0
  120. package/lib/sexpr/classes/Setup/PcbPlotParams.ts +729 -0
  121. package/lib/sexpr/classes/Setup/PcbPlotParamsBase.ts +9 -0
  122. package/lib/sexpr/classes/Setup/PcbPlotParamsNumericProperties.ts +105 -0
  123. package/lib/sexpr/classes/Setup/PcbPlotParamsStringPropertiesA.ts +104 -0
  124. package/lib/sexpr/classes/Setup/PcbPlotParamsStringPropertiesB.ts +105 -0
  125. package/lib/sexpr/classes/Setup/Setup.ts +573 -0
  126. package/lib/sexpr/classes/Setup/SetupPropertyTypes.ts +119 -0
  127. package/lib/sexpr/classes/Setup/Stackup.ts +140 -0
  128. package/lib/sexpr/classes/Setup/StackupLayer.ts +233 -0
  129. package/lib/sexpr/classes/Setup/StackupLayerProperties.ts +78 -0
  130. package/lib/sexpr/classes/Setup/StackupProperties.ts +41 -0
  131. package/lib/sexpr/classes/Setup/base.ts +167 -0
  132. package/lib/sexpr/classes/Setup/index.ts +14 -0
  133. package/lib/sexpr/classes/Setup/setupMultiValueProperties.ts +54 -0
  134. package/lib/sexpr/classes/Setup/setupNumericProperties.ts +151 -0
  135. package/lib/sexpr/classes/Setup/setupPropertyHandlers.ts +90 -0
  136. package/lib/sexpr/classes/Setup/setupStringProperties.ts +75 -0
  137. package/lib/sexpr/classes/Sheet.ts +205 -0
  138. package/lib/sexpr/classes/SheetFill.ts +44 -0
  139. package/lib/sexpr/classes/SheetInstances.ts +168 -0
  140. package/lib/sexpr/classes/SheetInstancesRoot.ts +165 -0
  141. package/lib/sexpr/classes/SheetPin.ts +122 -0
  142. package/lib/sexpr/classes/SheetProperty.ts +115 -0
  143. package/lib/sexpr/classes/SheetSize.ts +44 -0
  144. package/lib/sexpr/classes/Stroke.ts +58 -0
  145. package/lib/sexpr/classes/StrokeType.ts +34 -0
  146. package/lib/sexpr/classes/Symbol.ts +1541 -0
  147. package/lib/sexpr/classes/TextEffects.ts +444 -0
  148. package/lib/sexpr/classes/TitleBlock.ts +352 -0
  149. package/lib/sexpr/classes/Unit.ts +28 -0
  150. package/lib/sexpr/classes/Uuid.ts +8 -0
  151. package/lib/sexpr/classes/Via.ts +328 -0
  152. package/lib/sexpr/classes/ViaNet.ts +59 -0
  153. package/lib/sexpr/classes/Width.ts +8 -0
  154. package/lib/sexpr/classes/Wire.ts +91 -0
  155. package/lib/sexpr/classes/Xy.ts +35 -0
  156. package/lib/sexpr/classes/Zone.ts +41 -0
  157. package/lib/sexpr/index.ts +130 -0
  158. package/lib/sexpr/parseKicadSexpr.ts +5 -0
  159. package/lib/sexpr/parseToPrimitiveSExpr.ts +240 -0
  160. package/lib/sexpr/utils/indentLines.ts +3 -0
  161. package/lib/sexpr/utils/parseYesNo.ts +12 -0
  162. package/lib/sexpr/utils/quoteSExprString.ts +8 -0
  163. package/lib/sexpr/utils/strokeFromArgs.ts +19 -0
  164. package/lib/sexpr/utils/toNumberValue.ts +13 -0
  165. package/lib/sexpr/utils/toStringValue.ts +10 -0
  166. package/package.json +26 -0
  167. package/scripts/download-references.ts +66 -0
  168. package/tests/fixtures/expectEqualPrimitiveSExpr.ts +200 -0
  169. package/tests/sexpr/KicadPcbDemos.test.ts +48 -0
  170. package/tests/sexpr/KicadSchDemos.test.ts +49 -0
  171. package/tests/sexpr/classes/Footprint.test.ts +277 -0
  172. package/tests/sexpr/classes/FootprintPad.test.ts +71 -0
  173. package/tests/sexpr/classes/FpArc.test.ts +45 -0
  174. package/tests/sexpr/classes/FpCircle.test.ts +39 -0
  175. package/tests/sexpr/classes/FpPoly.test.ts +43 -0
  176. package/tests/sexpr/classes/FpRect.test.ts +40 -0
  177. package/tests/sexpr/classes/FpTextBox.test.ts +84 -0
  178. package/tests/sexpr/classes/Image.test.ts +50 -0
  179. package/tests/sexpr/classes/KicadSch.test.ts +97 -0
  180. package/tests/sexpr/classes/Paper.test.ts +30 -0
  181. package/tests/sexpr/classes/Property.test.ts +48 -0
  182. package/tests/sexpr/classes/Setup.test.ts +189 -0
  183. package/tests/sexpr/classes/Sheet.test.ts +107 -0
  184. package/tests/sexpr/classes/Stroke.test.ts +15 -0
  185. package/tests/sexpr/classes/Symbol.test.ts +96 -0
  186. package/tests/sexpr/classes/TextEffects.test.ts +56 -0
  187. package/tests/sexpr/classes/TitleBlock.test.ts +40 -0
  188. package/tsconfig.json +35 -0
@@ -0,0 +1,412 @@
1
+ import { SxClass } from "../base-classes/SxClass"
2
+ import { printSExpr, type PrimitiveSExpr } from "../parseToPrimitiveSExpr"
3
+ import { quoteSExprString } from "../utils/quoteSExprString"
4
+ import { toNumberValue } from "../utils/toNumberValue"
5
+ import { toStringValue } from "../utils/toStringValue"
6
+ import { Layer } from "./Layer"
7
+ import { TextEffects } from "./TextEffects"
8
+ import { Uuid } from "./Uuid"
9
+ import { Stroke } from "./Stroke"
10
+ import { Pts } from "./Pts"
11
+ import { RenderCache } from "./RenderCache"
12
+
13
+ function primitiveToString(value: PrimitiveSExpr | undefined): string {
14
+ if (value === undefined) return ""
15
+ const str = toStringValue(value)
16
+ if (str !== undefined) return str
17
+ return printSExpr(value)
18
+ }
19
+
20
+ const SUPPORTED_SINGLE_TOKENS = new Set([
21
+ "start",
22
+ "end",
23
+ "pts",
24
+ "angle",
25
+ "layer",
26
+ "uuid",
27
+ "effects",
28
+ "stroke",
29
+ "render_cache",
30
+ ])
31
+
32
+ const SUPPORTED_MULTI_TOKENS = new Set<string>()
33
+
34
+ export class FpTextBox extends SxClass {
35
+ static override token = "fp_text_box"
36
+ token = "fp_text_box"
37
+
38
+ private _sxLocked?: FpTextBoxLocked
39
+ private _text = ""
40
+ private _sxStart?: FpTextBoxStart
41
+ private _sxEnd?: FpTextBoxEnd
42
+ private _sxPts?: Pts
43
+ private _sxAngle?: FpTextBoxAngle
44
+ private _sxLayer?: Layer
45
+ private _sxUuid?: Uuid
46
+ private _sxEffects?: TextEffects
47
+ private _sxStroke?: Stroke
48
+ private _sxRenderCache?: RenderCache
49
+
50
+ static override fromSexprPrimitives(
51
+ primitiveSexprs: PrimitiveSExpr[],
52
+ ): FpTextBox {
53
+ const fpTextBox = new FpTextBox()
54
+
55
+ if (primitiveSexprs.length === 0) {
56
+ throw new Error("fp_text_box requires a text value")
57
+ }
58
+
59
+ let capturedText = false
60
+ let locked = false
61
+ const structuredPrimitives: PrimitiveSExpr[] = []
62
+
63
+ for (const primitive of primitiveSexprs) {
64
+ if (typeof primitive === "string") {
65
+ if (primitive === "locked") {
66
+ if (locked) {
67
+ throw new Error("fp_text_box encountered duplicate locked flags")
68
+ }
69
+ locked = true
70
+ continue
71
+ }
72
+ if (!capturedText) {
73
+ fpTextBox._text = primitive
74
+ capturedText = true
75
+ continue
76
+ }
77
+ throw new Error(
78
+ `fp_text_box encountered unsupported flag "${primitive}"`,
79
+ )
80
+ }
81
+
82
+ if (!capturedText) {
83
+ fpTextBox._text = primitiveToString(primitive)
84
+ capturedText = true
85
+ continue
86
+ }
87
+
88
+ if (!Array.isArray(primitive) || primitive.length === 0) {
89
+ throw new Error(
90
+ `fp_text_box encountered invalid child expression: ${JSON.stringify(primitive)}`,
91
+ )
92
+ }
93
+
94
+ structuredPrimitives.push(primitive)
95
+ }
96
+
97
+ if (!capturedText) {
98
+ throw new Error("fp_text_box did not include a text value")
99
+ }
100
+
101
+ if (locked) {
102
+ fpTextBox._sxLocked = new FpTextBoxLocked()
103
+ }
104
+
105
+ const { propertyMap, arrayPropertyMap } =
106
+ SxClass.parsePrimitivesToClassProperties(structuredPrimitives, this.token)
107
+
108
+ for (const token of Object.keys(propertyMap)) {
109
+ if (!SUPPORTED_SINGLE_TOKENS.has(token)) {
110
+ throw new Error(
111
+ `fp_text_box encountered unsupported child token "${token}"`,
112
+ )
113
+ }
114
+ }
115
+
116
+ for (const [token, entries] of Object.entries(arrayPropertyMap)) {
117
+ if (!SUPPORTED_SINGLE_TOKENS.has(token)) {
118
+ throw new Error(
119
+ `fp_text_box encountered unsupported child token "${token}"`,
120
+ )
121
+ }
122
+ if (!SUPPORTED_MULTI_TOKENS.has(token) && entries.length > 1) {
123
+ throw new Error(
124
+ `fp_text_box does not support repeated child token "${token}"`,
125
+ )
126
+ }
127
+ }
128
+
129
+ fpTextBox._sxStart = propertyMap.start as FpTextBoxStart | undefined
130
+ fpTextBox._sxEnd = propertyMap.end as FpTextBoxEnd | undefined
131
+ fpTextBox._sxPts = propertyMap.pts as Pts | undefined
132
+ fpTextBox._sxAngle = propertyMap.angle as FpTextBoxAngle | undefined
133
+ fpTextBox._sxLayer = propertyMap.layer as Layer | undefined
134
+ fpTextBox._sxUuid = propertyMap.uuid as Uuid | undefined
135
+ fpTextBox._sxEffects = propertyMap.effects as TextEffects | undefined
136
+ fpTextBox._sxStroke = propertyMap.stroke as Stroke | undefined
137
+ fpTextBox._sxRenderCache = propertyMap.render_cache as RenderCache | undefined
138
+
139
+ return fpTextBox
140
+ }
141
+
142
+ get locked(): boolean {
143
+ return this._sxLocked !== undefined
144
+ }
145
+
146
+ set locked(value: boolean) {
147
+ this._sxLocked = value ? new FpTextBoxLocked() : undefined
148
+ }
149
+
150
+ get text(): string {
151
+ return this._text
152
+ }
153
+
154
+ set text(value: string) {
155
+ this._text = value
156
+ }
157
+
158
+ get start(): FpTextBoxStart | undefined {
159
+ return this._sxStart
160
+ }
161
+
162
+ set start(value: FpTextBoxStart | { x: number; y: number } | undefined) {
163
+ if (value === undefined) {
164
+ this._sxStart = undefined
165
+ return
166
+ }
167
+ this._sxStart = value instanceof FpTextBoxStart ? value : new FpTextBoxStart([value.x, value.y])
168
+ }
169
+
170
+ get end(): FpTextBoxEnd | undefined {
171
+ return this._sxEnd
172
+ }
173
+
174
+ set end(value: FpTextBoxEnd | { x: number; y: number } | undefined) {
175
+ if (value === undefined) {
176
+ this._sxEnd = undefined
177
+ return
178
+ }
179
+ this._sxEnd = value instanceof FpTextBoxEnd ? value : new FpTextBoxEnd([value.x, value.y])
180
+ }
181
+
182
+ get pts(): Pts | undefined {
183
+ return this._sxPts
184
+ }
185
+
186
+ set pts(value: Pts | undefined) {
187
+ this._sxPts = value
188
+ }
189
+
190
+ get angle(): FpTextBoxAngle | undefined {
191
+ return this._sxAngle
192
+ }
193
+
194
+ set angle(value: FpTextBoxAngle | number | undefined) {
195
+ if (value === undefined) {
196
+ this._sxAngle = undefined
197
+ return
198
+ }
199
+ this._sxAngle = value instanceof FpTextBoxAngle ? value : new FpTextBoxAngle([value])
200
+ }
201
+
202
+ get layer(): Layer | undefined {
203
+ return this._sxLayer
204
+ }
205
+
206
+ set layer(value: Layer | string | string[] | undefined) {
207
+ if (value === undefined) {
208
+ this._sxLayer = undefined
209
+ return
210
+ }
211
+ if (value instanceof Layer) {
212
+ this._sxLayer = value
213
+ } else {
214
+ const names = Array.isArray(value) ? value : [value]
215
+ this._sxLayer = new Layer(names)
216
+ }
217
+ }
218
+
219
+ get effects(): TextEffects | undefined {
220
+ return this._sxEffects
221
+ }
222
+
223
+ set effects(value: TextEffects | undefined) {
224
+ this._sxEffects = value
225
+ }
226
+
227
+ get stroke(): Stroke | undefined {
228
+ return this._sxStroke
229
+ }
230
+
231
+ set stroke(value: Stroke | undefined) {
232
+ this._sxStroke = value
233
+ }
234
+
235
+ get uuid(): Uuid | undefined {
236
+ return this._sxUuid
237
+ }
238
+
239
+ set uuid(value: Uuid | string | undefined) {
240
+ if (value === undefined) {
241
+ this._sxUuid = undefined
242
+ return
243
+ }
244
+ this._sxUuid = value instanceof Uuid ? value : new Uuid(value)
245
+ }
246
+
247
+ get renderCache(): RenderCache | undefined {
248
+ return this._sxRenderCache
249
+ }
250
+
251
+ set renderCache(value: RenderCache | undefined) {
252
+ this._sxRenderCache = value
253
+ }
254
+
255
+ override getChildren(): SxClass[] {
256
+ const children: SxClass[] = []
257
+ if (this._sxLocked) children.push(this._sxLocked)
258
+ if (this._sxStart) children.push(this._sxStart)
259
+ if (this._sxEnd) children.push(this._sxEnd)
260
+ if (this._sxPts) children.push(this._sxPts)
261
+ if (this._sxAngle) children.push(this._sxAngle)
262
+ if (this._sxLayer) children.push(this._sxLayer)
263
+ if (this._sxEffects) children.push(this._sxEffects)
264
+ if (this._sxStroke) children.push(this._sxStroke)
265
+ if (this._sxUuid) children.push(this._sxUuid)
266
+ if (this._sxRenderCache) children.push(this._sxRenderCache)
267
+ return children
268
+ }
269
+
270
+ override getString(): string {
271
+ const lines = ["(fp_text_box"]
272
+
273
+ if (this._sxLocked) {
274
+ lines.push(this._sxLocked.getStringIndented())
275
+ }
276
+
277
+ lines.push(` ${quoteSExprString(this._text)}`)
278
+
279
+ for (const child of this.getChildren()) {
280
+ if (child === this._sxLocked) continue
281
+ lines.push(child.getStringIndented())
282
+ }
283
+
284
+ lines.push(")")
285
+ return lines.join("\n")
286
+ }
287
+ }
288
+ SxClass.register(FpTextBox)
289
+
290
+ export class FpTextBoxStart extends SxClass {
291
+ static override token = "start"
292
+ static override parentToken = "fp_text_box"
293
+ token = "start"
294
+
295
+ x: number
296
+ y: number
297
+
298
+ constructor(args: [number, number]) {
299
+ super()
300
+ this.x = args[0]
301
+ this.y = args[1]
302
+ }
303
+
304
+ static override fromSexprPrimitives(
305
+ primitiveSexprs: PrimitiveSExpr[],
306
+ ): FpTextBoxStart {
307
+ const [rawX, rawY] = primitiveSexprs
308
+ const x = Number(rawX)
309
+ const y = Number(rawY)
310
+ return new FpTextBoxStart([
311
+ Number.isFinite(x) ? x : 0,
312
+ Number.isFinite(y) ? y : 0,
313
+ ])
314
+ }
315
+
316
+ override getChildren(): SxClass[] {
317
+ return []
318
+ }
319
+
320
+ override getString(): string {
321
+ return `(start ${this.x} ${this.y})`
322
+ }
323
+ }
324
+ SxClass.register(FpTextBoxStart)
325
+
326
+ export class FpTextBoxEnd extends SxClass {
327
+ static override token = "end"
328
+ static override parentToken = "fp_text_box"
329
+ token = "end"
330
+
331
+ x: number
332
+ y: number
333
+
334
+ constructor(args: [number, number]) {
335
+ super()
336
+ this.x = args[0]
337
+ this.y = args[1]
338
+ }
339
+
340
+ static override fromSexprPrimitives(
341
+ primitiveSexprs: PrimitiveSExpr[],
342
+ ): FpTextBoxEnd {
343
+ const [rawX, rawY] = primitiveSexprs
344
+ const x = Number(rawX)
345
+ const y = Number(rawY)
346
+ return new FpTextBoxEnd([
347
+ Number.isFinite(x) ? x : 0,
348
+ Number.isFinite(y) ? y : 0,
349
+ ])
350
+ }
351
+
352
+ override getChildren(): SxClass[] {
353
+ return []
354
+ }
355
+
356
+ override getString(): string {
357
+ return `(end ${this.x} ${this.y})`
358
+ }
359
+ }
360
+ SxClass.register(FpTextBoxEnd)
361
+
362
+ export class FpTextBoxAngle extends SxClass {
363
+ static override token = "angle"
364
+ static override parentToken = "fp_text_box"
365
+ token = "angle"
366
+
367
+ value: number
368
+
369
+ constructor(args: [number]) {
370
+ super()
371
+ this.value = args[0]
372
+ }
373
+
374
+ static override fromSexprPrimitives(
375
+ primitiveSexprs: PrimitiveSExpr[],
376
+ ): FpTextBoxAngle {
377
+ const [raw] = primitiveSexprs
378
+ const numeric =
379
+ typeof raw === "number"
380
+ ? raw
381
+ : toNumberValue(raw as PrimitiveSExpr) ?? 0
382
+ return new FpTextBoxAngle([numeric])
383
+ }
384
+
385
+ override getChildren(): SxClass[] {
386
+ return []
387
+ }
388
+
389
+ override getString(): string {
390
+ return `(angle ${this.value})`
391
+ }
392
+ }
393
+ SxClass.register(FpTextBoxAngle)
394
+
395
+ class FpTextBoxLocked extends SxClass {
396
+ static override token = "locked"
397
+ static override parentToken = "fp_text_box"
398
+ token = "locked"
399
+
400
+ static override fromSexprPrimitives(): FpTextBoxLocked {
401
+ return new FpTextBoxLocked()
402
+ }
403
+
404
+ override getChildren(): SxClass[] {
405
+ return []
406
+ }
407
+
408
+ override getString(): string {
409
+ return "locked"
410
+ }
411
+ }
412
+ SxClass.register(FpTextBoxLocked)
@@ -0,0 +1,245 @@
1
+ import { SxClass } from "../base-classes/SxClass"
2
+ import type { PrimitiveSExpr } from "../parseToPrimitiveSExpr"
3
+ import { Layer } from "./Layer"
4
+ import { Stroke } from "./Stroke"
5
+ import { Uuid } from "./Uuid"
6
+ import { Width } from "./Width"
7
+ import { GrLineStart } from "./GrLineStart"
8
+ import { GrLineEnd } from "./GrLineEnd"
9
+ import { GrLineAngle } from "./GrLineAngle"
10
+ import { GrLineLocked } from "./GrLineLocked"
11
+
12
+ export interface GrLinePoint {
13
+ x: number
14
+ y: number
15
+ }
16
+
17
+ const SUPPORTED_SINGLE_TOKENS = new Set([
18
+ "start",
19
+ "end",
20
+ "angle",
21
+ "layer",
22
+ "width",
23
+ "stroke",
24
+ "uuid",
25
+ "locked",
26
+ ])
27
+
28
+ export class GrLine extends SxClass {
29
+ static override token = "gr_line"
30
+ override token = "gr_line"
31
+
32
+ private _sxStart?: GrLineStart
33
+ private _sxEnd?: GrLineEnd
34
+ private _sxAngle?: GrLineAngle
35
+ private _sxLayer?: Layer
36
+ private _sxWidth?: Width
37
+ private _sxStroke?: Stroke
38
+ private _sxUuid?: Uuid
39
+ private _sxLocked?: GrLineLocked
40
+
41
+ constructor() {
42
+ super()
43
+ }
44
+
45
+ static override fromSexprPrimitives(
46
+ primitiveSexprs: PrimitiveSExpr[],
47
+ ): GrLine {
48
+ const grLine = new GrLine()
49
+
50
+ const { propertyMap, arrayPropertyMap } =
51
+ SxClass.parsePrimitivesToClassProperties(primitiveSexprs, this.token)
52
+
53
+ const unexpectedTokens = new Set<string>()
54
+ for (const token of Object.keys(propertyMap)) {
55
+ if (!SUPPORTED_SINGLE_TOKENS.has(token)) {
56
+ unexpectedTokens.add(token)
57
+ }
58
+ }
59
+ for (const token of Object.keys(arrayPropertyMap)) {
60
+ if (!SUPPORTED_SINGLE_TOKENS.has(token)) {
61
+ unexpectedTokens.add(token)
62
+ continue
63
+ }
64
+ if (arrayPropertyMap[token]!.length > 1) {
65
+ throw new Error(
66
+ `gr_line does not support repeated child tokens: ${token}`,
67
+ )
68
+ }
69
+ }
70
+
71
+ if (unexpectedTokens.size > 0) {
72
+ throw new Error(
73
+ `Unsupported child tokens inside gr_line expression: ${[...unexpectedTokens].join(", ")}`,
74
+ )
75
+ }
76
+
77
+ for (const primitive of primitiveSexprs) {
78
+ if (Array.isArray(primitive)) continue
79
+ throw new Error(
80
+ `gr_line encountered unexpected primitive child: ${JSON.stringify(primitive)}`,
81
+ )
82
+ }
83
+
84
+ grLine._sxStart = propertyMap.start as GrLineStart | undefined
85
+ grLine._sxEnd = propertyMap.end as GrLineEnd | undefined
86
+ grLine._sxAngle = propertyMap.angle as GrLineAngle | undefined
87
+ grLine._sxLayer = propertyMap.layer as Layer | undefined
88
+ grLine._sxWidth = propertyMap.width as Width | undefined
89
+ grLine._sxStroke = propertyMap.stroke as Stroke | undefined
90
+ const locked = propertyMap.locked as GrLineLocked | undefined
91
+ grLine._sxLocked = locked && locked.value ? locked : undefined
92
+ grLine._sxUuid = propertyMap.uuid as Uuid | undefined
93
+
94
+ if (!grLine._sxStart) {
95
+ throw new Error("gr_line requires a start child token")
96
+ }
97
+ if (!grLine._sxEnd) {
98
+ throw new Error("gr_line requires an end child token")
99
+ }
100
+ if (!grLine._sxLayer) {
101
+ throw new Error("gr_line requires a layer child token")
102
+ }
103
+
104
+ return grLine
105
+ }
106
+
107
+ get start(): GrLineStart | undefined {
108
+ return this._sxStart
109
+ }
110
+
111
+ set start(value: GrLineStart | GrLinePoint | undefined) {
112
+ this._sxStart = this.normalizeStart(value)
113
+ }
114
+
115
+ get end(): GrLineEnd | undefined {
116
+ return this._sxEnd
117
+ }
118
+
119
+ set end(value: GrLineEnd | GrLinePoint | undefined) {
120
+ this._sxEnd = this.normalizeEnd(value)
121
+ }
122
+
123
+ get startPoint(): GrLinePoint | undefined {
124
+ return this._sxStart?.toObject()
125
+ }
126
+
127
+ get endPoint(): GrLinePoint | undefined {
128
+ return this._sxEnd?.toObject()
129
+ }
130
+
131
+ get angle(): number | undefined {
132
+ return this._sxAngle?.value
133
+ }
134
+
135
+ set angle(value: number | undefined) {
136
+ if (value === undefined) {
137
+ this._sxAngle = undefined
138
+ return
139
+ }
140
+ this._sxAngle = new GrLineAngle(value)
141
+ }
142
+
143
+ get layer(): Layer | undefined {
144
+ return this._sxLayer
145
+ }
146
+
147
+ set layer(value: Layer | string | Array<string | number> | undefined) {
148
+ if (value === undefined) {
149
+ this._sxLayer = undefined
150
+ return
151
+ }
152
+ if (value instanceof Layer) {
153
+ this._sxLayer = value
154
+ return
155
+ }
156
+ const names = Array.isArray(value) ? value : [value]
157
+ this._sxLayer = new Layer(names)
158
+ }
159
+
160
+ get width(): number | undefined {
161
+ return this._sxWidth?.value
162
+ }
163
+
164
+ set width(value: Width | number | undefined) {
165
+ if (value === undefined) {
166
+ this._sxWidth = undefined
167
+ return
168
+ }
169
+ this._sxWidth = value instanceof Width ? value : new Width(value)
170
+ }
171
+
172
+ get widthClass(): Width | undefined {
173
+ return this._sxWidth
174
+ }
175
+
176
+ set widthClass(value: Width | undefined) {
177
+ this._sxWidth = value
178
+ }
179
+
180
+ get stroke(): Stroke | undefined {
181
+ return this._sxStroke
182
+ }
183
+
184
+ set stroke(value: Stroke | undefined) {
185
+ this._sxStroke = value
186
+ }
187
+
188
+ get uuid(): Uuid | undefined {
189
+ return this._sxUuid
190
+ }
191
+
192
+ set uuid(value: Uuid | string | undefined) {
193
+ if (value === undefined) {
194
+ this._sxUuid = undefined
195
+ return
196
+ }
197
+ this._sxUuid = value instanceof Uuid ? value : new Uuid(value)
198
+ }
199
+
200
+ get locked(): boolean {
201
+ return this._sxLocked?.value ?? false
202
+ }
203
+
204
+ set locked(value: boolean) {
205
+ this._sxLocked = value ? new GrLineLocked(true) : undefined
206
+ }
207
+
208
+ override getChildren(): SxClass[] {
209
+ const children: SxClass[] = []
210
+ if (this._sxStart) children.push(this._sxStart)
211
+ if (this._sxEnd) children.push(this._sxEnd)
212
+ if (this._sxAngle) children.push(this._sxAngle)
213
+ if (this._sxStroke) children.push(this._sxStroke)
214
+ if (this._sxWidth) children.push(this._sxWidth)
215
+ if (this._sxLocked) children.push(this._sxLocked)
216
+ if (this._sxLayer) children.push(this._sxLayer)
217
+ if (this._sxUuid) children.push(this._sxUuid)
218
+ return children
219
+ }
220
+
221
+ private normalizeStart(
222
+ value: GrLineStart | GrLinePoint | undefined,
223
+ ): GrLineStart | undefined {
224
+ if (value === undefined) {
225
+ return undefined
226
+ }
227
+ if (value instanceof GrLineStart) {
228
+ return value
229
+ }
230
+ return new GrLineStart(value.x, value.y)
231
+ }
232
+
233
+ private normalizeEnd(
234
+ value: GrLineEnd | GrLinePoint | undefined,
235
+ ): GrLineEnd | undefined {
236
+ if (value === undefined) {
237
+ return undefined
238
+ }
239
+ if (value instanceof GrLineEnd) {
240
+ return value
241
+ }
242
+ return new GrLineEnd(value.x, value.y)
243
+ }
244
+ }
245
+ SxClass.register(GrLine)
@@ -0,0 +1,32 @@
1
+ import type { PrimitiveSExpr } from "../parseToPrimitiveSExpr"
2
+ import { toNumberValue } from "../utils/toNumberValue"
3
+ import { SxPrimitiveNumber } from "../base-classes/SxPrimitiveNumber"
4
+ import { SxClass } from "../base-classes/SxClass"
5
+
6
+ export class GrLineAngle extends SxPrimitiveNumber {
7
+ static override token = "angle"
8
+ static override parentToken = "gr_line"
9
+ override token = "angle"
10
+
11
+ constructor(value: number) {
12
+ super(value)
13
+ }
14
+
15
+ static override fromSexprPrimitives(
16
+ primitiveSexprs: PrimitiveSExpr[],
17
+ ): GrLineAngle {
18
+ const [rawAngle] = primitiveSexprs
19
+ const angle = toNumberValue(rawAngle)
20
+
21
+ if (angle === undefined) {
22
+ throw new Error("gr_line angle expects a numeric value")
23
+ }
24
+
25
+ return new GrLineAngle(angle)
26
+ }
27
+
28
+ override getString(): string {
29
+ return `(angle ${this.value})`
30
+ }
31
+ }
32
+ SxClass.register(GrLineAngle)