reladraw 0.4.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 +37 -0
- package/README.md +1 -1
- package/SYNTAX.md +65 -5
- package/dist/ast.d.ts +33 -3
- package/dist/ast.js +24 -2
- package/dist/cli.js +18 -2
- package/dist/grammar.d.ts +1 -1
- package/dist/grammar.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/parser.js +72 -1
- package/dist/render.d.ts +1 -22
- package/dist/render.js +13 -28
- package/dist/resolve.js +63 -6
- package/dist/themes.d.ts +46 -0
- package/dist/themes.js +190 -0
- package/package.json +1 -1
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.
|
|
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.
|
|
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
|
|
|
@@ -630,7 +630,7 @@ Every attribute, and what takes one. The kinds here are what a node's **body** i
|
|
|
630
630
|
| `line` | | | | ✓ | color |
|
|
631
631
|
| `url` | ✓ | ✓ | ✓ | ✓ | a destination to open when the thing is clicked |
|
|
632
632
|
|
|
633
|
-
The `diagram` statement has a vocabulary of its own — `
|
|
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".
|
|
634
634
|
|
|
635
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:
|
|
636
636
|
|
|
@@ -762,12 +762,66 @@ diagram <attributes>
|
|
|
762
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.
|
|
763
763
|
|
|
764
764
|
```
|
|
765
|
-
diagram background: #
|
|
765
|
+
diagram theme: nord background: #1e2229 text: (color: #e0e0e0)
|
|
766
766
|
```
|
|
767
767
|
|
|
768
|
-
|
|
768
|
+
Three attributes:
|
|
769
769
|
|
|
770
|
-
|
|
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)
|
|
809
|
+
```
|
|
810
|
+
|
|
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
|
|
821
|
+
|
|
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.
|
|
771
825
|
|
|
772
826
|
## What the language refuses
|
|
773
827
|
|
|
@@ -832,6 +886,12 @@ That one was found by testing the lexer, not by rendering — and it could not h
|
|
|
832
886
|
|
|
833
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.
|
|
834
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
|
+
|
|
835
895
|
**0.4.0**
|
|
836
896
|
|
|
837
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.
|
package/dist/ast.d.ts
CHANGED
|
@@ -282,8 +282,38 @@ export interface DiagramStmt {
|
|
|
282
282
|
attrs: Attrs;
|
|
283
283
|
line: number;
|
|
284
284
|
}
|
|
285
|
-
/**
|
|
286
|
-
|
|
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[]>;
|
|
287
317
|
/**
|
|
288
318
|
* The attributes whose value is a color rather than text. A color is written
|
|
289
319
|
* as the viewer will receive it and the renderer keeps no list of color words
|
|
@@ -365,7 +395,7 @@ export interface StyleStmt {
|
|
|
365
395
|
attrs: Attrs;
|
|
366
396
|
line: number;
|
|
367
397
|
}
|
|
368
|
-
export type Stmt = NodeStmt | EdgeStmt | StyleStmt | DiagramStmt;
|
|
398
|
+
export type Stmt = NodeStmt | EdgeStmt | StyleStmt | DiagramStmt | DefaultStmt;
|
|
369
399
|
export interface Document {
|
|
370
400
|
statements: Stmt[];
|
|
371
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
|
-
/**
|
|
226
|
-
|
|
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
|
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) {
|
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", "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,7 +30,7 @@ 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', '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
35
|
const DECLARES_NAME = ['node', 'style'];
|
|
36
36
|
/**
|
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,
|
|
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
|
|
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/parser.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { COLOR_KEYS, DIAGRAM_KEYS, DIRECTIONS, describePlacement, SIDE_AXIS, SIDES, PASSAGE_AXES, TEXT_KEYS, CONTENTS_KEYS, PLACEMENT_KEYS, BOUNDARY_PARTS, INWARD, OPPOSITE, isDirection, isPart, isPosition, listTargets, nameTarget, } from './ast.js';
|
|
1
|
+
import { COLOR_KEYS, DEFAULT_KEYS, DEFAULT_TARGETS, DIAGRAM_KEYS, DIRECTIONS, describePlacement, SIDE_AXIS, SIDES, PASSAGE_AXES, TEXT_KEYS, CONTENTS_KEYS, PLACEMENT_KEYS, BOUNDARY_PARTS, INWARD, OPPOSITE, isDirection, isPart, isPosition, listTargets, nameTarget, } from './ast.js';
|
|
2
2
|
import { SourceError } from './errors.js';
|
|
3
3
|
import { isAttrKey, tokenizeLine } from './lexer.js';
|
|
4
|
+
import { THEME_NAMES, THEMES } from './themes.js';
|
|
4
5
|
/** Parse a whole source file. One statement per line; blanks and comments drop out. */
|
|
5
6
|
export function parse(source) {
|
|
6
7
|
const statements = [];
|
|
@@ -27,6 +28,8 @@ function parseStatement(tokens, line) {
|
|
|
27
28
|
return parseStyle(tokens, line);
|
|
28
29
|
case 'diagram':
|
|
29
30
|
return parseDiagram(tokens, line);
|
|
31
|
+
case 'default':
|
|
32
|
+
return parseDefault(tokens, line);
|
|
30
33
|
default:
|
|
31
34
|
throw new SourceError(substitution(keyword.text, tokens), line);
|
|
32
35
|
}
|
|
@@ -502,12 +505,80 @@ function parseDiagram(head, line) {
|
|
|
502
505
|
throw new SourceError('diagram sets nothing', line);
|
|
503
506
|
}
|
|
504
507
|
for (const key of Object.keys(attrs)) {
|
|
508
|
+
if (key.startsWith('text.')) {
|
|
509
|
+
// The theme has one text color, shared by everything, and that is all
|
|
510
|
+
// the diagram's text sets. A size or a wrap is about one kind of text.
|
|
511
|
+
if (key !== 'text.color') {
|
|
512
|
+
const inner = key.slice('text.'.length);
|
|
513
|
+
throw new SourceError(`diagram text: sets only a color — \`${inner}\` is about one kind of text, so write ` +
|
|
514
|
+
`\`text: (${inner}: ${attrs[key]})\` on a \`default node\` or \`default edge\``, line);
|
|
515
|
+
}
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
505
518
|
if (!DIAGRAM_KEYS.includes(key)) {
|
|
506
519
|
throw new SourceError(`diagram has no "${key}" — it takes ${DIAGRAM_KEYS.join(', ')}`, line);
|
|
507
520
|
}
|
|
508
521
|
}
|
|
522
|
+
if (attrs['text'] !== undefined) {
|
|
523
|
+
throw new SourceError('diagram text: takes a bracket — `text: (color: #e0e0e0)`', line);
|
|
524
|
+
}
|
|
525
|
+
const theme = attrs['theme'];
|
|
526
|
+
if (theme !== undefined && THEMES[theme] === undefined) {
|
|
527
|
+
throw new SourceError(`there is no theme called "${theme}" — the themes are ${THEME_NAMES.join(', ')}`, line);
|
|
528
|
+
}
|
|
509
529
|
return { kind: 'diagram', attrs, line };
|
|
510
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
* `default <node | leaf | container | edge> <attributes>` — a style every thing
|
|
533
|
+
* of that kind wears without naming it. It is strict about kind where a style
|
|
534
|
+
* is permissive, because it names the kind it is for: a word that kind has no
|
|
535
|
+
* use for can only be a mistake.
|
|
536
|
+
*/
|
|
537
|
+
function parseDefault(head, line) {
|
|
538
|
+
const target = head[1];
|
|
539
|
+
if (!target || target.quoted || !DEFAULT_TARGETS.includes(target.text)) {
|
|
540
|
+
throw new SourceError(`default needs the kind it is for — ${DEFAULT_TARGETS.join(', ')}` +
|
|
541
|
+
(target && !isAttrKey(target) ? `, not "${target.text}"` : ''), line);
|
|
542
|
+
}
|
|
543
|
+
const kind = target.text;
|
|
544
|
+
const subject = `default ${kind}`;
|
|
545
|
+
// `default leaf node` reads naturally and says nothing `default leaf` does not.
|
|
546
|
+
const extra = head[2];
|
|
547
|
+
if (extra && !extra.quoted && !isAttrKey(extra) && extra.text === 'node' && kind !== 'node') {
|
|
548
|
+
throw new SourceError(`write \`default ${kind}\` — a ${kind} is already a node`, line);
|
|
549
|
+
}
|
|
550
|
+
const attrs = attrsOnly(head, 2, line, subject);
|
|
551
|
+
if (Object.keys(attrs).length === 0) {
|
|
552
|
+
throw new SourceError(`${subject} sets nothing`, line);
|
|
553
|
+
}
|
|
554
|
+
const allowed = DEFAULT_KEYS[kind];
|
|
555
|
+
for (const key of new Set(Object.keys(attrs).map((k) => k.split('.')[0]))) {
|
|
556
|
+
if (allowed.includes(key))
|
|
557
|
+
continue;
|
|
558
|
+
throw new SourceError(`${subject} has ${key}:, ${defaultRefusal(kind, key)}`, line);
|
|
559
|
+
}
|
|
560
|
+
return { kind: 'default', target: kind, attrs, line };
|
|
561
|
+
}
|
|
562
|
+
/** Why a default cannot carry a word, and what to write instead. */
|
|
563
|
+
function defaultRefusal(kind, key) {
|
|
564
|
+
const takes = `it takes ${DEFAULT_KEYS[kind].map((k) => `${k}:`).join(', ')}`;
|
|
565
|
+
if (key === 'url')
|
|
566
|
+
return `and a destination belongs to one thing rather than to every one of a kind`;
|
|
567
|
+
if (key === 'badge') {
|
|
568
|
+
return 'and a badge is a child, so every leaf given one would become a container. ' +
|
|
569
|
+
'Put the badge in a style, or write `default container badge:`';
|
|
570
|
+
}
|
|
571
|
+
if (key === 'icon') {
|
|
572
|
+
return 'and a picture cannot hold children, so an icon is a leaf\'s word — write `default leaf icon:`';
|
|
573
|
+
}
|
|
574
|
+
if (kind === 'edge' && (key === 'fill' || key === 'border')) {
|
|
575
|
+
return `which an edge does not have — an edge is colored by line: and text: (color: …)`;
|
|
576
|
+
}
|
|
577
|
+
if (kind !== 'edge' && key === 'line') {
|
|
578
|
+
return `which is an edge's — a node is colored by fill:, border: and text: (color: …)`;
|
|
579
|
+
}
|
|
580
|
+
return `which a default does not set — ${takes}`;
|
|
581
|
+
}
|
|
511
582
|
function requireName(token, keyword, line) {
|
|
512
583
|
if (!token || token.quoted) {
|
|
513
584
|
throw new SourceError(`${keyword} needs a name`, line);
|
package/dist/render.d.ts
CHANGED
|
@@ -1,31 +1,10 @@
|
|
|
1
1
|
import { type Measurer } from './measure.js';
|
|
2
2
|
import type { Layout } from './model.js';
|
|
3
|
+
import { type Theme } from './themes.js';
|
|
3
4
|
export interface RenderOptions {
|
|
4
5
|
measurer?: Measurer;
|
|
5
6
|
fontSize?: number;
|
|
6
7
|
theme?: Theme;
|
|
7
8
|
}
|
|
8
|
-
export interface Theme {
|
|
9
|
-
background: string;
|
|
10
|
-
boxFill: string;
|
|
11
|
-
boxStroke: string;
|
|
12
|
-
containerFill: string;
|
|
13
|
-
/** A container is a region rather than a thing, so its outline is quieter. */
|
|
14
|
-
containerStroke: string;
|
|
15
|
-
text: string;
|
|
16
|
-
mutedText: string;
|
|
17
|
-
edge: string;
|
|
18
|
-
/** An icon's drawn line. */
|
|
19
|
-
iconInk: string;
|
|
20
|
-
/** The body an icon's lines enclose. */
|
|
21
|
-
iconShade: string;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Sampled out of `examples/reference/arch.png` rather than invented,
|
|
25
|
-
* so the benchmark render and the drawing it is measured against differ by
|
|
26
|
-
* geometry and typography alone. A container is a shade off the page and barely
|
|
27
|
-
* outlined; a leaf is the navy that carries the diagram's weight.
|
|
28
|
-
*/
|
|
29
|
-
export declare const DARK_THEME: Theme;
|
|
30
9
|
/** Turn solved geometry into a standalone SVG document. */
|
|
31
10
|
export declare function render(layout: Layout, options?: RenderOptions): string;
|
package/dist/render.js
CHANGED
|
@@ -3,40 +3,25 @@ import { describeAxis } from './ast.js';
|
|
|
3
3
|
import { SourceError } from './errors.js';
|
|
4
4
|
import { ICON_STROKE } from './icons.js';
|
|
5
5
|
import { monospaceMeasurer } from './measure.js';
|
|
6
|
+
import { DARK_THEME, THEMES } from './themes.js';
|
|
6
7
|
import { plain } from './text.js';
|
|
7
|
-
/**
|
|
8
|
-
* Sampled out of `examples/reference/arch.png` rather than invented,
|
|
9
|
-
* so the benchmark render and the drawing it is measured against differ by
|
|
10
|
-
* geometry and typography alone. A container is a shade off the page and barely
|
|
11
|
-
* outlined; a leaf is the navy that carries the diagram's weight.
|
|
12
|
-
*/
|
|
13
|
-
export const DARK_THEME = {
|
|
14
|
-
background: '#111111',
|
|
15
|
-
boxFill: '#191728',
|
|
16
|
-
boxStroke: '#4f5367',
|
|
17
|
-
containerFill: '#191920',
|
|
18
|
-
containerStroke: '#25242f',
|
|
19
|
-
text: '#d9d9d9',
|
|
20
|
-
mutedText: '#8b8b8b',
|
|
21
|
-
edge: '#5c5c7c',
|
|
22
|
-
// Both sampled off the reference's machine glyphs. Note that the reference
|
|
23
|
-
// gives each icon its own hue — the drive is gray, the laptop periwinkle, the
|
|
24
|
-
// workstation violet — which is a drawing tool's per-shape default and not a
|
|
25
|
-
// system. One pair for the whole set is the deliberate difference: an icon
|
|
26
|
-
// should read as part of the diagram's palette, not as clip art dropped in.
|
|
27
|
-
iconInk: '#8d8d8e',
|
|
28
|
-
iconShade: '#3e3d58',
|
|
29
|
-
};
|
|
30
8
|
const CORNER = 8;
|
|
31
9
|
/** Turn solved geometry into a standalone SVG document. */
|
|
32
10
|
export function render(layout, options = {}) {
|
|
33
11
|
const measurer = options.measurer ?? monospaceMeasurer();
|
|
34
12
|
const fontSize = options.fontSize ?? DEFAULT_FONT_SIZE;
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
13
|
+
// A theme passed in — the command line's `--theme` — beats the one the file
|
|
14
|
+
// names, so one source renders in either. `diagram background:` and `text:`
|
|
15
|
+
// are the author overruling a color of whichever theme that is, and a color
|
|
16
|
+
// written by hand wins over any theme, so they are folded in afterwards and
|
|
17
|
+
// everything downstream sees one theme.
|
|
18
|
+
const named = layout.diagram['theme'];
|
|
19
|
+
const base = options.theme ?? (named === undefined ? undefined : THEMES[named]) ?? DARK_THEME;
|
|
20
|
+
const theme = {
|
|
21
|
+
...base,
|
|
22
|
+
...(layout.diagram['background'] !== undefined && { background: layout.diagram['background'] }),
|
|
23
|
+
...(layout.diagram['text.color'] !== undefined && { text: layout.diagram['text.color'] }),
|
|
24
|
+
};
|
|
40
25
|
const body = [];
|
|
41
26
|
for (const root of layout.roots) {
|
|
42
27
|
body.push(drawNode(root, theme, measurer, fontSize, layout.markup));
|
package/dist/resolve.js
CHANGED
|
@@ -23,11 +23,12 @@ export function resolve(doc, options = {}) {
|
|
|
23
23
|
const margin = options.margin ?? DEFAULT_MARGIN;
|
|
24
24
|
const styles = collectStyles(doc.statements);
|
|
25
25
|
checkStyleKeys(doc.statements);
|
|
26
|
-
const
|
|
26
|
+
const defaults = collectDefaults(doc.statements, styles);
|
|
27
|
+
const { nodes, byName, roots } = buildTree(doc.statements, styles, defaults);
|
|
27
28
|
// Edges are resolved to nodes before anything is sized, because a labeled
|
|
28
29
|
// edge claims room in the gap it crosses and so has to be in hand while the
|
|
29
30
|
// gaps are being worked out. Nothing here reads geometry.
|
|
30
|
-
const edges = buildEdges(doc.statements, byName, styles);
|
|
31
|
+
const edges = buildEdges(doc.statements, byName, styles, defaults);
|
|
31
32
|
const local = new Map();
|
|
32
33
|
for (const root of roots)
|
|
33
34
|
sizeNode(root, edges, measurer, fontSize, local);
|
|
@@ -58,6 +59,40 @@ function collectStyles(statements) {
|
|
|
58
59
|
}
|
|
59
60
|
return styles;
|
|
60
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Each kind's default, with any style it names already folded in beneath its
|
|
64
|
+
* own words, so a default arrives at a node as one flat bundle. The same kind
|
|
65
|
+
* written twice is refused, as a second `diagram` is: nothing says which was
|
|
66
|
+
* meant.
|
|
67
|
+
*/
|
|
68
|
+
function collectDefaults(statements, styles) {
|
|
69
|
+
const defaults = new Map();
|
|
70
|
+
const seen = new Map();
|
|
71
|
+
for (const stmt of statements) {
|
|
72
|
+
if (stmt.kind !== 'default')
|
|
73
|
+
continue;
|
|
74
|
+
const earlier = seen.get(stmt.target);
|
|
75
|
+
if (earlier !== undefined) {
|
|
76
|
+
throw new SourceError(`default ${stmt.target} is written twice, here and on line ${earlier} — keep one`, stmt.line);
|
|
77
|
+
}
|
|
78
|
+
seen.set(stmt.target, stmt.line);
|
|
79
|
+
const { style: _named, ...flat } = appearanceOf(stmt.attrs, styles, stmt.line);
|
|
80
|
+
defaults.set(stmt.target, flat);
|
|
81
|
+
}
|
|
82
|
+
return defaults;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* One bundle laid over a weaker one, key by key — except the body, which is
|
|
86
|
+
* one thing said by either of two words. A default's `shape: cylinder` under
|
|
87
|
+
* a style's `icon: disk` is not a node with two bodies; the style said what
|
|
88
|
+
* this node is drawn as, and that settles it.
|
|
89
|
+
*/
|
|
90
|
+
function over(base, top) {
|
|
91
|
+
if (top['shape'] === undefined && top['icon'] === undefined)
|
|
92
|
+
return { ...base, ...top };
|
|
93
|
+
const { shape: _shape, icon: _icon, ...rest } = base;
|
|
94
|
+
return { ...rest, ...top };
|
|
95
|
+
}
|
|
61
96
|
/** A file holds one diagram, so a second `diagram` statement is a mistake. */
|
|
62
97
|
function collectDiagram(statements) {
|
|
63
98
|
let found;
|
|
@@ -70,12 +105,27 @@ function collectDiagram(statements) {
|
|
|
70
105
|
}
|
|
71
106
|
return found ?? {};
|
|
72
107
|
}
|
|
73
|
-
function buildTree(statements, styles) {
|
|
108
|
+
function buildTree(statements, styles, defaults) {
|
|
74
109
|
const nodes = [];
|
|
75
110
|
const byName = new Map();
|
|
76
111
|
const roots = [];
|
|
77
112
|
/** Each badge child's name, and the node whose `badge:` it was written out from. */
|
|
78
113
|
const badges = new Map();
|
|
114
|
+
// Which nodes will hold children has to be known before any node is built,
|
|
115
|
+
// because it decides which default a node wears, and its children are
|
|
116
|
+
// declared after it. A child is a dotted name, or a badge the node's own
|
|
117
|
+
// line or styles give it; a default cannot give a leaf a badge, so what
|
|
118
|
+
// the defaults say cannot change the answer.
|
|
119
|
+
const parents = new Set();
|
|
120
|
+
for (const stmt of statements) {
|
|
121
|
+
if (stmt.kind !== 'node')
|
|
122
|
+
continue;
|
|
123
|
+
const cut = stmt.name.lastIndexOf('.');
|
|
124
|
+
if (cut !== -1)
|
|
125
|
+
parents.add(stmt.name.slice(0, cut));
|
|
126
|
+
if (appearanceOf(stmt.attrs, styles, stmt.line)['badge'] !== undefined)
|
|
127
|
+
parents.add(stmt.name);
|
|
128
|
+
}
|
|
79
129
|
for (const stmt of statements) {
|
|
80
130
|
if (stmt.kind !== 'node')
|
|
81
131
|
continue;
|
|
@@ -87,7 +137,9 @@ function buildTree(statements, styles) {
|
|
|
87
137
|
}
|
|
88
138
|
throw new SourceError(`"${stmt.name}" is declared twice`, stmt.line);
|
|
89
139
|
}
|
|
90
|
-
|
|
140
|
+
// Weakest first: every node's default, then the leaf's or the container's,
|
|
141
|
+
// then the node's own styles and words over both.
|
|
142
|
+
const appearance = over(over(defaults.get('node') ?? {}, defaults.get(parents.has(stmt.name) ? 'container' : 'leaf') ?? {}), appearanceOf(stmt.attrs, styles, stmt.line));
|
|
91
143
|
const body = bodyFor(stmt.attrs, appearance, stmt.line);
|
|
92
144
|
const kind = KIND_OF_BODY[body.kind];
|
|
93
145
|
// A node with no text of its own is labelled with its name, because the
|
|
@@ -275,6 +327,11 @@ function checkAttrs(kind, name, attrs, line) {
|
|
|
275
327
|
const parts = COLOR_PARTS[kind].map((part) => `\`${part}\``).join(', ');
|
|
276
328
|
throw new SourceError(`"${name}" is ${article(KIND_WORD[kind])} and has ${wrote}. ${capital(article(KIND_WORD[kind]))} is ${KIND_PARTS[kind]}, so it has no ${key} — it takes ${parts}`, line);
|
|
277
329
|
}
|
|
330
|
+
// A word only the whole drawing takes, such as a theme.
|
|
331
|
+
if (belongTo(key).length === 0) {
|
|
332
|
+
throw new SourceError(`"${name}" is ${article(KIND_WORD[kind])} and has ${wrote}. \`${key}:\` is said about the ` +
|
|
333
|
+
`whole drawing — write \`diagram ${key}: ${shown}\``, line);
|
|
334
|
+
}
|
|
278
335
|
// Anything else names no part, so what the kind is made of explains
|
|
279
336
|
// nothing. What does explain it is where the word *does* belong, which is
|
|
280
337
|
// also the more useful thing to be told: the author has usually written a
|
|
@@ -373,7 +430,7 @@ function appearanceOf(attrs, styles, line) {
|
|
|
373
430
|
}
|
|
374
431
|
return { ...merged, ...attrs };
|
|
375
432
|
}
|
|
376
|
-
function buildEdges(statements, byName, styles) {
|
|
433
|
+
function buildEdges(statements, byName, styles, defaults) {
|
|
377
434
|
const edges = [];
|
|
378
435
|
for (const stmt of statements) {
|
|
379
436
|
if (stmt.kind !== 'edge')
|
|
@@ -400,7 +457,7 @@ function buildEdges(statements, byName, styles) {
|
|
|
400
457
|
}),
|
|
401
458
|
...(stmt.between.axis !== undefined ? { axis: stmt.between.axis } : {}),
|
|
402
459
|
};
|
|
403
|
-
const appearance = appearanceOf(stmt.attrs, styles, stmt.line);
|
|
460
|
+
const appearance = { ...defaults.get('edge'), ...appearanceOf(stmt.attrs, styles, stmt.line) };
|
|
404
461
|
const what = `${stmt.from} -> ${stmt.to}`;
|
|
405
462
|
checkAttrs('edge', what, stmt.attrs, stmt.line);
|
|
406
463
|
checkStyleUse('edge', what, stmt.attrs, styles, stmt.line);
|
package/dist/themes.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The named themes a file chooses with `diagram theme: <name>`, and the one it
|
|
3
|
+
* gets when it says nothing.
|
|
4
|
+
*
|
|
5
|
+
* A theme supplies every color the file does not state. It never supplies a
|
|
6
|
+
* size or a distance, so switching themes cannot move anything — geometry may
|
|
7
|
+
* not depend on appearance.
|
|
8
|
+
*
|
|
9
|
+
* Most of the palettes are borrowed from editor color schemes, all of them MIT
|
|
10
|
+
* licensed and credited in NOTICE. Those palettes were made for code, where a
|
|
11
|
+
* color marks a keyword or a string; a diagram needs a page, two box fills, a
|
|
12
|
+
* border, text and a line. So each is the scheme read as a diagram rather than
|
|
13
|
+
* a transfer of it: the page is the scheme's background, a leaf is its raised
|
|
14
|
+
* surface, a container sits between the two, and the lines take one of its
|
|
15
|
+
* accents.
|
|
16
|
+
*/
|
|
17
|
+
export interface Theme {
|
|
18
|
+
background: string;
|
|
19
|
+
boxFill: string;
|
|
20
|
+
boxStroke: string;
|
|
21
|
+
containerFill: string;
|
|
22
|
+
/** A container is a region rather than a thing, so its outline is quieter. */
|
|
23
|
+
containerStroke: string;
|
|
24
|
+
text: string;
|
|
25
|
+
mutedText: string;
|
|
26
|
+
edge: string;
|
|
27
|
+
/** An icon's drawn line. */
|
|
28
|
+
iconInk: string;
|
|
29
|
+
/** The body an icon's lines enclose. */
|
|
30
|
+
iconShade: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Sampled out of `examples/reference/arch.png` rather than invented,
|
|
34
|
+
* so the benchmark render and the drawing it is measured against differ by
|
|
35
|
+
* geometry and typography alone. A container is a shade off the page and barely
|
|
36
|
+
* outlined; a leaf is the navy that carries the diagram's weight.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DARK_THEME: Theme;
|
|
39
|
+
/**
|
|
40
|
+
* Every theme a file may name, in the order they are offered. Pairs sit
|
|
41
|
+
* together, dark first, and the two that have no light half follow them.
|
|
42
|
+
*/
|
|
43
|
+
export declare const THEMES: Readonly<Record<string, Theme>>;
|
|
44
|
+
/** The theme a file gets when it names none. */
|
|
45
|
+
export declare const DEFAULT_THEME = "dark";
|
|
46
|
+
export declare const THEME_NAMES: readonly string[];
|
package/dist/themes.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sampled out of `examples/reference/arch.png` rather than invented,
|
|
3
|
+
* so the benchmark render and the drawing it is measured against differ by
|
|
4
|
+
* geometry and typography alone. A container is a shade off the page and barely
|
|
5
|
+
* outlined; a leaf is the navy that carries the diagram's weight.
|
|
6
|
+
*/
|
|
7
|
+
export const DARK_THEME = {
|
|
8
|
+
background: '#111111',
|
|
9
|
+
boxFill: '#191728',
|
|
10
|
+
boxStroke: '#4f5367',
|
|
11
|
+
containerFill: '#191920',
|
|
12
|
+
containerStroke: '#25242f',
|
|
13
|
+
text: '#d9d9d9',
|
|
14
|
+
mutedText: '#8b8b8b',
|
|
15
|
+
edge: '#5c5c7c',
|
|
16
|
+
// Both sampled off the reference's machine glyphs. Note that the reference
|
|
17
|
+
// gives each icon its own hue — the drive is gray, the laptop periwinkle, the
|
|
18
|
+
// workstation violet — which is a drawing tool's per-shape default and not a
|
|
19
|
+
// system. One pair for the whole set is the deliberate difference: an icon
|
|
20
|
+
// should read as part of the diagram's palette, not as clip art dropped in.
|
|
21
|
+
iconInk: '#8d8d8e',
|
|
22
|
+
iconShade: '#3e3d58',
|
|
23
|
+
};
|
|
24
|
+
/** The dark theme's counterpart: the same roles, on a white page. */
|
|
25
|
+
const LIGHT_THEME = {
|
|
26
|
+
background: '#ffffff',
|
|
27
|
+
boxFill: '#eef0f7',
|
|
28
|
+
boxStroke: '#8a90a8',
|
|
29
|
+
containerFill: '#f6f7fa',
|
|
30
|
+
containerStroke: '#dcdfe7',
|
|
31
|
+
text: '#1f2328',
|
|
32
|
+
mutedText: '#6e7781',
|
|
33
|
+
edge: '#7c83a0',
|
|
34
|
+
iconInk: '#57606a',
|
|
35
|
+
iconShade: '#d6d9e6',
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Every theme a file may name, in the order they are offered. Pairs sit
|
|
39
|
+
* together, dark first, and the two that have no light half follow them.
|
|
40
|
+
*/
|
|
41
|
+
export const THEMES = {
|
|
42
|
+
dark: DARK_THEME,
|
|
43
|
+
light: LIGHT_THEME,
|
|
44
|
+
// Solarized, Ethan Schoonover. base03 page, base02 leaves, blue lines.
|
|
45
|
+
'solarized-dark': {
|
|
46
|
+
background: '#002b36',
|
|
47
|
+
boxFill: '#073642',
|
|
48
|
+
boxStroke: '#586e75',
|
|
49
|
+
containerFill: '#03313c',
|
|
50
|
+
containerStroke: '#0b3f4c',
|
|
51
|
+
text: '#93a1a1',
|
|
52
|
+
mutedText: '#657b83',
|
|
53
|
+
edge: '#268bd2',
|
|
54
|
+
iconInk: '#839496',
|
|
55
|
+
iconShade: '#0f4a58',
|
|
56
|
+
},
|
|
57
|
+
// base3 page, base2 leaves, the same blue.
|
|
58
|
+
'solarized-light': {
|
|
59
|
+
background: '#fdf6e3',
|
|
60
|
+
boxFill: '#eee8d5',
|
|
61
|
+
boxStroke: '#93a1a1',
|
|
62
|
+
containerFill: '#f6efdc',
|
|
63
|
+
containerStroke: '#e3dcc7',
|
|
64
|
+
text: '#586e75',
|
|
65
|
+
mutedText: '#93a1a1',
|
|
66
|
+
edge: '#268bd2',
|
|
67
|
+
iconInk: '#657b83',
|
|
68
|
+
iconShade: '#e0d9c3',
|
|
69
|
+
},
|
|
70
|
+
// Gruvbox, Pavel Pertsev. bg0 page, bg1 leaves, the warm yellow for lines.
|
|
71
|
+
'gruvbox-dark': {
|
|
72
|
+
background: '#282828',
|
|
73
|
+
boxFill: '#3c3836',
|
|
74
|
+
boxStroke: '#665c54',
|
|
75
|
+
containerFill: '#32302f',
|
|
76
|
+
containerStroke: '#3c3836',
|
|
77
|
+
text: '#ebdbb2',
|
|
78
|
+
mutedText: '#a89984',
|
|
79
|
+
edge: '#d79921',
|
|
80
|
+
iconInk: '#a89984',
|
|
81
|
+
iconShade: '#504945',
|
|
82
|
+
},
|
|
83
|
+
'gruvbox-light': {
|
|
84
|
+
background: '#fbf1c7',
|
|
85
|
+
boxFill: '#ebdbb2',
|
|
86
|
+
boxStroke: '#bdae93',
|
|
87
|
+
containerFill: '#f2e5bc',
|
|
88
|
+
containerStroke: '#e5d4a7',
|
|
89
|
+
text: '#3c3836',
|
|
90
|
+
mutedText: '#7c6f64',
|
|
91
|
+
edge: '#b57614',
|
|
92
|
+
iconInk: '#7c6f64',
|
|
93
|
+
iconShade: '#d5c4a1',
|
|
94
|
+
},
|
|
95
|
+
// Catppuccin. Mocha's base page and surface leaves, blue lines.
|
|
96
|
+
'catppuccin-mocha': {
|
|
97
|
+
background: '#1e1e2e',
|
|
98
|
+
boxFill: '#313244',
|
|
99
|
+
boxStroke: '#6c7086',
|
|
100
|
+
containerFill: '#25253a',
|
|
101
|
+
containerStroke: '#313244',
|
|
102
|
+
text: '#cdd6f4',
|
|
103
|
+
mutedText: '#9399b2',
|
|
104
|
+
edge: '#89b4fa',
|
|
105
|
+
iconInk: '#a6adc8',
|
|
106
|
+
iconShade: '#45475a',
|
|
107
|
+
},
|
|
108
|
+
// Latte's base page and crust leaves, lavender lines.
|
|
109
|
+
'catppuccin-latte': {
|
|
110
|
+
background: '#eff1f5',
|
|
111
|
+
boxFill: '#dce0e8',
|
|
112
|
+
boxStroke: '#9ca0b0',
|
|
113
|
+
containerFill: '#e6e9ef',
|
|
114
|
+
containerStroke: '#ccd0da',
|
|
115
|
+
text: '#4c4f69',
|
|
116
|
+
mutedText: '#7c7f93',
|
|
117
|
+
edge: '#7287fd',
|
|
118
|
+
iconInk: '#6c6f85',
|
|
119
|
+
iconShade: '#ccd0da',
|
|
120
|
+
},
|
|
121
|
+
// Nord, Sven Greb. Polar Night page and leaves, Frost lines.
|
|
122
|
+
nord: {
|
|
123
|
+
background: '#2e3440',
|
|
124
|
+
boxFill: '#3b4252',
|
|
125
|
+
boxStroke: '#4c566a',
|
|
126
|
+
containerFill: '#333a47',
|
|
127
|
+
containerStroke: '#3b4252',
|
|
128
|
+
text: '#d8dee9',
|
|
129
|
+
mutedText: '#7b88a1',
|
|
130
|
+
edge: '#81a1c1',
|
|
131
|
+
iconInk: '#aeb7c6',
|
|
132
|
+
iconShade: '#434c5e',
|
|
133
|
+
},
|
|
134
|
+
// Dracula, the free palette. Current-line leaves, comment borders, purple lines.
|
|
135
|
+
dracula: {
|
|
136
|
+
background: '#282a36',
|
|
137
|
+
boxFill: '#44475a',
|
|
138
|
+
boxStroke: '#6272a4',
|
|
139
|
+
containerFill: '#21222c',
|
|
140
|
+
containerStroke: '#343746',
|
|
141
|
+
text: '#f8f8f2',
|
|
142
|
+
mutedText: '#6272a4',
|
|
143
|
+
edge: '#bd93f9',
|
|
144
|
+
iconInk: '#b6b9cc',
|
|
145
|
+
iconShade: '#565a70',
|
|
146
|
+
},
|
|
147
|
+
// For low vision and projectors: no fills to lean on, every line at full
|
|
148
|
+
// strength, and a container told apart by a gray outline alone.
|
|
149
|
+
'high-contrast-dark': {
|
|
150
|
+
background: '#000000',
|
|
151
|
+
boxFill: '#000000',
|
|
152
|
+
boxStroke: '#ffffff',
|
|
153
|
+
containerFill: '#000000',
|
|
154
|
+
containerStroke: '#9a9a9a',
|
|
155
|
+
text: '#ffffff',
|
|
156
|
+
mutedText: '#c8c8c8',
|
|
157
|
+
edge: '#ffffff',
|
|
158
|
+
iconInk: '#ffffff',
|
|
159
|
+
iconShade: '#3a3a3a',
|
|
160
|
+
},
|
|
161
|
+
'high-contrast-light': {
|
|
162
|
+
background: '#ffffff',
|
|
163
|
+
boxFill: '#ffffff',
|
|
164
|
+
boxStroke: '#000000',
|
|
165
|
+
containerFill: '#ffffff',
|
|
166
|
+
containerStroke: '#6a6a6a',
|
|
167
|
+
text: '#000000',
|
|
168
|
+
mutedText: '#3d3d3d',
|
|
169
|
+
edge: '#000000',
|
|
170
|
+
iconInk: '#000000',
|
|
171
|
+
iconShade: '#d0d0d0',
|
|
172
|
+
},
|
|
173
|
+
// For paper: no fill anywhere an ink cartridge would notice, black lines,
|
|
174
|
+
// gray only where the dark theme is quiet.
|
|
175
|
+
print: {
|
|
176
|
+
background: '#ffffff',
|
|
177
|
+
boxFill: '#ffffff',
|
|
178
|
+
boxStroke: '#000000',
|
|
179
|
+
containerFill: '#ffffff',
|
|
180
|
+
containerStroke: '#8c8c8c',
|
|
181
|
+
text: '#000000',
|
|
182
|
+
mutedText: '#666666',
|
|
183
|
+
edge: '#333333',
|
|
184
|
+
iconInk: '#000000',
|
|
185
|
+
iconShade: '#ffffff',
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
/** The theme a file gets when it names none. */
|
|
189
|
+
export const DEFAULT_THEME = 'dark';
|
|
190
|
+
export const THEME_NAMES = Object.keys(THEMES);
|