@openpowershift/logic-diagram-language 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +139 -0
  3. package/dist/examples.d.ts +2 -0
  4. package/dist/examples.js +469 -0
  5. package/dist/export-image.d.ts +10 -0
  6. package/dist/export-image.js +47 -0
  7. package/dist/index.d.ts +10 -0
  8. package/dist/index.html +13 -0
  9. package/dist/index.js +18 -0
  10. package/dist/parser/ast.d.ts +118 -0
  11. package/dist/parser/ast.js +79 -0
  12. package/dist/parser/index.d.ts +3 -0
  13. package/dist/parser/index.js +2 -0
  14. package/dist/parser/parser.d.ts +2 -0
  15. package/dist/parser/parser.js +635 -0
  16. package/dist/renderer/astar-router.d.ts +16 -0
  17. package/dist/renderer/astar-router.js +532 -0
  18. package/dist/renderer/gates.d.ts +19 -0
  19. package/dist/renderer/gates.js +92 -0
  20. package/dist/renderer/graph.d.ts +31 -0
  21. package/dist/renderer/graph.js +403 -0
  22. package/dist/renderer/layout.d.ts +76 -0
  23. package/dist/renderer/layout.js +3121 -0
  24. package/dist/renderer/math-renderer.d.ts +16 -0
  25. package/dist/renderer/math-renderer.js +119 -0
  26. package/dist/renderer/svg-renderer.d.ts +3 -0
  27. package/dist/renderer/svg-renderer.js +599 -0
  28. package/dist/renderer/wires.d.ts +5 -0
  29. package/dist/renderer/wires.js +12 -0
  30. package/dist/theme/themes.d.ts +58 -0
  31. package/dist/theme/themes.js +104 -0
  32. package/docs/api.adoc +196 -0
  33. package/docs/ldl-for-llms.md +163 -0
  34. package/docs/user-guide.adoc +318 -0
  35. package/package.json +78 -0
  36. package/spec/render.sh +22 -0
  37. package/spec/sections/attributes.adoc +80 -0
  38. package/spec/sections/connections.adoc +39 -0
  39. package/spec/sections/examples.adoc +212 -0
  40. package/spec/sections/expressions.adoc +182 -0
  41. package/spec/sections/file-extension.adoc +5 -0
  42. package/spec/sections/function-blocks.adoc +120 -0
  43. package/spec/sections/grammar.adoc +64 -0
  44. package/spec/sections/hyperlinks.adoc +18 -0
  45. package/spec/sections/introduction.adoc +16 -0
  46. package/spec/sections/layout-rules.adoc +491 -0
  47. package/spec/sections/lexical-conventions.adoc +68 -0
  48. package/spec/sections/objects.adoc +31 -0
  49. package/spec/sections/options.adoc +146 -0
  50. package/spec/sections/ports.adoc +77 -0
  51. package/spec/sections/rendering-contract.adoc +11 -0
  52. package/spec/sections/revision-history.adoc +9 -0
  53. package/spec/sections/styling.adoc +83 -0
  54. package/spec/sections/svg-symbol-specification.adoc +149 -0
  55. package/spec/sections/symbol-definitions.adoc +143 -0
  56. package/spec/sections/templates.adoc +31 -0
  57. package/spec/spec.adoc +49 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Mulholland
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # LDL — Logic Diagram Language
2
+
3
+ LDL is a small text language and renderer for **logic and protection diagrams**. You
4
+ write boolean equations; the renderer draws the gates, routes the wires, and lays the
5
+ whole diagram out automatically — producing clean, publication-quality SVG (and PNG/PDF
6
+ in the browser).
7
+
8
+ > **Try it in the browser: [openpowershift.github.io/logic-diagram-language](https://openpowershift.github.io/logic-diagram-language/)**
9
+ > — a live playground: type LDL on the left, see the diagram on the right.
10
+ >
11
+ > Published as **`@openpowershift/logic-diagram-language`**. See the **[API Reference](docs/api.adoc)**
12
+ > for programmatic use.
13
+
14
+ ```
15
+ TRIP = OVERCURRENT AND NOT BLOCK
16
+ OR (EARTH AND MANUAL)
17
+ ```
18
+
19
+ That single expression yields a complete diagram: boundary inputs on the left, gates in
20
+ the middle, the `TRIP` output on the right, with wires routed and crossings minimised —
21
+ no coordinates, no manual placement.
22
+
23
+ ## Why
24
+
25
+ Protection and control schematics are tedious to draw by hand and awkward to keep in
26
+ sync with the logic they represent. LDL treats the **logic as the source of truth**: the
27
+ diagram is a rendering of an expression, so it is diffable, reviewable, and regenerated
28
+ automatically whenever the logic changes.
29
+
30
+ ## Goals & concepts
31
+
32
+ - **Expression-first.** A simple boolean expression produces a complete diagram. Names
33
+ that are consumed elsewhere become shared internal signals; names that aren't become
34
+ outputs.
35
+ - **Declarative — the layout is the renderer's job.** You describe _what_ is connected,
36
+ not _where_ things go. A Sugiyama-style layout engine assigns layers and coordinates
37
+ and actively minimises wire crossings; every reshaping pass validates a single
38
+ wire-separation contract so wires never overlap or crowd.
39
+ - **Protection-domain aware.** Beyond `AND`/`OR`/`NOT`, LDL has SEL-style function
40
+ blocks — `TIMER`, `SR` latch, `RISING`/`FALLING` edge triggers, `COMPARE` comparator,
41
+ and a generic `FB` block — plus seal-in/feedback loops drawn as loop-back wires.
42
+ - **Labelled & styled.** Inputs, outputs and gates carry `.Name`/`.Description` labels
43
+ (with inline TeX math via MathJax), and every element has a stable id/class for CSS.
44
+ - **Readable output.** Options control inversion bubbles vs. NOT gates, input bars,
45
+ compactness, adaptive column spacing, and more.
46
+
47
+ ## Use as a library
48
+
49
+ ```bash
50
+ npm install @openpowershift/logic-diagram-language
51
+ ```
52
+
53
+ ```ts
54
+ import {
55
+ parse,
56
+ renderDiagram,
57
+ resolveOptions,
58
+ } from "@openpowershift/logic-diagram-language";
59
+
60
+ const { diagram } = parse("O1 = I1 AND NOT I2");
61
+ const svg = renderDiagram(
62
+ diagram,
63
+ diagram.portMeta,
64
+ true,
65
+ false,
66
+ resolveOptions(diagram.options),
67
+ ); // → a complete <svg>…</svg> string
68
+ ```
69
+
70
+ SVG rendering is isomorphic (Node + browser). In the browser, `svgToPngBlob(svg)` and
71
+ `svgToPdfBlob(svg)` rasterise it to a PNG/PDF `Blob`. Full API, options table and PNG/PDF
72
+ recipes are in the **[API Reference](docs/api.adoc)**.
73
+
74
+ ## Playground / development
75
+
76
+ Requires Node 20+ (the dev playground uses Node 24 — see `.nvmrc`).
77
+
78
+ ```bash
79
+ npm install
80
+ npm run dev # live playground at the printed URL — type LDL, see SVG update live
81
+ npm run build # type-check + production build of the playground app to dist/
82
+ npm run build:lib # build the publishable library to dist/ (index.js + type declarations)
83
+ npm test # run the test suite (vitest)
84
+ ```
85
+
86
+ The playground has a source editor on the left and a live diagram on the right, with a
87
+ dropdown of built-in examples covering every language feature. Toggle labels/ids,
88
+ junction dots, zoom, and export to SVG/PNG/PDF.
89
+
90
+ ## A taste of the language
91
+
92
+ ```ldl
93
+ OPTION OUTPUT_ORDER = AUTO
94
+
95
+ // SEL-style: a comparator feeds an SR latch, sealed in, into a pickup timer
96
+ A = SR(COMPARE(IA, IPICKUP), RESET)
97
+ TRIP = TIMER(A, 0, 30cyc)
98
+ ALARM = RISING(COMPARE(IA, IPICKUP))
99
+
100
+ IA.Name = "$I_a$"
101
+ IPICKUP.Name = "$I_{pickup}$"
102
+ TRIP.Name = "Trip"
103
+ ```
104
+
105
+ See the **[User Guide](docs/user-guide.adoc)** for the full working syntax with
106
+ examples, and the **[LDL Specification](spec/spec.adoc)** for the formal grammar and the
107
+ rendering contract. Current implementation status is tracked in
108
+ [IMPLEMENTATION.md](IMPLEMENTATION.md).
109
+
110
+ **Generating LDL with an AI agent?** Point it at
111
+ **[docs/ldl-for-llms.md](docs/ldl-for-llms.md)** — a compact, implemented-only authoring guide (the
112
+ full spec includes reserved features that don't render yet, which trips agents up).
113
+
114
+ ## Project layout
115
+
116
+ | Path | What |
117
+ | --------------------- | ------------------------------------------------------------------------------------- |
118
+ | `src/index.ts` | Public library API (`parse`, `renderDiagram`, `layoutDiagram`, options, types) |
119
+ | `src/export-image.ts` | Browser-only PNG/PDF helpers (`svgToPngBlob`, `svgToPdfBlob`) |
120
+ | `src/parser/` | Tokeniser, parser, and AST for LDL source |
121
+ | `src/renderer/` | Layout engine (`layout.ts`, `graph.ts`), A\* wire router, SVG renderer, gate symbols |
122
+ | `src/components/` | Lit web components — the playground app, editor and viewer |
123
+ | `src/worker/` | Off-thread parse → layout → render worker |
124
+ | `docs/` | [User guide](docs/user-guide.adoc) and [API reference](docs/api.adoc) |
125
+ | `spec/` | AsciiDoc language specification (`spec/spec.adoc`) |
126
+ | `tests/` | Vitest unit tests, layout invariants, and golden geometry/visual-regression snapshots |
127
+
128
+ ## Status
129
+
130
+ LDL is under active development. The core language (boolean expressions, intermediates,
131
+ feedback, SEL function blocks, labels, TeX math, styling, and the layout options) is
132
+ implemented and tested. Some specified constructs are reserved but not yet implemented —
133
+ the `NAND`/`NOR`/`XOR`/`XNOR` operators, `CONNECT`, custom `SYMBOL` definitions,
134
+ `IMPORT`/`STYLESHEET`, and hyperlinks. See the guide and `IMPLEMENTATION.md` for the
135
+ current boundary.
136
+
137
+ ## License
138
+
139
+ [MIT](LICENSE) © 2026 Daniel Mulholland
@@ -0,0 +1,2 @@
1
+ export declare const EXAMPLES: Record<string, string>;
2
+ export declare const EXAMPLE_NAMES: string[];
@@ -0,0 +1,469 @@
1
+ export const EXAMPLES = {
2
+ 'Breaker Failure (SEL-751A)': `// Feedback seal-in: BFT appears inside its OWN definition, so rather than
3
+ // becoming a second input it is drawn as a loop-back wire (a latch holding
4
+ // itself in). Labels use inline TeX ($...$). OUTPUT_ORDER / INPUT_ORDER = AUTO
5
+ // reorder ports to minimise crossings (INPUT_ORDER is AUTO by default anyway).
6
+ OPTION OUTPUT_ORDER = AUTO
7
+ OPTION INPUT_ORDER = AUTO
8
+
9
+ BFT = BFI OR BFT AND ((CB52A AND CB52ABFY) OR (I1I2 AND INOM))
10
+
11
+ BFI.Description = "Relay Word Bit"
12
+
13
+ CB52A.Name = "52A"
14
+ CB52ABFY.Name = "52ABF = Y"
15
+ CB52ABFY.Description = "Setting"
16
+
17
+ I1I2.Name = "$\\mathrm{|I1|+|I2|}$"
18
+ INOM.Name = "$\\mathrm{0.02 \\cdot I_{NOM}}$"
19
+
20
+ // SEL-751A Instruction Manual Figure 4.55 Breaker Failure Logic`,
21
+ 'SEL Function Blocks': `// SEL-style function blocks compose like any sub-expression:
22
+ // TIMER(in, pu, do) - pickup/dropout delays. A bare number is cycles; ms/s/m
23
+ // also work, or name them PU=/DO=. 0 = no delay.
24
+ // SR(set, reset).Q - set/reset latch (.NQ for Q-bar; DOMINANT=SET, see "SR Latch").
25
+ // COMPARE(+, -) - analog comparator. RISING/FALLING(in) - edge one-shots.
26
+ OPTION OUTPUT_ORDER = AUTO
27
+ A = SR(COMPARE(IA, IPICKUP), RESET)
28
+ TRIP = TIMER(A, 0, 30cyc)
29
+ ALARM = RISING(COMPARE(IA, IPICKUP))
30
+ RESTART = FALLING(BLOCK)`,
31
+ 'SR Latch (Q and NQ)': `// SR latch with both outputs: .Q (default) and .NQ (the inverted output, Q-bar).
32
+ // Referencing SR#L1 twice reuses the SAME latch; DOMINANT=SET makes 'set' win
33
+ // when both inputs are asserted (reset-dominant by default). Each output port
34
+ // is drawn only when it is referenced.
35
+ PERMIT = SR#L1(START, STOP, DOMINANT=SET).Q
36
+ BLOCKED = SR#L1(START, STOP).NQ
37
+ L1.Name = "Enable Latch"`,
38
+ 'Layout Options': `// Layout controls: ADAPTIVE column spacing packs each column to its content
39
+ // (default UNIFORM), COMPACT_V tightens row spacing (also COMPACT_H, COMPACT,
40
+ // SPACIOUS, or explicit factors like [70,70]), and an expression may span
41
+ // multiple lines — a continuation line simply begins with whitespace.
42
+ OPTION COLUMN_SPACING = ADAPTIVE
43
+ OPTION COMPACTNESS = COMPACT_V
44
+ OPTION OUTPUT_ORDER = AUTO
45
+
46
+ TRIP = PHASE AND ENABLE
47
+ OR (EARTH AND NOT BLOCK)
48
+ OR MANUAL
49
+
50
+ PHASE.Name = "Phase OC"
51
+ EARTH.Name = "Earth Fault"
52
+ BLOCK.Name = "Harmonic Block"`,
53
+ 'Labelled Gates': `// Name a gate by assigning it to an intermediate, then label that name — the
54
+ // name/description appear at the gate's output and wires route around them.
55
+ // (For the same result without the pass-through name, see "Named Gates".)
56
+ OPTION OUTPUT_ORDER = AUTO
57
+ PH = O51 OR O50 OR NEGSEQ
58
+ EF = E51N OR E50N
59
+ HS = O502 OR E50N2
60
+ SUP = PH AND NOT HBLK
61
+ TRIP = SUP OR EF OR HS
62
+ PH.Name = "Phase OC"
63
+ PH.Description = "51/50/46"
64
+ EF.Name = "Earth Fault"
65
+ EF.Description = "51N/50N"
66
+ HS.Name = "High-Set"
67
+ HS.Description = "50-2"
68
+ SUP.Name = "Supervised"
69
+ SUP.Description = "harmonic block"`,
70
+ 'Complex Protection (SEL)': `// Every block type, nested and mixed with gates. A block may take an instance
71
+ // id (TIMER#TD, COMPARE#C50) so it can carry a .Name/.Description, and blocks
72
+ // nest freely — here a COMPARE feeds an SR feeds a TIMER. Bare cycle numbers
73
+ // (2cyc, 12cyc) are positional PU/DO settings.
74
+ OPTION OUTPUT_ORDER = AUTO
75
+
76
+ OC_TRIP = TIMER#TD(SR#TL(COMPARE#C50(IA, IPICKUP) AND NOT BLOCK, RESET), 2cyc, 0)
77
+ BF_TRIP = TIMER#BFT(COMPARE#C87(IDIFF, IREST) AND CB52A, 0, 12cyc)
78
+ ALARM = RISING(START) OR EXT_ALARM
79
+ RECLOSE = FALLING(CB52A) AND NOT LOCKOUT
80
+
81
+ C50.Name = "50P1"
82
+ C50.Description = "Phase OC"
83
+ C87.Name = "87"
84
+ C87.Description = "Differential"
85
+ TL.Name = "Trip Latch"
86
+ TD.Name = "62T"
87
+ TD.Description = "Trip delay"
88
+ BFT.Name = "62BF"
89
+ BFT.Description = "BF delay"`,
90
+ 'Shared Intermediates': `// Consumed intermediates: A is internal but labelled at its junction;
91
+ // B is forced to an output with .OUT = TRUE
92
+ OPTION OUTPUT_ORDER = AUTO
93
+ B = COMPARE(IA, IPICKUP)
94
+ A = SR(B, RESET) OR B OR C
95
+ TRIP = TIMER(A, 0, 30cyc)
96
+ ALARM = RISING(COMPARE(IA, IPICKUP))
97
+ A.Name = "Trip Permit"
98
+ A.Description = "Seal-in"
99
+ B.OUT = TRUE`,
100
+ 'Generic Block (FB)': `// A generic user block (FB): a box with whatever ports you need, for a function
101
+ // whose internals aren't drawn. Call arguments are input ports (NAME=expr labels
102
+ // them); each .PORT you reference is an output port. Reusing one #id (FB#PROT)
103
+ // binds several outputs to a SINGLE box.
104
+ OPTION OUTPUT_ORDER = AUTO
105
+ TRIP = FB#PROT(PHASE=IA, EARTH=IN, EN=ENABLE).TRIP
106
+ ALARM = FB#PROT.ALARM
107
+ CLOSE = FB#PROT.CLOSE
108
+ PROT.Name = "Feeder Protection"
109
+ PROT.Description = "SEL-751A"`,
110
+ 'Named Gates': `// Direct gate naming with AND#ID(...) / OR#ID(...) — no pass-through intermediate needed
111
+ OPTION OUTPUT_ORDER = AUTO
112
+ OC = OR#OC1(I51, I50, I46)
113
+ OC1.Name = "Overcurrent"
114
+ OC1.Description = "51/50N"
115
+ TRIP = AND#TRIP1(OC, NOT BLOCK)
116
+ TRIP1.Name = "Trip AND"
117
+ TRIP1.Description = "permissive"
118
+ O1 = AND#GEN(A, B)
119
+ GEN.Name = "Combo Gate"
120
+ GEN.Description = "stage 1"`,
121
+ 'Simple AND Gate': `// The smallest diagram: one assignment. Bare identifiers that are never
122
+ // assigned (A, B) become boundary inputs on the left; the assigned name (OUT)
123
+ // becomes the output on the right. The operators are AND, OR and NOT.
124
+ OUT = A AND B`,
125
+ 'Simple OR Gate': `// An OR gate. Its ">=1" glyph is the IEC 60617 symbol for OR (AND shows "&",
126
+ // NOT a triangle). Combine AND / OR / NOT to build any boolean expression.
127
+ OUT = A OR B`,
128
+ 'NOT Gate': `// A NOT gate (inverter), drawn as a triangle with a bubble. With
129
+ // OPTION INVERSION = BUBBLES the inversion is drawn as just a bubble on the
130
+ // downstream port instead of a separate gate (see "Inversion Bubbles").
131
+ OUT = NOT A`,
132
+ 'Combined Logic (CBFPS)': `// Operator precedence is NOT > AND > OR, so this parses as
133
+ // (AB AND DC) OR ((NOT DC) AND GF). Add parentheses to override precedence.
134
+ CBFPS = AB AND DC OR (NOT DC AND GF)`,
135
+ 'Trip Logic': `// TRIP is used by MAIN_TRIP, so it is a consumed intermediate — drawn as an
136
+ // internal gate, not a boundary output. Add "TRIP.OUT = TRUE" to also expose it
137
+ // as an output (see "Shared Intermediates").
138
+ TRIP = OVERCURRENT OR (NOT EARTH_FAULT)
139
+ MAIN_TRIP = TRIP AND (MANUAL_TRIP OR REMOTE_TRIP)`,
140
+ 'Three-Input AND': `// A multi-input gate. By default (GATE_INPUT_STYLE = EXPAND) the gate grows
141
+ // vertically to fit every input; GATE_INPUT_STYLE = BARS keeps it 2-input-sized
142
+ // and taps the extra inputs onto vertical bars instead (see "Input Bars").
143
+ OUT = A AND B AND C`,
144
+ 'Triple OR': `// Three inputs into one OR gate. INPUT_ORDER = AUTO (the default) may reorder
145
+ // a gate's inputs to reduce wire crossings; INPUT_ORDER = DECLARATION keeps the
146
+ // order as written.
147
+ ALARM = TEMP OR PRESSURE OR FLOW`,
148
+ 'Nested NOT': `// Double negation cancels: NOT NOT A = A, so no inverter is drawn. An odd
149
+ // number of NOTs reduces to a single inversion — try adding one more NOT.
150
+ OUT = NOT NOT A`,
151
+ 'Complex Protection': `// TRIP_1 and TRIP_2 are referenced by MAIN_TRIP, so they are consumed
152
+ // intermediates: drawn as internal gates that fan out, not as boundary outputs.
153
+ // Only MAIN_TRIP (referenced by nothing) becomes an output. OUTPUT_ORDER = AUTO
154
+ // stacks outputs by their driver to reduce crossings (default: DECLARATION order).
155
+ OPTION OUTPUT_ORDER = AUTO
156
+ TRIP_1 = OVERCURRENT_A OR OVERCURRENT_B
157
+ TRIP_2 = EARTH_FAULT AND NOT BLOCK
158
+ MAIN_TRIP = TRIP_1 AND TRIP_2`,
159
+ 'Mixed Logic': `// Mixed AND/OR with negation: RESULT is consumed (not shown), OUTPUT is the result
160
+ RESULT = (A AND B) OR (C AND NOT D)
161
+ OUTPUT = RESULT AND (ENABLE OR FORCE)`,
162
+ 'Launch Interlock': `// Launch interlock for Pad 39A: all safety permissives, no hold,
163
+ // AND either the automatic sequence OR a manual firing enable ignition
164
+
165
+ I1.Name = "Pad Cleared"
166
+ I1.Description = "(LP 3.1)"
167
+
168
+ I2.Name = "Umbilicals Retracted"
169
+ I2.Description = "(LP 3.24)"
170
+
171
+ I3.Name = "Tanks Pressurised"
172
+ I3.Description = "(LP 3.3)"
173
+
174
+ I4.Name = "Guidance Aligned"
175
+ I4.Description = "(GNC 3.5)"
176
+
177
+ I5.Name = "Hold Fired"
178
+ I5.Description = "(RSO 3.15)"
179
+
180
+ I6.Name = "Auto Sequence"
181
+ I6.Description = "(SEQ 3.11)"
182
+
183
+ I7.Name = "Range Safety Armed"
184
+ I7.Description = "(RSO 3.23)"
185
+
186
+ I8.Name = "GO from LCC"
187
+ I8.Description = "(via LCC)"
188
+
189
+ I9.Name = "Manual Enable"
190
+ I9.Description = "(LCC 3.12)"
191
+
192
+ I10.Name = "Fire Button"
193
+ I10.Description = "(LCC 3.20)"
194
+
195
+ O1 = (I1 AND I2) AND (I3 AND I4 AND NOT I5) AND ((I6 AND I7 AND I8) OR (I9 AND I10))
196
+
197
+ O1.Name = "Launch Command"
198
+ O1.Description = "(BO 3.2)"`,
199
+ 'Inversion Bubbles': `// INVERSION = BUBBLES draws every NOT as a small bubble on the downstream
200
+ // input/output port instead of a separate NOT gate (the default is
201
+ // INVERSION = GATES). O4 shows double-inversion cancellation (NOT NOT I3 = I3).
202
+ OPTION INVERSION = BUBBLES
203
+
204
+ I1.Name = "Start Permitted"
205
+ I1.Description = "(BI 1.1)"
206
+ I2.Name = "Stop Condition"
207
+ I2.Description = "(BI 1.2)"
208
+ I3.Name = "Reset"
209
+ I3.Description = "(BI 1.3)"
210
+
211
+ O1 = I1 AND NOT I2 AND I3
212
+ O2 = NOT (I1 AND I3)
213
+ O3 = NOT I2
214
+ O4 = NOT NOT I3
215
+
216
+ O1.Name = "Run"
217
+ O1.Description = "(BO 1.1)"
218
+ O2.Name = "NAND Result"
219
+ O2.Description = "(BO 1.2)"
220
+ O3.Name = "Inverted Stop"
221
+ O3.Description = "(BO 1.3)"
222
+ O4.Name = "Pass-Through"
223
+ O4.Description = "(BO 1.4)"`,
224
+ 'Input Bars': `// GATE_INPUT_STYLE = BARS keeps a multi-input gate at its base 2-input size and
225
+ // taps the extra inputs onto vertical bars. The default, EXPAND, grows the gate
226
+ // vertically instead (compare "Three-Input AND").
227
+ OPTION GATE_INPUT_STYLE = BARS
228
+
229
+ I1.Name = "Start"
230
+ I2.Name = "Stop"
231
+ I3.Name = "Reset"
232
+ I4.Name = "Enable"
233
+ I5.Name = "Supervision"
234
+
235
+ O1 = I1 AND I2 AND I3 AND I4 AND I5
236
+ O1.Name = "Output"`,
237
+ 'Square Ports': `// PORT_STYLE = SQUARE draws port markers as small squares; the default is
238
+ // CIRCLE. (Other rendering options: INVERSION, GATE_INPUT_STYLE, LABEL_STYLE.)
239
+ OPTION PORT_STYLE = SQUARE
240
+
241
+ A.Name = "Signal A"
242
+ B.Name = "Signal B"
243
+ O1 = A AND B
244
+ O1.Name = "Result"`,
245
+ 'Combined Options': `// Options combine freely. Here three at once — bubbles for NOT, bars for the
246
+ // multi-input gate, and square port markers — each overriding its own default
247
+ // (GATES / EXPAND / CIRCLE). Options must precede the logic they affect.
248
+ OPTION INVERSION = BUBBLES
249
+ OPTION GATE_INPUT_STYLE = BARS
250
+ OPTION PORT_STYLE = SQUARE
251
+
252
+ I1.Name = "Start"
253
+ I2.Name = "Stop"
254
+ I3.Name = "Reset"
255
+ I4.Name = "Enable"
256
+ I5.Name = "Supervision"
257
+ I6.Name = "Override"
258
+
259
+ O1 = (I1 AND NOT I2) AND (I3 AND I4 AND I5 AND I6)
260
+ O1.Name = "Master Trip"
261
+ O1.Description = "(BO 1.1)"`,
262
+ 'Overcurrent Protection': `// Inline TeX math in labels: wrap it in $...$ (e.g. $I_a$, $I_{set}$). Plain
263
+ // text and math may be mixed, and a literal dollar is written \\$. I1 feeds both
264
+ // outputs — a name used more than once is one shared input that fans out.
265
+ OPTION OUTPUT_ORDER = AUTO
266
+ I1.Name = "$I_a$"
267
+ I1.Description = "Phase A current"
268
+
269
+ I2.Name = "$I_b$"
270
+ I2.Description = "Phase B current"
271
+
272
+ I3.Name = "$I_c$"
273
+ I3.Description = "Phase C current"
274
+
275
+ I4.Name = "$I_{set}$"
276
+ I4.Description = "Setting $= 5.0 \\$ A$"
277
+
278
+ O1.Name = "$I > I_{set}$"
279
+ O1.Description = "Overcurrent trip"
280
+
281
+ O2.Name = "$I_a + I_b + I_c$"
282
+ O2.Description = "Residual current"
283
+
284
+ O1 = I1 AND I4
285
+ O2 = I1 OR I2 OR I3`,
286
+ 'Differential Protection': `// More TeX-labelled I/O — subscripts ($I_{in}$), inequalities ($>$) and mixed
287
+ // text/math. I3 (Block) is used by both outputs, so it fans out to each.
288
+ // Differential protection: $I_{diff} = I_{in} - I_{out}$
289
+
290
+ I1.Name = "$I_{in}$"
291
+ I1.Description = "CT primary current"
292
+
293
+ I2.Name = "$I_{out}$"
294
+ I2.Description = "CT secondary current"
295
+
296
+ I3.Name = "Block"
297
+ I3.Description = "Blocking signal"
298
+
299
+ O1.Name = "$I_{diff} > I_{bias}$"
300
+ O1.Description = "Differential trip"
301
+
302
+ O2.Name = "Restricted Earth Fault"
303
+ O2.Description = "$I_0 > I_{0\\_set}$"
304
+
305
+ O1 = (I1 AND NOT I2) AND NOT I3
306
+ O2 = NOT I3`,
307
+ 'Boolean Algebra': `// The four basic functions. There is no NAND operator — write it as
308
+ // NOT (A AND B), as O4 does. The overline in the labels is TeX
309
+ // ($\\overline{A \\cdot B}$), not a language construct.
310
+ OPTION OUTPUT_ORDER = AUTO
311
+
312
+ A.Name = "$A$"
313
+ A.Description = "Input A"
314
+
315
+ B.Name = "$B$"
316
+ B.Description = "Input B"
317
+
318
+ C.Name = "$C$"
319
+ C.Description = "Input C"
320
+
321
+ O1.Name = "$A \\cdot B$"
322
+ O1.Description = "Logical AND"
323
+
324
+ O2.Name = "$A + B$"
325
+ O2.Description = "Logical OR"
326
+
327
+ O3.Name = "$\\overline{A}$"
328
+ O3.Description = "Logical NOT"
329
+
330
+ O4.Name = "$\\overline{A \\cdot B}$"
331
+ O4.Description = "NAND: $\\overline{AB}$"
332
+
333
+ O1 = A AND B
334
+ O2 = A OR B
335
+ O3 = NOT A
336
+ O4 = NOT (A AND B)`,
337
+ 'Motor Control Circuit': `// A motor starter: TeX-labelled I/O plus INVERSION = BUBBLES for the NOT
338
+ // inputs. Note the OPTION line can sit anywhere before the expressions that use
339
+ // it (here it follows the label declarations).
340
+
341
+ I1.Name = "Start PB"
342
+ I1.Description = "$NO\\ contact$"
343
+
344
+ I2.Name = "Stop PB"
345
+ I2.Description = "$NC\\ contact$"
346
+
347
+ I3.Name = "OL Trip"
348
+ I3.Description = "$I^2 > I_{rated}^2$"
349
+
350
+ I4.Name = "Thermal"
351
+ I4.Description = "$\\Theta > \\Theta_{trip}$"
352
+
353
+ I5.Name = "$I_s$"
354
+ I5.Description = "Start current"
355
+
356
+ O1.Name = "Contactor"
357
+ O1.Description = "$K_1$"
358
+
359
+ O2.Name = "Trip"
360
+ O2.Description = "$\\Delta I > \\Delta I_{set}$"
361
+
362
+ OPTION INVERSION = BUBBLES
363
+
364
+ O1 = I1 AND NOT I2 AND NOT I3 AND NOT I4
365
+ O2 = I3 OR I4 OR NOT I5`,
366
+ 'Styled by ID': `// Reveal IDs in the toolbar to see the SVG id on each gate, block and port.
367
+ //
368
+ // A gate/block group carries its instance id: AND#G1 -> id "G1", FB#P -> id "P".
369
+ // The gate body is a <path> and the block body a <rect>, each carrying its OWN fill
370
+ // and stroke, so target the shape, not the group: "#G1 path" styles the gate body and
371
+ // "#P rect" styles the block body. Setting fill/stroke on the group ("#G1" / "#P") does
372
+ // not reach the body — the shape's own attribute wins — it only reaches inheriting
373
+ // children such as the block's text.
374
+ // Every wire carries data-from / data-to with its net endpoints, so
375
+ // '.ldl-wire[data-to="TRIP"]' colours the wire feeding the TRIP output.
376
+ //
377
+ // What each rule in the STYLE block below targets:
378
+ // #G1 path -> the AND gate's body <path> (fill).
379
+ // #P rect -> the block's body <rect> (fill + stroke).
380
+ // .ldl-wire[data-to="TRIP"] -> the signal wire whose destination net is TRIP.
381
+ // .ldl-wire[data-to="ALARM"] -> the signal wire whose destination net is ALARM.
382
+ // Note: a bare "#TRIP" matches only the output node's short id-stub, NOT the wire that
383
+ // feeds it — colour the wire itself via its data-to net.
384
+ //
385
+ // Toggle the Dots toolbar button to hide junction tie-points (item 11).
386
+ // Use the PNG dropdown for selectable-DPI raster export (item 13).
387
+ OPTION OUTPUT_ORDER = AUTO
388
+ OPTION HIDE_JUNCTIONS = FALSE
389
+
390
+ TRIP = AND#G1(START, INHIBIT, FB#P(BAND=IA, EARTH=IN, EN=BLK).TRIP)
391
+ ALARM = FB#P.ALARM
392
+
393
+ G1.Name = "Trip AND"
394
+ P.Name = "Feeder Protection"
395
+ P.Description = "SEL-751A"
396
+
397
+ STYLE
398
+ #G1 path { fill: #ff003333; }
399
+ #P rect { fill: #e8f5e9; stroke: #1b5e20; }
400
+ .ldl-wire[data-to="TRIP"] { stroke: #c62828; stroke-width: 4; }
401
+ .ldl-wire[data-to="ALARM"] { stroke: #ef6c00; }
402
+ END STYLE`,
403
+ 'Hidden Dots': `// OPTION HIDE_JUNCTIONS hides every junction dot on the diagram (the toolbar
404
+ // Dots button flips the same flag on top of the source option). Useful when
405
+ // printing or when the wiring/junction pattern is visually noisy.
406
+ OPTION HIDE_JUNCTIONS = TRUE
407
+ OPTION INVERSION = BUBBLES
408
+ A = A1 OR A2 OR A3
409
+ B = B1 AND B2 AND B3
410
+ O = A AND B AND C`,
411
+ 'Reactor Scram Matrix': `// Real-world scram matrix: a large (18-input) OR gathers every reactor
412
+ // protection trip, an SR latch seals it in, and the result fans out to
413
+ // annunciators, the plant computer, transient records and rod-drive trips.
414
+ // Exercises large fan-in and reconvergence (SCR01 drives several outputs and
415
+ // the RTS OR; RTS drives more).
416
+ OPTION OUTPUT_ORDER = AUTO
417
+
418
+ E24U2T1.Name = "NEUTRON FLUX HIGH"
419
+ EWT.Name = "CORE OUTLET TEMP HIGH (RT301)"
420
+ E50TP2.Name = "RCS PRESSURE HIGH (PT502)"
421
+ EPSV17.Name = "RCS PRESSURE LOW (PT517)"
422
+ E51T01.Name = "SG-1 LEVEL LOW (LT101)"
423
+ E51T02.Name = "SG-2 LEVEL LOW (LT102)"
424
+ E593P1T.Name = "CONTAINMENT PRESS HI-1 (PT591)"
425
+ E593P2T.Name = "CONTAINMENT PRESS HI-2 (PT592)"
426
+ EBUCH.Name = "RCP UNDERSPEED (ST307)"
427
+ ETP.Name = "STEAM LINE PRESSURE"
428
+ EREFF2.Name = "REACTOR COOLANT FLOW LOW (FT2)"
429
+ E87Z2.Name = "PRESSURISER LEVEL HIGH (LT2)"
430
+ E87Q2.Name = "FEED/STEAM MISMATCH (FQ2)"
431
+ EIN202.Name = "LOSS OF COOLANT FLOW (IN202)"
432
+ EPLQ_X_IT.Name = "MANUAL SCRAM A (IN408)"
433
+ EPLQ_X_CBF.Name = "MANUAL SCRAM B (IN407)"
434
+ EGCB_CBF.Name = "TURBINE TRIP (TTFS)"
435
+ SWA_CON_MON.Name = "DIV A DC BUS MONITOR (PSV20)"
436
+ SWB_CON_MON.Name = "DIV B DC BUS MONITOR (PSV20)"
437
+ CTR.Name = "From Div II Scram (IN203)"
438
+
439
+ TR01 = E24U2T1 OR EWT OR E50TP2 OR EPSV17 OR E51T01 OR E51T02 OR (E593P1T OR E593P2T) OR EBUCH OR ETP OR EREFF2 OR E87Z2 OR E87Q2 OR EIN202 OR EPLQ_X_IT OR EPLQ_X_CBF OR EGCB_CBF OR SWA_CON_MON OR SWB_CON_MON
440
+ TR01.Name = "REACTOR SCRAM"
441
+
442
+ PSV01 = SR(TR01, ULTR01).Q OR CTR
443
+ TRS = PSV01 OR TRIP3 OR TRIP5
444
+
445
+ LED_01 = PSV01
446
+ LED_01.Out = True
447
+ LED_01.Name = "TO SCRAM LED"
448
+
449
+ ICMS_PSV01 = PSV01
450
+ ICMS_PSV01.Out = True
451
+ ICMS_PSV01.Name = "Reactor Scram [TO SPDS] & [TO PPC]"
452
+
453
+ OCT = PSV01
454
+ OCT.Out = True
455
+ OCT.Name = "To Transient Recorder (OUT204)"
456
+
457
+ SWA_CB_TRIP_1 = PSV01
458
+ SWA_CB_TRIP_1.Name = "TO DIV A ROD DRIVE TRIP"
459
+
460
+ SWB_CB_TRIP_1 = PSV01
461
+ SWB_CB_TRIP_1.Name = "TO DIV B ROD DRIVE TRIP"
462
+
463
+ GCB_TRIP_1 = TRS
464
+ GCB_TRIP_1.Name = "TO REACTOR TRIP BKR (OUT401)"
465
+
466
+ GCB_CBF = TRS
467
+ GCB_CBF.Name = "TO SCRAM BKR FAIL"`,
468
+ };
469
+ export const EXAMPLE_NAMES = Object.keys(EXAMPLES);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Rasterise an SVG string to a PNG Blob at `scale`× the diagram's intrinsic pixel size (default 2×).
3
+ * Browser only.
4
+ */
5
+ export declare function svgToPngBlob(svg: string, scale?: number): Promise<Blob>;
6
+ /**
7
+ * Rasterise an SVG string into a single-page PDF Blob sized to the diagram (orientation follows the
8
+ * aspect ratio). Loads `jspdf` on demand. Browser only.
9
+ */
10
+ export declare function svgToPdfBlob(svg: string, scale?: number): Promise<Blob>;