reladraw 0.3.0 → 0.5.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.
package/NOTICE CHANGED
@@ -12,3 +12,40 @@ any other trade names, trademarks, service marks, or product names of the
12
12
  project, except as required for reasonable and customary use in describing the
13
13
  origin of the work (see section 6 of the License). Forks and derived works are
14
14
  welcome and must be distributed under a different name.
15
+
16
+ ---
17
+
18
+ Color palettes
19
+
20
+ Several of reladraw's themes (src/themes.ts) are adapted from the color
21
+ palettes below. Each is distributed under the MIT License, whose text follows
22
+ the list of copyright notices and applies to each of them.
23
+
24
+ Solarized Copyright (c) 2011 Ethan Schoonover
25
+ https://github.com/altercation/solarized
26
+ Gruvbox Copyright (c) 2018 Pavel Pertsev
27
+ https://github.com/morhetz/gruvbox
28
+ Catppuccin Copyright (c) 2021 Catppuccin
29
+ https://github.com/catppuccin/catppuccin
30
+ Nord Copyright (c) 2016-present Sven Greb
31
+ https://github.com/nordtheme/nord
32
+ Dracula Copyright (c) 2023 Dracula Theme
33
+ https://github.com/dracula/dracula-theme
34
+
35
+ Permission is hereby granted, free of charge, to any person obtaining a copy
36
+ of this software and associated documentation files (the "Software"), to deal
37
+ in the Software without restriction, including without limitation the rights
38
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
39
+ copies of the Software, and to permit persons to whom the Software is
40
+ furnished to do so, subject to the following conditions:
41
+
42
+ The above copyright notice and this permission notice shall be included in
43
+ all copies or substantial portions of the Software.
44
+
45
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
48
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
50
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
51
+ THE SOFTWARE.
package/README.md CHANGED
@@ -68,7 +68,7 @@ It is [plain Markdown](.claude/skills/reladraw/SKILL.md) with the syntax referen
68
68
 
69
69
  ## Status
70
70
 
71
- Version 0.3.0. Early, but it runs: a parser, resolver and SVG renderer in TypeScript with no runtime dependencies, and a command-line tool that takes a text file and writes a standalone SVG. The comparison at the top of this page is that pipeline run on [`examples/arch.reladraw`](examples/arch.reladraw). What is still visibly off there is typography, not placement.
71
+ Version 0.5.0. Early, but it runs: a parser, resolver and SVG renderer in TypeScript with no runtime dependencies, and a command-line tool that takes a text file and writes a standalone SVG. The comparison at the top of this page is that pipeline run on [`examples/arch.reladraw`](examples/arch.reladraw). What is still visibly off there is typography, not placement.
72
72
 
73
73
  ```
74
74
  npm install -g reladraw
package/SYNTAX.md CHANGED
@@ -1,4 +1,4 @@
1
- # Syntax reference — 0.3.0
1
+ # Syntax reference — 0.5.0
2
2
 
3
3
  What the language accepts. The parser, resolver and SVG renderer implement all of it; the sections at the end record what is defective, unchecked or undecided.
4
4
 
@@ -129,7 +129,7 @@ Bundling a text's properties into a style is how they come to mean something: `s
129
129
 
130
130
  `color` takes a color written as the viewer will receive it — `#8b8b8b`, or any CSS color — or the one reserved word `muted`, which means the theme's secondary text color and so survives a change of theme.
131
131
 
132
- `size: small | normal | large` sets how big the text is set. The sizes are named for the reason gaps are named: a number would be typography by coordinate, stale the moment the document is set at another size, and silent about *why* one piece of text is smaller than another. Every kind of text has a default and `size` overrides it, exactly as `fill:` overrides the theme's color; only `shape: none` defaults to anything but `normal`, and it defaults to `small`.
132
+ `size: small | normal | large` sets how big the text is set, and so does a plain number of pixels, `size: 22`. The names are the default and the preferred form: they follow the document's size if it is ever set differently, where `22` stays 22. A number says nothing about *why* one text is bigger than another, so bundle it into a style that does. `14px` is refused, since the number is already in pixels, and so is zero or less. Every kind of text has a default and `size` overrides it, exactly as `fill:` overrides the theme's color; only `shape: none` defaults to anything but `normal`, and it defaults to `small`.
133
133
 
134
134
  `wrap: <n>` folds the text at word boundaries every `n` characters, on top of whatever ` / ` already breaks. It is how you make a block of text narrow and tall so it can sit snugly beside something, rather than wide and short so it cannot. The number is a count of characters and not a distance: it says how much fits on a line and nothing about where anything sits.
135
135
 
@@ -602,10 +602,12 @@ Nothing bounds bare text the way a border bounds a node, so a sentence-length on
602
602
  ## Decks
603
603
 
604
604
  ```
605
- deck <name> "<text>" ["<text>" ...]
605
+ node drive "Drive 1" deck: "Drive 2" "Drive 3"
606
606
  ```
607
607
 
608
- Draws the named container with offset copies behind it, one per text, to say "there are several of these and they are the same." Only the front copy shows its contents.
608
+ `deck:` draws offset copies behind a box, one per text, back to front, to say "there are several of these and they are the same." Only the front copy shows its contents. It goes on a box only; a picture or a `shape: none` node has no outline to repeat, and refuses it. A style may carry it.
609
+
610
+ **Changed after 0.3.0.** This was a statement of its own, `deck drive "Drive 2" "Drive 3"`, which created nothing and only said more about a node declared elsewhere. The statement is now an error that quotes the attribute to write instead.
609
611
 
610
612
  ## Attributes
611
613
 
@@ -613,11 +615,12 @@ Every attribute, and what takes one. The kinds here are what a node's **body** i
613
615
 
614
616
  | attribute | `shape:` | `icon:` | `shape: none` | edge | says |
615
617
  |---|---|---|---|---|---|
616
- | `style` | ✓ | ✓ | ✓ | ✓ | the named bundle to take appearance from |
618
+ | `style` | ✓ | ✓ | ✓ | ✓ | the named bundles to take appearance from, one or a list |
617
619
  | `gap` | ✓ | ✓ | ✓ | | the default distance to whatever it is placed against |
618
620
  | `overlap` | ✓ | ✓ | ✓ | | `allow`, to opt out of non-overlap |
619
621
  | `contents` | ✓ | | | | how the children are sized and where the block of them sits, in brackets |
620
622
  | `badge` | ✓ | | | | the picture beside the text |
623
+ | `deck` | ✓ | | | | offset copies behind the box, one quoted text each |
621
624
  | `shape` | ✓ | | ✓ | | the outline the node is drawn with, `none` included |
622
625
  | `icon` | | ✓ | | | the picture the node is drawn as |
623
626
  | `from` `to` | | | | ✓ | which side the line leaves and arrives on |
@@ -627,7 +630,7 @@ Every attribute, and what takes one. The kinds here are what a node's **body** i
627
630
  | `line` | | | | ✓ | color |
628
631
  | `url` | ✓ | ✓ | ✓ | ✓ | a destination to open when the thing is clicked |
629
632
 
630
- The `diagram` statement has a vocabulary of its own — `background`, and so far nothing else — which is checked the same way. Writing `background:` on a node is an error that points at `fill:`.
633
+ The `diagram` statement has a vocabulary of its own — `theme`, `background` and `text` — which is checked the same way. Writing `background:` on a node is an error that points at `fill:`, and `theme:` on one points at `diagram theme:`. A `default` takes a narrower list for each kind; see "Defaults".
631
634
 
632
635
  **A word this table does not give the kind is an error.** The two ways of being wrong get different answers, because they have different remedies. A word that is an attribute nowhere is a misspelling, and the error lists what the kind does take. A word that is an attribute *somewhere else* is usually a real statement written on the wrong half of the diagram, so the error says where it belongs:
633
636
 
@@ -637,6 +640,8 @@ The `diagram` statement has a vocabulary of its own — `background`, and so far
637
640
 
638
641
  Some of the gaps in the table are worth saying out loud, because none of them looks like a mistake while you are writing it. A picture and a bodiless node take no `fill:` or `border:` — there is no outline for either to reach. Neither takes `contents:` either, which says how a node's children sit, and neither may have any. `shape:` and `icon:` each appear only on the kind they make, and writing both is an error naming both. An edge takes no `gap:` or `overlap:` — those say where a node sits, and an edge is not placed, it joins two things that are.
639
642
 
643
+ **A key is written once per statement.** `fill: red fill: blue` is an error that quotes both values and asks you to keep one: two words on one line are equally explicit, so nothing says which was meant, and keeping either would drop the other in silence. It is almost always an edit that forgot to delete the old value. The same holds inside brackets — `(size: small, size: large)`, a placement's `(gap: wide, gap: tight)` — for `style:` written twice, and for a `style` statement that repeats one of its own keys. Two `text: (…)` brackets in one style are fine as long as they say different things. A node overriding its style is not a repeat; see "Styles".
644
+
640
645
  **Changed 2026-09-09.** Until then a node or edge attribute the tool did not recognize was parsed, stored and never read: `wibble: red` on a node drew nothing and said nothing. This was the last place in the language where a key could silently do nothing, and the rule everywhere else — an unknown `diagram` key, an unknown placement modifier, a color naming a part the kind has not got — has always been that a key which silently does nothing looks like the tool being broken rather than like a typo. A file that rendered with a stray word in it will now stop with an error naming it.
641
646
 
642
647
  ## Destinations
@@ -673,6 +678,23 @@ node server.mirror "shared folder / mirror" style: backup
673
678
 
674
679
  The appearance attributes are `fill`, `border`, `line`, `text`, `badge`, `icon` and `shape`. The first three each take a color written as the viewer will receive it — `#142814`, or any CSS color, or `none`; `text` takes the bracket described under "The text and its brackets".
675
680
 
681
+ **What a statement says itself beats what its style says**, wherever `style:` sits on the line. Here the server's border is red, not orange:
682
+
683
+ ```
684
+ style backup border: #d2904e
685
+ node server "Server" border: red style: backup
686
+ ```
687
+
688
+ Whoever wrote `border: red` on the node meant it for that node, so it is an override rather than a contradiction, and nothing is refused. The rest of the style still applies.
689
+
690
+ **A thing may take several styles**, listed the way a placement lists its targets — commas and `and` both separate:
691
+
692
+ ```
693
+ node pump "Pump 3" style: equipment, critical and alarm
694
+ ```
695
+
696
+ They apply in the order written, and a later style wins where two set the same key; the statement's own words still win over all of them. Each style in the list is checked on its own, so one that gives the thing nothing is refused even when its neighbors do not. A style named twice in one list is refused, and so is `style:` written twice on one statement — the list is the way to say several.
697
+
676
698
  ### A color names the part it colors
677
699
 
678
700
  A color attribute says which part of a thing it colors, and a part exists only on the kinds that have one:
@@ -740,12 +762,66 @@ diagram <attributes>
740
762
  Settings that belong to the drawing as a whole rather than to anything in it. There is no name, because a file holds one diagram, and a second `diagram` statement is an error rather than a second opinion.
741
763
 
742
764
  ```
743
- diagram background: #111111
765
+ diagram theme: nord background: #1e2229 text: (color: #e0e0e0)
766
+ ```
767
+
768
+ Three attributes:
769
+
770
+ - `theme` names the theme that supplies every color the file does not state. See "Themes" below.
771
+ - `background` takes a color the same way `fill` does, and it colors the page behind everything, including the strip an edge text knocks out of whatever it crosses. Say nothing and the theme's own background stands.
772
+ - `text: (color: …)` sets the color of every text, on nodes and edges alike. It takes a color and nothing else; a text size is about one kind of text and belongs on a `default`.
773
+
774
+ An unknown key is refused by name — `diagram has no "backround" — it takes theme, background, text` — rather than quietly ignored, the same as every other attribute. See "Attributes".
775
+
776
+ ### Themes
777
+
778
+ ```
779
+ diagram theme: solarized-dark
780
+ ```
781
+
782
+ | theme | |
783
+ |---|---|
784
+ | `dark` | the default: the palette of the benchmark drawing |
785
+ | `light` | the same roles on a white page |
786
+ | `solarized-dark`, `solarized-light` | [Solarized](https://ethanschoonover.com/solarized/) |
787
+ | `gruvbox-dark`, `gruvbox-light` | [Gruvbox](https://github.com/morhetz/gruvbox) |
788
+ | `catppuccin-mocha`, `catppuccin-latte` | [Catppuccin](https://catppuccin.com) |
789
+ | `nord` | [Nord](https://www.nordtheme.com) |
790
+ | `dracula` | [Dracula](https://draculatheme.com) |
791
+ | `high-contrast-dark`, `high-contrast-light` | no fills to lean on, every line at full strength |
792
+ | `print` | a white page, black lines and no fills, for paper |
793
+
794
+ The borrowed palettes were made for code, where a color marks a keyword or a string. A diagram needs a page, a box, a border, text and a line, so each theme is its scheme read as a diagram rather than a copy of it. Their licenses are in `NOTICE`.
795
+
796
+ A theme only ever supplies a color, so changing it never moves anything. A color you write yourself — `fill: #14532d`, or a style's — stays that color under every theme, which means a hand-picked color can clash with a theme it was not chosen against. `muted` is the one word that follows the theme.
797
+
798
+ An unknown name is an error that lists the themes.
799
+
800
+ The command line can render a file in another theme without editing it — `reladraw arch.reladraw --theme light` — and the flag wins over the file's `theme:`. That is how one source becomes a light and a dark image for a README that shows whichever matches the reader's settings. The file's own `background:` and `text:` still win over the flag's theme, as a hand-written color wins over any theme.
801
+
802
+ ### Defaults
803
+
804
+ ```
805
+ default node border: #4a8a5c
806
+ default leaf fill: #2e5d3a
807
+ default container fill: #1a2620 border: #2a3a30
808
+ default edge line: #7a8a99 text: (color: #9aa5b1, size: small)
744
809
  ```
745
810
 
746
- One attribute so far. `background` takes a color the same way `fill` does, and it colors the page behind everything, including the strip an edge text knocks out of whatever it crosses. Say nothing and the theme's own background stands.
811
+ A default is a style that every thing of one kind wears without naming it. `node` covers every node; `leaf` a node with no children, and `container` a node with some — the same "has children" the theme's quieter backdrop colors follow, so setting a leaf's fill leaves containers looking like containers. `edge` covers every edge.
812
+
813
+ The more specific word wins, all the way down:
814
+
815
+ 1. the theme
816
+ 2. `diagram`
817
+ 3. `default node`
818
+ 4. `default leaf` or `default container`, or `default edge`
819
+ 5. the thing's styles, a later one winning where two set the same key
820
+ 6. what the thing's own line says
747
821
 
748
- An unknown key is refused by name — `diagram has no "backround" — it takes background` — rather than quietly ignored, the same as every other attribute. See "Attributes".
822
+ A default takes the words a style does, and may name a style of its own with `style:`, which it lays beneath its own words. Unlike a style, it is strict about kind: it names the kind it is for, so a word that kind has no use for can only be a mistake. `default edge fill: red` is refused and points at `line:`. `badge:` is refused on `default leaf` and `default node`, since a badge is a child and would make every leaf a container, and `icon:` is a leaf's alone, since a picture cannot hold children. A `url:` belongs to one thing, never to a kind.
823
+
824
+ Each kind is written at most once. `default leaf` twice is an error naming both lines, and `default leaf node` is refused with `default leaf` — a leaf is already a node.
749
825
 
750
826
  ## What the language refuses
751
827
 
@@ -810,6 +886,19 @@ That one was found by testing the lexer, not by rendering — and it could not h
810
886
 
811
887
  Pre-1.0, so the minor number is where a breaking change goes. Every removal below is refused by name with the replacement quoted, rather than dropped in silence — an older file stops with an error saying what to write instead.
812
888
 
889
+ **0.5.0**
890
+
891
+ - Named themes: `diagram theme: nord`, one of thirteen, and `--theme` on the command line to render a file in another without editing it. The playground has a theme picker, which writes the `theme:` line into the source.
892
+ - `diagram text: (color: …)` sets every text's color at once.
893
+ - `default node | leaf | container | edge` sets what every thing of that kind looks like unless it says otherwise.
894
+
895
+ **0.4.0**
896
+
897
+ - A node with any children takes the theme's container colors, however the children are placed. A node with a `badge:` or a single child in a corner now draws as a container too; in 0.3.0 only a node with a title band did. Its text still sits where it did. Say `fill:` and `border:` to keep one looking like a leaf.
898
+ - A text's `size` takes a plain number of pixels beside `small`, `normal` and `large`: `(size: 22)`.
899
+ - **Breaking:** `deck` is an attribute of the node, not a statement. `deck drive "Drive 2" "Drive 3"` is written `deck: "Drive 2" "Drive 3"` on `node drive`, and the old statement is an error that quotes that line back. It is refused on a picture and on a `shape: none` node, where the statement used to draw nothing.
900
+ - **Breaking:** a key written twice on one statement is an error that quotes both values — `fill: red fill: blue`, `style:` twice, or the same property twice inside one set of brackets. In 0.3.0 one of the two was dropped in silence. A node's own word overriding its style's is unaffected.
901
+ - `style:` takes a list, `style: equipment, critical and alarm`, applied in order with a later style winning where two set the same key.
813
902
  **0.3.0** — the vocabulary, reworked in one breaking version so there is one migration rather than five.
814
903
 
815
904
  - `box` is `node` and `link` is `edge`. The string on either is its *text*; "label" is not a word the language has.
package/dist/ast.d.ts CHANGED
@@ -272,14 +272,6 @@ export interface EdgeStmt {
272
272
  attrs: Attrs;
273
273
  line: number;
274
274
  }
275
- export interface DeckStmt {
276
- kind: 'deck';
277
- /** The container to draw with offset copies behind it. */
278
- name: string;
279
- /** One text per copy, back to front as written. */
280
- texts: string[];
281
- line: number;
282
- }
283
275
  /**
284
276
  * `diagram background: #111111` — settings that belong to the drawing as a
285
277
  * whole rather than to anything in it. It has no name because there is only
@@ -290,8 +282,38 @@ export interface DiagramStmt {
290
282
  attrs: Attrs;
291
283
  line: number;
292
284
  }
293
- /** The attributes a `diagram` statement understands. */
294
- export declare const DIAGRAM_KEYS: readonly ["background"];
285
+ /**
286
+ * The attributes a `diagram` statement understands. `text` takes a bracket,
287
+ * `text: (color: …)`, and sets the text color of everything at once — the
288
+ * theme has one text color, shared by nodes and edges, and this is that.
289
+ */
290
+ export declare const DIAGRAM_KEYS: readonly ["theme", "background", "text"];
291
+ /**
292
+ * `default leaf fill: #2e5d3a` — a style that applies to every thing of one
293
+ * kind without being named. The more specific wins: `node` covers every node,
294
+ * `leaf` and `container` beat it for the nodes they cover, and a thing's own
295
+ * styles and words beat any default.
296
+ */
297
+ export interface DefaultStmt {
298
+ kind: 'default';
299
+ target: DefaultTarget;
300
+ attrs: Attrs;
301
+ line: number;
302
+ }
303
+ export declare const DEFAULT_TARGETS: readonly ["node", "leaf", "container", "edge"];
304
+ export type DefaultTarget = (typeof DEFAULT_TARGETS)[number];
305
+ /**
306
+ * What each default may say. A default carries a style's vocabulary, less the
307
+ * words that would contradict the kind it is written for:
308
+ *
309
+ * - A default names its kind, so it is strict where a style is permissive —
310
+ * `default edge fill:` can only be a mistake.
311
+ * - `badge:` gives a box a child, which makes it a container, so every leaf
312
+ * given one by `default leaf` or `default node` would stop being a leaf.
313
+ * - `icon:` draws a picture, and a picture cannot hold children, so it is a
314
+ * leaf's word and not a container's or every node's.
315
+ */
316
+ export declare const DEFAULT_KEYS: Record<DefaultTarget, readonly string[]>;
295
317
  /**
296
318
  * The attributes whose value is a color rather than text. A color is written
297
319
  * as the viewer will receive it and the renderer keeps no list of color words
@@ -373,7 +395,7 @@ export interface StyleStmt {
373
395
  attrs: Attrs;
374
396
  line: number;
375
397
  }
376
- export type Stmt = NodeStmt | EdgeStmt | DeckStmt | StyleStmt | DiagramStmt;
398
+ export type Stmt = NodeStmt | EdgeStmt | StyleStmt | DiagramStmt | DefaultStmt;
377
399
  export interface Document {
378
400
  statements: Stmt[];
379
401
  }
package/dist/ast.js CHANGED
@@ -222,8 +222,30 @@ export function describeAxis(axis) {
222
222
  * `align` is how its lines range against each other once it is there.
223
223
  */
224
224
  export const TEXT_KEYS = ['color', 'size', 'wrap', 'align', 'at'];
225
- /** The attributes a `diagram` statement understands. */
226
- export const DIAGRAM_KEYS = ['background'];
225
+ /**
226
+ * The attributes a `diagram` statement understands. `text` takes a bracket,
227
+ * `text: (color: …)`, and sets the text color of everything at once — the
228
+ * theme has one text color, shared by nodes and edges, and this is that.
229
+ */
230
+ export const DIAGRAM_KEYS = ['theme', 'background', 'text'];
231
+ export const DEFAULT_TARGETS = ['node', 'leaf', 'container', 'edge'];
232
+ /**
233
+ * What each default may say. A default carries a style's vocabulary, less the
234
+ * words that would contradict the kind it is written for:
235
+ *
236
+ * - A default names its kind, so it is strict where a style is permissive —
237
+ * `default edge fill:` can only be a mistake.
238
+ * - `badge:` gives a box a child, which makes it a container, so every leaf
239
+ * given one by `default leaf` or `default node` would stop being a leaf.
240
+ * - `icon:` draws a picture, and a picture cannot hold children, so it is a
241
+ * leaf's word and not a container's or every node's.
242
+ */
243
+ export const DEFAULT_KEYS = {
244
+ node: ['style', 'shape', 'fill', 'border', 'text'],
245
+ leaf: ['style', 'shape', 'icon', 'fill', 'border', 'text'],
246
+ container: ['style', 'shape', 'badge', 'fill', 'border', 'text'],
247
+ edge: ['style', 'line', 'text'],
248
+ };
227
249
  /**
228
250
  * The attributes whose value is a color rather than text. A color is written
229
251
  * as the viewer will receive it and the renderer keeps no list of color words
@@ -290,7 +312,7 @@ export const COLOR_PARTS = {
290
312
  * an edge is not placed — it joins two things that are.
291
313
  */
292
314
  export const ATTR_KEYS = {
293
- shape: ['style', 'gap', 'overlap', 'contents', 'badge', 'shape', 'fill', 'border', 'text', 'url'],
315
+ shape: ['style', 'gap', 'overlap', 'contents', 'badge', 'deck', 'shape', 'fill', 'border', 'text', 'url'],
294
316
  icon: ['style', 'gap', 'overlap', 'icon', 'text', 'url'],
295
317
  none: ['style', 'gap', 'overlap', 'shape', 'text', 'url'],
296
318
  edge: ['style', 'from', 'to', 'line', 'text', 'url'],
package/dist/cli.js CHANGED
@@ -3,12 +3,15 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
3
  import { dirname, resolve as resolvePath } from 'node:path';
4
4
  import { SourceError } from './errors.js';
5
5
  import { compile } from './index.js';
6
+ import { THEME_NAMES, THEMES } from './themes.js';
6
7
  const USAGE = `reladraw — render a diagram from stated placement
7
8
 
8
- reladraw <input.reladraw> [-o <output.svg>]
9
+ reladraw <input.reladraw> [-o <output.svg>] [--theme <name>]
9
10
 
10
11
  -o, --out where to write the SVG. Defaults to the input path with
11
12
  its extension replaced by .svg. Use - for standard output.
13
+ --theme render in this theme, whatever the file's \`diagram theme:\`
14
+ says. One of ${THEME_NAMES.join(', ')}.
12
15
  -h, --help print this.
13
16
  `;
14
17
  async function main(argv) {
@@ -18,6 +21,7 @@ async function main(argv) {
18
21
  }
19
22
  let input;
20
23
  let out;
24
+ let theme;
21
25
  for (let i = 0; i < argv.length; i += 1) {
22
26
  const arg = argv[i];
23
27
  if (arg === '-o' || arg === '--out') {
@@ -28,6 +32,18 @@ async function main(argv) {
28
32
  }
29
33
  i += 1;
30
34
  }
35
+ else if (arg === '--theme') {
36
+ theme = argv[i + 1];
37
+ if (theme === undefined) {
38
+ process.stderr.write(`reladraw: --theme needs a name: ${THEME_NAMES.join(', ')}\n`);
39
+ return 1;
40
+ }
41
+ if (THEMES[theme] === undefined) {
42
+ process.stderr.write(`reladraw: there is no theme called "${theme}" — the themes are ${THEME_NAMES.join(', ')}\n`);
43
+ return 1;
44
+ }
45
+ i += 1;
46
+ }
31
47
  else if (arg.startsWith('-') && arg !== '-') {
32
48
  process.stderr.write(`reladraw: unknown option ${arg}\n`);
33
49
  return 1;
@@ -47,7 +63,7 @@ async function main(argv) {
47
63
  const source = await readFile(input, 'utf8');
48
64
  let svg;
49
65
  try {
50
- svg = compile(source);
66
+ svg = compile(source, theme === undefined ? {} : { theme: THEMES[theme] });
51
67
  }
52
68
  catch (error) {
53
69
  if (error instanceof SourceError) {
@@ -98,10 +98,10 @@ export declare const ICON_LINES = 2;
98
98
  export declare const ICON_GAP = 10;
99
99
  export declare const DEFAULT_FONT_SIZE = 14;
100
100
  /**
101
- * The named text sizes, each a multiple of the document's own size. Named
102
- * rather than numeric for the reason gaps are: a number here is typography by
103
- * coordinate. It goes stale the moment the document is set at another size, and
104
- * it says nothing about why one piece of text is smaller than another.
101
+ * The named text sizes, each a multiple of the document's own size. A plain
102
+ * number of pixels is accepted too, as it is for a gap: nothing else in the
103
+ * diagram moving can make it wrong. The names stay the default because they
104
+ * follow the document's size when that is retuned, and a number does not.
105
105
  *
106
106
  * `small` is sampled rather than chosen. In
107
107
  * `examples/reference/arch.png` the box and container texts run 25
package/dist/constants.js CHANGED
@@ -110,10 +110,10 @@ export const ICON_LINES = 2;
110
110
  export const ICON_GAP = 10;
111
111
  export const DEFAULT_FONT_SIZE = 14;
112
112
  /**
113
- * The named text sizes, each a multiple of the document's own size. Named
114
- * rather than numeric for the reason gaps are: a number here is typography by
115
- * coordinate. It goes stale the moment the document is set at another size, and
116
- * it says nothing about why one piece of text is smaller than another.
113
+ * The named text sizes, each a multiple of the document's own size. A plain
114
+ * number of pixels is accepted too, as it is for a gap: nothing else in the
115
+ * diagram moving can make it wrong. The names stay the default because they
116
+ * follow the document's size when that is retuned, and a number does not.
117
117
  *
118
118
  * `small` is sampled rather than chosen. In
119
119
  * `examples/reference/arch.png` the box and container texts run 25
@@ -143,10 +143,17 @@ const DEFAULT_TEXT_SIZE = { none: 'small' };
143
143
  export function fontSizeFor(kind, textAttrs, fontSize, line) {
144
144
  const named = textAttrs['size'] ?? DEFAULT_TEXT_SIZE[kind] ?? 'normal';
145
145
  const scale = TEXT_SIZES[named];
146
- if (scale === undefined) {
147
- throw new SourceError(`size takes ${Object.keys(TEXT_SIZES).join(', ')}, not "${named}"`, line);
148
- }
149
- return Math.round(fontSize * scale);
146
+ if (scale !== undefined)
147
+ return Math.round(fontSize * scale);
148
+ if (/^\d+(\.\d+)?$/.test(named) && Number(named) > 0)
149
+ return Number(named);
150
+ const unit = named.match(/^(\d+(?:\.\d+)?)px$/);
151
+ const hint = unit
152
+ ? `; write "size: ${unit[1]}", a size's number is already in pixels`
153
+ : named.startsWith('-') || /^0+(\.0+)?$/.test(named)
154
+ ? '; a text size has to be more than zero'
155
+ : '';
156
+ throw new SourceError(`size takes ${Object.keys(TEXT_SIZES).join(', ')} or a number of pixels, not "${named}"${hint}`, line);
150
157
  }
151
158
  export const DEFAULT_MARGIN = 40;
152
159
  /**
package/dist/grammar.d.ts CHANGED
@@ -61,7 +61,7 @@ export interface Span {
61
61
  * it. A word missing here is a word that draws in the plain color, which is a
62
62
  * dull page rather than a wrong one.
63
63
  */
64
- export declare const STATEMENT_KEYWORDS: readonly ["node", "edge", "deck", "style", "diagram"];
64
+ export declare const STATEMENT_KEYWORDS: readonly ["node", "edge", "style", "diagram", "default"];
65
65
  /**
66
66
  * Every word that says something about where a thing goes. Assembled from the
67
67
  * lists the parser itself reads, so a direction or a passage axis added there
package/dist/grammar.js CHANGED
@@ -30,9 +30,9 @@ import { DIRECTIONS, POSITIONS, SIDES, PASSAGE_AXES } from './ast.js';
30
30
  * it. A word missing here is a word that draws in the plain color, which is a
31
31
  * dull page rather than a wrong one.
32
32
  */
33
- export const STATEMENT_KEYWORDS = ['node', 'edge', 'deck', 'style', 'diagram'];
33
+ export const STATEMENT_KEYWORDS = ['node', 'edge', 'style', 'diagram', 'default'];
34
34
  /** Statements whose second word declares a name. `diagram` has none. */
35
- const DECLARES_NAME = ['node', 'deck', 'style'];
35
+ const DECLARES_NAME = ['node', 'style'];
36
36
  /**
37
37
  * Every word that says something about where a thing goes. Assembled from the
38
38
  * lists the parser itself reads, so a direction or a passage axis added there
package/dist/index.d.ts CHANGED
@@ -6,7 +6,8 @@ export * from './measure.js';
6
6
  export * from './model.js';
7
7
  export { parse } from './parser.js';
8
8
  export { resolve, type ResolveOptions } from './resolve.js';
9
- export { render, DARK_THEME, type RenderOptions, type Theme } from './render.js';
9
+ export { render, type RenderOptions } from './render.js';
10
+ export { DARK_THEME, DEFAULT_THEME, THEMES, THEME_NAMES, type Theme } from './themes.js';
10
11
  import { type RenderOptions } from './render.js';
11
12
  import { type ResolveOptions } from './resolve.js';
12
13
  /** Source text in, SVG out. The whole pipeline in one call. */
package/dist/index.js CHANGED
@@ -6,7 +6,8 @@ export * from './measure.js';
6
6
  export * from './model.js';
7
7
  export { parse } from './parser.js';
8
8
  export { resolve } from './resolve.js';
9
- export { render, DARK_THEME } from './render.js';
9
+ export { render } from './render.js';
10
+ export { DARK_THEME, DEFAULT_THEME, THEMES, THEME_NAMES } from './themes.js';
10
11
  import { parse } from './parser.js';
11
12
  import { render } from './render.js';
12
13
  import { resolve } from './resolve.js';
package/dist/model.d.ts CHANGED
@@ -57,9 +57,10 @@ export interface LayoutNode {
57
57
  /**
58
58
  * Whether things stack beside this node's text — below it, or above it when
59
59
  * the text is at the bottom. That is what a title band *is*, so it is what
60
- * decides a container's look and its text's defaults, rather than whether
61
- * the node has children: a node whose only child sits beside its text or in
62
- * a corner has nothing stacked below the text, and draws as a leaf.
60
+ * decides its text's defaults, rather than whether the node has children: a
61
+ * node whose only child sits beside its text or in a corner has nothing
62
+ * stacked below the text, and its text sits as a leaf's does. Its *colors*
63
+ * follow having children, not this.
63
64
  */
64
65
  banded: boolean;
65
66
  /**