@johnmorrisdotca/kyogijo-stadiums 0.0.0-stage → 0.4.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/CHANGELOG.md +129 -0
- package/LICENSE +21 -0
- package/NOTICE.md +293 -0
- package/README.md +600 -2
- package/dist/annotate.d.ts +47 -0
- package/dist/annotate.js +349 -0
- package/dist/atlas-entry.d.ts +4 -0
- package/dist/atlas-entry.js +3 -0
- package/dist/atlas.d.ts +131 -0
- package/dist/atlas.js +149 -0
- package/dist/background.d.ts +56 -0
- package/dist/background.js +127 -0
- package/dist/ballpark.d.ts +48 -0
- package/dist/ballpark.js +129 -0
- package/dist/canvas-entry.d.ts +2 -0
- package/dist/canvas-entry.js +2 -0
- package/dist/canvas.d.ts +85 -0
- package/dist/canvas.js +279 -0
- package/dist/charts-entry.d.ts +10 -0
- package/dist/charts-entry.js +8 -0
- package/dist/charts.baseball.d.ts +30 -0
- package/dist/charts.baseball.js +152 -0
- package/dist/charts.curling.d.ts +52 -0
- package/dist/charts.curling.js +196 -0
- package/dist/charts.d.ts +172 -0
- package/dist/charts.js +508 -0
- package/dist/charts.network.d.ts +66 -0
- package/dist/charts.network.js +64 -0
- package/dist/charts.soccer.d.ts +43 -0
- package/dist/charts.soccer.js +191 -0
- package/dist/charts.types.d.ts +396 -0
- package/dist/charts.types.js +1 -0
- package/dist/colour.d.ts +29 -0
- package/dist/colour.js +97 -0
- package/dist/common.d.ts +14 -0
- package/dist/common.js +32 -0
- package/dist/court.d.ts +89 -0
- package/dist/court.js +248 -0
- package/dist/courts.style.d.ts +20 -0
- package/dist/courts.style.js +54 -0
- package/dist/cricket.board.d.ts +18 -0
- package/dist/cricket.board.js +67 -0
- package/dist/curling.board.d.ts +24 -0
- package/dist/curling.board.js +147 -0
- package/dist/d3-entry.d.ts +3 -0
- package/dist/d3-entry.js +2 -0
- package/dist/d3.d.ts +45 -0
- package/dist/d3.js +56 -0
- package/dist/draw.d.ts +52 -0
- package/dist/draw.js +705 -0
- package/dist/element-define.d.ts +2 -0
- package/dist/element-define.js +4 -0
- package/dist/element-entry.d.ts +1 -0
- package/dist/element-entry.js +2 -0
- package/dist/element.d.ts +39 -0
- package/dist/element.js +321 -0
- package/dist/eras-entry.d.ts +2 -0
- package/dist/eras-entry.js +2 -0
- package/dist/eras.d.ts +204 -0
- package/dist/eras.js +396 -0
- package/dist/events-entry.d.ts +3 -0
- package/dist/events-entry.js +3 -0
- package/dist/events.d.ts +54 -0
- package/dist/events.data.d.ts +59 -0
- package/dist/events.data.js +297 -0
- package/dist/events.js +127 -0
- package/dist/features.d.ts +31 -0
- package/dist/features.js +284 -0
- package/dist/field.score.d.ts +5 -0
- package/dist/field.score.js +33 -0
- package/dist/fields/afl.d.ts +11 -0
- package/dist/fields/afl.js +103 -0
- package/dist/fields/cricket.d.ts +9 -0
- package/dist/fields/cricket.js +81 -0
- package/dist/fields/fieldhockey.d.ts +6 -0
- package/dist/fields/fieldhockey.js +81 -0
- package/dist/fields/gaelic.d.ts +10 -0
- package/dist/fields/gaelic.js +86 -0
- package/dist/fields/handball.d.ts +6 -0
- package/dist/fields/handball.js +85 -0
- package/dist/fields/lacrosse.d.ts +17 -0
- package/dist/fields/lacrosse.js +169 -0
- package/dist/fields/rugby.d.ts +10 -0
- package/dist/fields/rugby.js +130 -0
- package/dist/fields.d.ts +12 -0
- package/dist/fields.js +44 -0
- package/dist/fields.kit.d.ts +63 -0
- package/dist/fields.kit.js +108 -0
- package/dist/fields.style.d.ts +4 -0
- package/dist/fields.style.js +19 -0
- package/dist/fields.types.d.ts +155 -0
- package/dist/fields.types.js +1 -0
- package/dist/format.d.ts +10 -0
- package/dist/format.js +28 -0
- package/dist/formations.d.ts +18 -0
- package/dist/formations.js +211 -0
- package/dist/frames-entry.d.ts +8 -0
- package/dist/frames-entry.js +7 -0
- package/dist/frames.d.ts +199 -0
- package/dist/frames.js +271 -0
- package/dist/frames.providers.d.ts +16 -0
- package/dist/frames.providers.js +302 -0
- package/dist/geojson-entry.d.ts +2 -0
- package/dist/geojson-entry.js +2 -0
- package/dist/geojson.d.ts +79 -0
- package/dist/geojson.js +147 -0
- package/dist/geometry-entry.d.ts +7 -0
- package/dist/geometry-entry.js +5 -0
- package/dist/geometry.d.ts +295 -0
- package/dist/geometry.js +361 -0
- package/dist/grounds-entry.d.ts +4 -0
- package/dist/grounds-entry.js +3 -0
- package/dist/grounds.d.ts +134 -0
- package/dist/grounds.data.d.ts +2 -0
- package/dist/grounds.data.js +1849 -0
- package/dist/grounds.js +74 -0
- package/dist/heat.colour.d.ts +37 -0
- package/dist/heat.colour.js +54 -0
- package/dist/heat.d.ts +43 -0
- package/dist/heat.js +614 -0
- package/dist/heat.stats.d.ts +123 -0
- package/dist/heat.stats.js +413 -0
- package/dist/heat.types.d.ts +145 -0
- package/dist/heat.types.js +1 -0
- package/dist/heat.zones.d.ts +54 -0
- package/dist/heat.zones.js +424 -0
- package/dist/homeside.d.ts +16 -0
- package/dist/homeside.js +43 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +29 -0
- package/dist/layers-entry.d.ts +19 -0
- package/dist/layers-entry.js +11 -0
- package/dist/layers.d.ts +41 -0
- package/dist/layers.env.d.ts +72 -0
- package/dist/layers.env.js +79 -0
- package/dist/layers.js +533 -0
- package/dist/layers.marks.d.ts +45 -0
- package/dist/layers.marks.js +168 -0
- package/dist/layers.types.d.ts +226 -0
- package/dist/layers.types.js +1 -0
- package/dist/layout.d.ts +97 -0
- package/dist/layout.js +381 -0
- package/dist/layout.style.d.ts +6 -0
- package/dist/layout.style.js +17 -0
- package/dist/layouts-entry.d.ts +5 -0
- package/dist/layouts-entry.js +4 -0
- package/dist/leagues/aussie.d.ts +57 -0
- package/dist/leagues/aussie.js +36 -0
- package/dist/leagues/baseball.d.ts +58 -0
- package/dist/leagues/baseball.js +171 -0
- package/dist/leagues/basketball.d.ts +67 -0
- package/dist/leagues/basketball.js +128 -0
- package/dist/leagues/courts.d.ts +212 -0
- package/dist/leagues/courts.js +114 -0
- package/dist/leagues/cricket.d.ts +45 -0
- package/dist/leagues/cricket.js +34 -0
- package/dist/leagues/curling.d.ts +64 -0
- package/dist/leagues/curling.js +53 -0
- package/dist/leagues/fieldhockey.d.ts +48 -0
- package/dist/leagues/fieldhockey.js +29 -0
- package/dist/leagues/fields.d.ts +10 -0
- package/dist/leagues/fields.js +6 -0
- package/dist/leagues/football.d.ts +8 -0
- package/dist/leagues/football.js +82 -0
- package/dist/leagues/gaelic.d.ts +50 -0
- package/dist/leagues/gaelic.js +29 -0
- package/dist/leagues/handball.d.ts +45 -0
- package/dist/leagues/handball.js +32 -0
- package/dist/leagues/hockey.d.ts +63 -0
- package/dist/leagues/hockey.js +166 -0
- package/dist/leagues/lacrosse.d.ts +103 -0
- package/dist/leagues/lacrosse.js +175 -0
- package/dist/leagues/rugby.d.ts +59 -0
- package/dist/leagues/rugby.js +72 -0
- package/dist/leagues/sources.d.ts +18 -0
- package/dist/leagues/sources.js +95 -0
- package/dist/leagues/types.d.ts +43 -0
- package/dist/leagues/types.js +5 -0
- package/dist/leagues-entry.d.ts +23 -0
- package/dist/leagues-entry.js +15 -0
- package/dist/leagues.d.ts +172 -0
- package/dist/leagues.js +462 -0
- package/dist/look.d.ts +50 -0
- package/dist/look.js +186 -0
- package/dist/mark.d.ts +35 -0
- package/dist/mark.js +59 -0
- package/dist/mount-entry.d.ts +2 -0
- package/dist/mount-entry.js +2 -0
- package/dist/mount.d.ts +63 -0
- package/dist/mount.js +167 -0
- package/dist/mow.d.ts +36 -0
- package/dist/mow.js +111 -0
- package/dist/names.d.ts +77 -0
- package/dist/names.js +97 -0
- package/dist/outline.d.ts +25 -0
- package/dist/outline.js +112 -0
- package/dist/parks-entry.d.ts +4 -0
- package/dist/parks-entry.js +3 -0
- package/dist/parks.d.ts +200 -0
- package/dist/parks.data.d.ts +2 -0
- package/dist/parks.data.js +4489 -0
- package/dist/parks.js +92 -0
- package/dist/plot-entry.d.ts +2 -0
- package/dist/plot-entry.js +2 -0
- package/dist/plot.d.ts +74 -0
- package/dist/plot.js +68 -0
- package/dist/png-entry.d.ts +2 -0
- package/dist/png-entry.js +2 -0
- package/dist/png.d.ts +26 -0
- package/dist/png.js +46 -0
- package/dist/ranges.d.ts +33 -0
- package/dist/ranges.js +178 -0
- package/dist/ranges.names.d.ts +31 -0
- package/dist/ranges.names.js +65 -0
- package/dist/react-entry.d.ts +2 -0
- package/dist/react-entry.js +2 -0
- package/dist/react.d.ts +53 -0
- package/dist/react.js +58 -0
- package/dist/replay-entry.d.ts +5 -0
- package/dist/replay-entry.js +4 -0
- package/dist/replay.control.d.ts +47 -0
- package/dist/replay.control.js +184 -0
- package/dist/replay.d.ts +170 -0
- package/dist/replay.dom.d.ts +21 -0
- package/dist/replay.dom.js +388 -0
- package/dist/replay.js +579 -0
- package/dist/replay.types.d.ts +152 -0
- package/dist/replay.types.js +1 -0
- package/dist/restyle.d.ts +21 -0
- package/dist/restyle.js +70 -0
- package/dist/road.d.ts +26 -0
- package/dist/road.draw.d.ts +6 -0
- package/dist/road.draw.js +269 -0
- package/dist/road.js +59 -0
- package/dist/road.model.d.ts +63 -0
- package/dist/road.model.js +215 -0
- package/dist/road.style.d.ts +7 -0
- package/dist/road.style.js +26 -0
- package/dist/road.types.d.ts +122 -0
- package/dist/road.types.js +1 -0
- package/dist/scene-entry.d.ts +7 -0
- package/dist/scene-entry.js +5 -0
- package/dist/scene.css.d.ts +44 -0
- package/dist/scene.css.js +296 -0
- package/dist/scene.d.ts +27 -0
- package/dist/scene.fit.d.ts +12 -0
- package/dist/scene.fit.js +18 -0
- package/dist/scene.flat.d.ts +38 -0
- package/dist/scene.flat.js +167 -0
- package/dist/scene.js +420 -0
- package/dist/scene.path.d.ts +40 -0
- package/dist/scene.path.js +357 -0
- package/dist/scene.types.d.ts +172 -0
- package/dist/scene.types.js +1 -0
- package/dist/scene.xml.d.ts +31 -0
- package/dist/scene.xml.js +81 -0
- package/dist/score.d.ts +70 -0
- package/dist/score.js +212 -0
- package/dist/sets.board.d.ts +22 -0
- package/dist/sets.board.js +89 -0
- package/dist/sheet.d.ts +18 -0
- package/dist/sheet.js +38 -0
- package/dist/special.d.ts +44 -0
- package/dist/special.js +204 -0
- package/dist/special.style.d.ts +8 -0
- package/dist/special.style.js +36 -0
- package/dist/strings.d.ts +581 -0
- package/dist/strings.js +411 -0
- package/dist/style.d.ts +35 -0
- package/dist/style.js +177 -0
- package/dist/surfaces/baseball.d.ts +33 -0
- package/dist/surfaces/baseball.js +208 -0
- package/dist/surfaces/baseball.walls.d.ts +16 -0
- package/dist/surfaces/baseball.walls.js +150 -0
- package/dist/surfaces/basketball.d.ts +8 -0
- package/dist/surfaces/basketball.js +199 -0
- package/dist/surfaces/curling.d.ts +23 -0
- package/dist/surfaces/curling.js +128 -0
- package/dist/surfaces/field.d.ts +12 -0
- package/dist/surfaces/field.js +163 -0
- package/dist/surfaces/football.d.ts +12 -0
- package/dist/surfaces/football.js +208 -0
- package/dist/surfaces/hockey.d.ts +10 -0
- package/dist/surfaces/hockey.js +166 -0
- package/dist/surfaces/net.d.ts +7 -0
- package/dist/surfaces/net.js +151 -0
- package/dist/surfaces/soccer-providers.d.ts +124 -0
- package/dist/surfaces/soccer-providers.js +334 -0
- package/dist/surfaces/soccer-spec.d.ts +138 -0
- package/dist/surfaces/soccer-spec.js +175 -0
- package/dist/surfaces/soccer.d.ts +25 -0
- package/dist/surfaces/soccer.js +270 -0
- package/dist/surfaces/surface.d.ts +180 -0
- package/dist/surfaces/surface.js +49 -0
- package/dist/team.common.d.ts +75 -0
- package/dist/team.common.js +97 -0
- package/dist/team.formation.d.ts +24 -0
- package/dist/team.formation.js +121 -0
- package/dist/team.geometry.d.ts +66 -0
- package/dist/team.geometry.js +304 -0
- package/dist/team.layers.d.ts +16 -0
- package/dist/team.layers.js +32 -0
- package/dist/team.motion.d.ts +9 -0
- package/dist/team.motion.js +325 -0
- package/dist/team.network.d.ts +43 -0
- package/dist/team.network.js +215 -0
- package/dist/team.shape.d.ts +12 -0
- package/dist/team.shape.js +217 -0
- package/dist/team.types.d.ts +189 -0
- package/dist/team.types.js +1 -0
- package/dist/text.d.ts +31 -0
- package/dist/text.js +88 -0
- package/dist/themes.d.ts +14 -0
- package/dist/themes.js +24 -0
- package/dist/types.d.ts +565 -0
- package/dist/types.js +1 -0
- package/dist/units.d.ts +45 -0
- package/dist/units.js +90 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +234 -4
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// The part of CSS the drawing's stylesheet uses, enough to work out what a browser would paint: rules with class, attribute, tag and `:where()` / `:not()`
|
|
2
|
+
// selectors, descendant and child combinators, light and dark by `prefers-color-scheme` and `data-theme`, custom properties with `var()` and a fallback,
|
|
3
|
+
// `calc()` of a product, and the inheritance of the properties that inherit. Not a general CSS engine.
|
|
4
|
+
const IDENT = /[\w-]+/y;
|
|
5
|
+
function closing(text, open) {
|
|
6
|
+
let depth = 0;
|
|
7
|
+
for (let i = open; i < text.length; i += 1) {
|
|
8
|
+
if (text[i] === "(")
|
|
9
|
+
depth += 1;
|
|
10
|
+
else if (text[i] === ")" && --depth === 0)
|
|
11
|
+
return i;
|
|
12
|
+
}
|
|
13
|
+
return text.length;
|
|
14
|
+
}
|
|
15
|
+
/** Split at commas that are not inside brackets or parentheses. */
|
|
16
|
+
function splitTop(text) {
|
|
17
|
+
const parts = [];
|
|
18
|
+
let depth = 0;
|
|
19
|
+
let start = 0;
|
|
20
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
21
|
+
const c = text[i];
|
|
22
|
+
if (c === "(" || c === "[")
|
|
23
|
+
depth += 1;
|
|
24
|
+
else if (c === ")" || c === "]")
|
|
25
|
+
depth -= 1;
|
|
26
|
+
else if (c === "," && depth === 0) {
|
|
27
|
+
parts.push(text.slice(start, i));
|
|
28
|
+
start = i + 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
parts.push(text.slice(start));
|
|
32
|
+
return parts.map((part) => part.trim()).filter((part) => part !== "");
|
|
33
|
+
}
|
|
34
|
+
function parseComplex(text) {
|
|
35
|
+
const out = [];
|
|
36
|
+
let i = 0;
|
|
37
|
+
let combinator = "";
|
|
38
|
+
while (i < text.length) {
|
|
39
|
+
const c = text[i];
|
|
40
|
+
if (c === " " || c === "\n" || c === "\t") {
|
|
41
|
+
i += 1;
|
|
42
|
+
if (out.length > 0 && combinator === "")
|
|
43
|
+
combinator = " ";
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (c === ">") {
|
|
47
|
+
combinator = ">";
|
|
48
|
+
i += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const simple = { classes: [], attrs: [], where: [], not: [] };
|
|
52
|
+
let any = false;
|
|
53
|
+
while (i < text.length && !/[\s>]/.test(text[i])) {
|
|
54
|
+
const d = text[i];
|
|
55
|
+
if (d === "*")
|
|
56
|
+
i += 1;
|
|
57
|
+
else if (d === ".") {
|
|
58
|
+
IDENT.lastIndex = i + 1;
|
|
59
|
+
const m = IDENT.exec(text);
|
|
60
|
+
simple.classes.push(m[0]);
|
|
61
|
+
i = IDENT.lastIndex;
|
|
62
|
+
}
|
|
63
|
+
else if (d === "[") {
|
|
64
|
+
const end = text.indexOf("]", i);
|
|
65
|
+
const body = text.slice(i + 1, end);
|
|
66
|
+
const eq = body.indexOf("=");
|
|
67
|
+
simple.attrs.push(eq < 0 ? { name: body.trim() } : { name: body.slice(0, eq).trim(), value: body.slice(eq + 1).trim().replace(/^["']|["']$/g, "") });
|
|
68
|
+
i = end + 1;
|
|
69
|
+
}
|
|
70
|
+
else if (d === ":") {
|
|
71
|
+
IDENT.lastIndex = i + 1;
|
|
72
|
+
const m = IDENT.exec(text);
|
|
73
|
+
i = IDENT.lastIndex;
|
|
74
|
+
if (text[i] !== "(")
|
|
75
|
+
throw new Error(`Unsupported pseudo-class :${m[0]}`);
|
|
76
|
+
const end = closing(text, i);
|
|
77
|
+
const inner = splitTop(text.slice(i + 1, end)).map(parseComplex);
|
|
78
|
+
if (m[0] === "where")
|
|
79
|
+
simple.where.push(inner);
|
|
80
|
+
else if (m[0] === "not")
|
|
81
|
+
simple.not.push(inner);
|
|
82
|
+
else
|
|
83
|
+
throw new Error(`Unsupported pseudo-class :${m[0]}()`);
|
|
84
|
+
i = end + 1;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
IDENT.lastIndex = i;
|
|
88
|
+
const m = IDENT.exec(text);
|
|
89
|
+
if (m === null)
|
|
90
|
+
throw new Error(`Unsupported selector: ${text}`);
|
|
91
|
+
simple.tag = m[0];
|
|
92
|
+
i = IDENT.lastIndex;
|
|
93
|
+
}
|
|
94
|
+
any = true;
|
|
95
|
+
}
|
|
96
|
+
if (any) {
|
|
97
|
+
out.push({ combinator: out.length === 0 ? "" : combinator === "" ? " " : combinator, simple });
|
|
98
|
+
combinator = "";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
function simpleMatches(s, node) {
|
|
104
|
+
if (s.tag !== undefined && s.tag !== node.name)
|
|
105
|
+
return false;
|
|
106
|
+
if (s.classes.length > 0) {
|
|
107
|
+
const have = (node.attrs.class ?? "").split(/\s+/);
|
|
108
|
+
if (!s.classes.every((c) => have.includes(c)))
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
for (const a of s.attrs) {
|
|
112
|
+
const v = node.attrs[a.name];
|
|
113
|
+
if (v === undefined || (a.value !== undefined && v !== a.value))
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
for (const list of s.where)
|
|
117
|
+
if (!list.some((c) => complexMatches(c, c.length - 1, node)))
|
|
118
|
+
return false;
|
|
119
|
+
for (const list of s.not)
|
|
120
|
+
if (list.some((c) => complexMatches(c, c.length - 1, node)))
|
|
121
|
+
return false;
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
function complexMatches(selector, at, node) {
|
|
125
|
+
if (!simpleMatches(selector[at].simple, node))
|
|
126
|
+
return false;
|
|
127
|
+
if (at === 0)
|
|
128
|
+
return true;
|
|
129
|
+
const { combinator } = selector[at];
|
|
130
|
+
if (combinator === ">")
|
|
131
|
+
return node.parent !== undefined && complexMatches(selector, at - 1, node.parent);
|
|
132
|
+
for (let up = node.parent; up !== undefined; up = up.parent)
|
|
133
|
+
if (complexMatches(selector, at - 1, up))
|
|
134
|
+
return true;
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
/** The weight of a selector as a single number: ids are not used here, so classes and attributes count a thousand each and tags one. `:where()` has none. */
|
|
138
|
+
function weight(selector) {
|
|
139
|
+
let w = 0;
|
|
140
|
+
const add = (s) => {
|
|
141
|
+
w += s.classes.length * 1000 + s.attrs.length * 1000 + (s.tag === undefined ? 0 : 1);
|
|
142
|
+
for (const list of s.not)
|
|
143
|
+
w += Math.max(0, ...list.map(weight));
|
|
144
|
+
};
|
|
145
|
+
for (const c of selector)
|
|
146
|
+
add(c.simple);
|
|
147
|
+
return w;
|
|
148
|
+
}
|
|
149
|
+
function declarationsOf(body) {
|
|
150
|
+
const out = [];
|
|
151
|
+
for (const part of body.split(";")) {
|
|
152
|
+
const colon = part.indexOf(":");
|
|
153
|
+
if (colon < 0)
|
|
154
|
+
continue;
|
|
155
|
+
const name = part.slice(0, colon).trim();
|
|
156
|
+
const value = part.slice(colon + 1).trim();
|
|
157
|
+
if (name !== "" && value !== "")
|
|
158
|
+
out.push([name, value]);
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
/** Read a stylesheet. `theme` decides which `@media (prefers-color-scheme:…)` blocks count. */
|
|
163
|
+
export function parseStylesheet(css, theme, into = { rules: [] }) {
|
|
164
|
+
const text = css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
165
|
+
const read = (from, to, active) => {
|
|
166
|
+
let i = from;
|
|
167
|
+
while (i < to) {
|
|
168
|
+
const open = text.indexOf("{", i);
|
|
169
|
+
if (open < 0 || open >= to)
|
|
170
|
+
break;
|
|
171
|
+
const prelude = text.slice(i, open).trim();
|
|
172
|
+
let depth = 1;
|
|
173
|
+
let j = open + 1;
|
|
174
|
+
while (j < to && depth > 0) {
|
|
175
|
+
if (text[j] === "{")
|
|
176
|
+
depth += 1;
|
|
177
|
+
else if (text[j] === "}")
|
|
178
|
+
depth -= 1;
|
|
179
|
+
j += 1;
|
|
180
|
+
}
|
|
181
|
+
const end = j - 1;
|
|
182
|
+
if (prelude.startsWith("@")) {
|
|
183
|
+
const scheme = /prefers-color-scheme\s*:\s*(light|dark)/.exec(prelude);
|
|
184
|
+
if (prelude.startsWith("@media") && scheme !== null)
|
|
185
|
+
read(open + 1, end, active && scheme[1] === theme);
|
|
186
|
+
// Any other at-rule (another media query, keyframes) is not a rule of this drawing's paint.
|
|
187
|
+
}
|
|
188
|
+
else if (active) {
|
|
189
|
+
const declarations = declarationsOf(text.slice(open + 1, end));
|
|
190
|
+
for (const s of splitTop(prelude)) {
|
|
191
|
+
try {
|
|
192
|
+
const selector = parseComplex(s);
|
|
193
|
+
if (selector.length > 0)
|
|
194
|
+
into.rules.push({ selector, specificity: weight(selector), order: into.rules.length, declarations });
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// A selector this reader does not know matches nothing, as in a browser that does not know it.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
i = j;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
read(0, text.length, true);
|
|
205
|
+
return into;
|
|
206
|
+
}
|
|
207
|
+
/** The declarations that apply to an element, lowest priority first (by weight, then by order). */
|
|
208
|
+
export function matched(sheet, node) {
|
|
209
|
+
const hits = sheet.rules.filter((rule) => complexMatches(rule.selector, rule.selector.length - 1, node));
|
|
210
|
+
hits.sort((a, b) => a.specificity - b.specificity || a.order - b.order);
|
|
211
|
+
return hits.flatMap((rule) => rule.declarations);
|
|
212
|
+
}
|
|
213
|
+
/** The declarations of a `style` attribute. */
|
|
214
|
+
export const inlineStyle = (text) => (text === undefined ? [] : declarationsOf(text));
|
|
215
|
+
/** Replace every `var(--name, fallback)` in a value. A name with no value and no fallback makes the whole value `undefined` (the property is not set). */
|
|
216
|
+
export function substitute(value, vars, depth = 0) {
|
|
217
|
+
if (depth > 12)
|
|
218
|
+
return undefined;
|
|
219
|
+
let out = "";
|
|
220
|
+
let i = 0;
|
|
221
|
+
while (i < value.length) {
|
|
222
|
+
const at = value.indexOf("var(", i);
|
|
223
|
+
if (at < 0) {
|
|
224
|
+
out += value.slice(i);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
out += value.slice(i, at);
|
|
228
|
+
const end = closing(value, at + 3);
|
|
229
|
+
const inside = value.slice(at + 4, end);
|
|
230
|
+
const comma = inside.indexOf(",");
|
|
231
|
+
const name = (comma < 0 ? inside : inside.slice(0, comma)).trim();
|
|
232
|
+
const fallback = comma < 0 ? undefined : inside.slice(comma + 1).trim();
|
|
233
|
+
const own = vars[name];
|
|
234
|
+
const resolved = own !== undefined ? substitute(own, vars, depth + 1) : fallback !== undefined ? substitute(fallback, vars, depth + 1) : undefined;
|
|
235
|
+
if (resolved === undefined)
|
|
236
|
+
return undefined;
|
|
237
|
+
out += resolved;
|
|
238
|
+
i = end + 1;
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
/** A value with its lengths worked out: `px` dropped, `em` multiplied by the font size, and every `calc()` replaced by its number. */
|
|
243
|
+
export function evaluate(value, fontSize) {
|
|
244
|
+
let text = value.trim().replace(/(-?\d*\.?\d+)em\b/g, (_, n) => String(Number(n) * fontSize)).replace(/(-?\d*\.?\d+)px\b/g, "$1");
|
|
245
|
+
let guard = 0;
|
|
246
|
+
while (/calc\(/.test(text) && guard++ < 8)
|
|
247
|
+
text = text.replace(/calc\(([^()]*)\)/g, (_, expression) => String(calc(expression)));
|
|
248
|
+
return text;
|
|
249
|
+
}
|
|
250
|
+
/** A length or a number as a number of user units: `3`, `3px`, `.14em` (of the font size), and `calc()` of a product, sum or difference of those. */
|
|
251
|
+
export function lengthOf(value, fontSize) {
|
|
252
|
+
if (value === undefined || value.trim() === "")
|
|
253
|
+
return undefined;
|
|
254
|
+
const n = Number(evaluate(value, fontSize));
|
|
255
|
+
return Number.isFinite(n) ? n : undefined;
|
|
256
|
+
}
|
|
257
|
+
/** `a*b`, `a/b`, `a+b`, `a-b` of numbers, left to right with products first. */
|
|
258
|
+
function calc(expression) {
|
|
259
|
+
const tokens = expression.replace(/\s+/g, "").match(/[-+]?\d*\.?\d+(?:e[-+]?\d+)?|[*/+-]/gi) ?? [];
|
|
260
|
+
const terms = [];
|
|
261
|
+
const signs = [];
|
|
262
|
+
let current = NaN;
|
|
263
|
+
let op = "";
|
|
264
|
+
let sign = 1;
|
|
265
|
+
const push = () => {
|
|
266
|
+
terms.push(current);
|
|
267
|
+
signs.push(sign);
|
|
268
|
+
};
|
|
269
|
+
for (const t of tokens) {
|
|
270
|
+
if (t === "*" || t === "/")
|
|
271
|
+
op = t;
|
|
272
|
+
else if ((t === "+" || t === "-") && !Number.isNaN(current)) {
|
|
273
|
+
push();
|
|
274
|
+
sign = t === "-" ? -1 : 1;
|
|
275
|
+
current = NaN;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
const n = Number(t);
|
|
279
|
+
if (Number.isNaN(current))
|
|
280
|
+
current = n;
|
|
281
|
+
else if (op === "*")
|
|
282
|
+
current *= n;
|
|
283
|
+
else if (op === "/")
|
|
284
|
+
current /= n;
|
|
285
|
+
else {
|
|
286
|
+
push();
|
|
287
|
+
sign = 1;
|
|
288
|
+
current = n;
|
|
289
|
+
}
|
|
290
|
+
op = "";
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (!Number.isNaN(current))
|
|
294
|
+
push();
|
|
295
|
+
return terms.reduce((sum, term, i) => sum + signs[i] * term, 0);
|
|
296
|
+
}
|
package/dist/scene.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Scene, SceneOptions } from "./scene.types.ts";
|
|
2
|
+
import type { ScoreOptions, SurfaceOptions } from "./types.ts";
|
|
3
|
+
/**
|
|
4
|
+
* A drawing the package wrote, as a scene. `theme` works the colours out for light or dark: without it, the drawing's own `data-theme`, else light.
|
|
5
|
+
* A drawing made with `style: "none"` is read with the package's own stylesheet (`KYOGIJO_STYLE`); `css` adds a page's rules after the drawing's.
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { drawKyogijo } from "@johnmorrisdotca/kyogijo-stadiums";
|
|
9
|
+
* import { sceneFromSvg } from "@johnmorrisdotca/kyogijo-stadiums/scene";
|
|
10
|
+
* const scene = sceneFromSvg(drawKyogijo({ sport: "nhl", id: "rink" }), { theme: "dark" });
|
|
11
|
+
* console.log(scene.theme, scene.items[0].kind, scene.items[0].style.fill);
|
|
12
|
+
* // dark rect #1a2025
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function sceneFromSvg(svg: string, options?: SceneOptions): Scene;
|
|
16
|
+
/**
|
|
17
|
+
* A drawing as plain data: the options of `drawKyogijo` (or the SVG of a chart or a replay frame, or a scene already made) as the shapes it is painted
|
|
18
|
+
* with, in order, each with its colours worked out for light or dark.
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { sceneOf } from "@johnmorrisdotca/kyogijo-stadiums/scene";
|
|
22
|
+
* const scene = sceneOf({ sport: "nhl", id: "rink" });
|
|
23
|
+
* console.log(scene.viewBox, scene.items.length > 30, scene.items[0].kind);
|
|
24
|
+
* // { x: -8, y: -8, w: 216, h: 101 } true rect
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function sceneOf(source: SurfaceOptions | ScoreOptions | string | Scene, options?: SceneOptions): Scene;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Matrix, Scene } from "./scene.types.ts";
|
|
2
|
+
/** A scale as a function of a number, which is all of d3's and Plot's `scaleLinear` that is used here. */
|
|
3
|
+
export type ScaleFunction = (value: number) => number;
|
|
4
|
+
/** The least and the greatest of x and of y in the drawing's own numbers (feet), the domains to give two scales. */
|
|
5
|
+
export interface SurfaceDomain {
|
|
6
|
+
x: [number, number];
|
|
7
|
+
y: [number, number];
|
|
8
|
+
}
|
|
9
|
+
/** The extent of a scene: its `viewBox`, the margin round the surface included. */
|
|
10
|
+
export declare function domainOf(scene: Scene): SurfaceDomain;
|
|
11
|
+
/** The matrix that takes the drawing's numbers to pixels through two scales. Only a scale that is a straight line (linear) will do; any other is an error. */
|
|
12
|
+
export declare function affineOf(x: ScaleFunction, y: ScaleFunction): Matrix;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The extent of a scene: its `viewBox`, the margin round the surface included. */
|
|
2
|
+
export function domainOf(scene) {
|
|
3
|
+
const { x, y, w, h } = scene.viewBox;
|
|
4
|
+
return { x: [x, x + w], y: [y, y + h] };
|
|
5
|
+
}
|
|
6
|
+
/** The matrix that takes the drawing's numbers to pixels through two scales. Only a scale that is a straight line (linear) will do; any other is an error. */
|
|
7
|
+
export function affineOf(x, y) {
|
|
8
|
+
const line = (scale, name) => {
|
|
9
|
+
const at0 = scale(0);
|
|
10
|
+
const step = scale(1) - at0;
|
|
11
|
+
if (![at0, step].every(Number.isFinite) || Math.abs(scale(100) - (at0 + 100 * step)) > 1e-6 * Math.max(1, Math.abs(at0 + 100 * step)))
|
|
12
|
+
throw new TypeError(`The ${name} scale must be linear: the drawing is moved and stretched, not bent`);
|
|
13
|
+
return [step, at0];
|
|
14
|
+
};
|
|
15
|
+
const [sx, tx] = line(x, "x");
|
|
16
|
+
const [sy, ty] = line(y, "y");
|
|
17
|
+
return [sx, 0, 0, sy, tx, ty];
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Matrix, Scene, SceneOptions, SceneSource } from "./scene.types.ts";
|
|
2
|
+
/** An element to be made: its tag, attributes, text and children. */
|
|
3
|
+
export interface Spec {
|
|
4
|
+
tag: string;
|
|
5
|
+
attrs: Record<string, string>;
|
|
6
|
+
text?: string;
|
|
7
|
+
children?: Spec[];
|
|
8
|
+
}
|
|
9
|
+
/** A transform as an attribute, to six decimals: a scale of a few pixels to the foot, over a rink, must not drift. */
|
|
10
|
+
export declare const matrixAttr: (m: Matrix) => string;
|
|
11
|
+
/** The ids of a scene's clips as a renderer writes them. */
|
|
12
|
+
export declare const clipId: (scene: Scene, index: number) => string;
|
|
13
|
+
/** What goes in `<defs>` for a scene: its clips, gradients and patterns. */
|
|
14
|
+
export declare function defsOf(scene: Scene): Spec[];
|
|
15
|
+
/** The shapes of a scene as elements in paint order, those inside a clip in a group that clips them. */
|
|
16
|
+
export declare function bodyOf(scene: Scene): Spec[];
|
|
17
|
+
/** An element as text. */
|
|
18
|
+
export declare function specToSvg(spec: Spec): string;
|
|
19
|
+
/** What a flat SVG is written with. */
|
|
20
|
+
export interface FlatOptions extends SceneOptions {
|
|
21
|
+
/** The width of the picture in pixels. Without a width or a height the SVG has neither and takes the size of its place. */
|
|
22
|
+
width?: number;
|
|
23
|
+
height?: number;
|
|
24
|
+
/** A colour to fill the picture with first (a PNG has no page behind it). */
|
|
25
|
+
background?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A drawing as an SVG that needs no stylesheet: every colour, width and font worked out for the theme, so that a tool that does not read CSS variables
|
|
29
|
+
* (an image converter, an email, a slide) draws what a browser does. Light by default, or the drawing's own `data-theme`.
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { sceneToSvg } from "@johnmorrisdotca/kyogijo-stadiums/scene";
|
|
33
|
+
* const svg = sceneToSvg({ sport: "nhl", id: "rink" }, { theme: "dark", width: 800 });
|
|
34
|
+
* console.log(svg.includes("var("), svg.includes("<style"), svg.startsWith("<svg"));
|
|
35
|
+
* // false false true
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function sceneToSvg(source: SceneSource, options?: FlatOptions): string;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// A scene as elements that need no stylesheet: every colour, width and font is an attribute, every transform is on the shape, and a clip is a group. The d3,
|
|
2
|
+
// Observable Plot and PNG renderers all build from this list, and `sceneToSvg` writes it as text, so a drawing is the same wherever it is put.
|
|
3
|
+
import { isIdentity, multiply } from "./scene.path.js";
|
|
4
|
+
import { sceneOf } from "./scene.js";
|
|
5
|
+
import { encode } from "./scene.xml.js";
|
|
6
|
+
const f = (value) => {
|
|
7
|
+
const rounded = Math.round(value * 10000) / 10000;
|
|
8
|
+
return Object.is(rounded, -0) ? "0" : String(rounded);
|
|
9
|
+
};
|
|
10
|
+
/** A transform as an attribute, to six decimals: a scale of a few pixels to the foot, over a rink, must not drift. */
|
|
11
|
+
export const matrixAttr = (m) => `matrix(${m.map((v) => (Object.is(Math.round(v * 1e6) / 1e6, -0) ? 0 : Math.round(v * 1e6) / 1e6)).join(" ")})`;
|
|
12
|
+
const paintOf = (paint) => (typeof paint === "string" ? paint : "gradient" in paint ? `url(#${paint.gradient})` : `url(#${paint.pattern})`);
|
|
13
|
+
function paintAttrs(style, text) {
|
|
14
|
+
const out = { fill: paintOf(style.fill) };
|
|
15
|
+
if (style.fillOpacity !== 1)
|
|
16
|
+
out["fill-opacity"] = f(style.fillOpacity);
|
|
17
|
+
if (style.stroke !== "none") {
|
|
18
|
+
out.stroke = paintOf(style.stroke);
|
|
19
|
+
out["stroke-width"] = f(style.strokeWidth);
|
|
20
|
+
if (style.strokeOpacity !== 1)
|
|
21
|
+
out["stroke-opacity"] = f(style.strokeOpacity);
|
|
22
|
+
if (style.lineJoin !== "miter")
|
|
23
|
+
out["stroke-linejoin"] = style.lineJoin;
|
|
24
|
+
if (style.lineCap !== "butt")
|
|
25
|
+
out["stroke-linecap"] = style.lineCap;
|
|
26
|
+
if (style.dash !== undefined)
|
|
27
|
+
out["stroke-dasharray"] = style.dash.map(f).join(" ");
|
|
28
|
+
}
|
|
29
|
+
if (!text && style.fillRule === "evenodd")
|
|
30
|
+
out["fill-rule"] = "evenodd";
|
|
31
|
+
if (style.opacity !== 1)
|
|
32
|
+
out.opacity = f(style.opacity);
|
|
33
|
+
if (style.paintOrder !== undefined)
|
|
34
|
+
out["paint-order"] = style.paintOrder;
|
|
35
|
+
if (style.filter !== undefined)
|
|
36
|
+
out.style = `filter:${style.filter}`;
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
function geometry(item) {
|
|
40
|
+
switch (item.kind) {
|
|
41
|
+
case "rect":
|
|
42
|
+
return { tag: "rect", attrs: { x: f(item.x), y: f(item.y), width: f(item.width), height: f(item.height), ...(item.rx > 0 ? { rx: f(item.rx) } : {}) } };
|
|
43
|
+
case "circle":
|
|
44
|
+
return { tag: "circle", attrs: { cx: f(item.cx), cy: f(item.cy), r: f(item.r) } };
|
|
45
|
+
case "ellipse":
|
|
46
|
+
return { tag: "ellipse", attrs: { cx: f(item.cx), cy: f(item.cy), rx: f(item.rx), ry: f(item.ry) } };
|
|
47
|
+
case "line":
|
|
48
|
+
return { tag: "line", attrs: { x1: f(item.x1), y1: f(item.y1), x2: f(item.x2), y2: f(item.y2) } };
|
|
49
|
+
case "polygon":
|
|
50
|
+
case "polyline":
|
|
51
|
+
return { tag: item.kind, attrs: { points: item.points.map((p) => `${f(p[0])},${f(p[1])}`).join(" ") } };
|
|
52
|
+
default:
|
|
53
|
+
return { tag: "path", attrs: { d: item.d } };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function itemSpec(item, matrix, styled) {
|
|
57
|
+
const attrs = {};
|
|
58
|
+
if (item.class !== "")
|
|
59
|
+
attrs.class = item.class;
|
|
60
|
+
let spec;
|
|
61
|
+
if (item.kind === "text") {
|
|
62
|
+
Object.assign(attrs, { x: f(item.x), y: f(item.y), "font-size": f(item.fontSize), "font-weight": String(item.fontWeight), "font-family": item.fontFamily });
|
|
63
|
+
if (item.anchor !== "start")
|
|
64
|
+
attrs["text-anchor"] = item.anchor;
|
|
65
|
+
if (item.letterSpacing !== 0)
|
|
66
|
+
attrs["letter-spacing"] = f(item.letterSpacing);
|
|
67
|
+
spec = { tag: "text", attrs, text: item.text };
|
|
68
|
+
}
|
|
69
|
+
else if (item.kind === "image") {
|
|
70
|
+
Object.assign(attrs, { href: item.href, x: f(item.x), y: f(item.y), width: f(item.width), height: f(item.height) });
|
|
71
|
+
if (item.preserveAspectRatio !== undefined)
|
|
72
|
+
attrs.preserveAspectRatio = item.preserveAspectRatio;
|
|
73
|
+
spec = { tag: "image", attrs };
|
|
74
|
+
if (item.style.opacity !== 1)
|
|
75
|
+
attrs.opacity = f(item.style.opacity);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const g = geometry(item);
|
|
79
|
+
Object.assign(attrs, g.attrs);
|
|
80
|
+
spec = { tag: g.tag, attrs };
|
|
81
|
+
}
|
|
82
|
+
if (styled && item.kind !== "image")
|
|
83
|
+
Object.assign(attrs, paintAttrs(item.style, item.kind === "text"));
|
|
84
|
+
if (!isIdentity(matrix))
|
|
85
|
+
attrs.transform = matrixAttr(matrix);
|
|
86
|
+
if (item.label !== undefined)
|
|
87
|
+
spec.children = [{ tag: "title", attrs: {}, text: item.label }];
|
|
88
|
+
return spec;
|
|
89
|
+
}
|
|
90
|
+
/** The ids of a scene's clips as a renderer writes them. */
|
|
91
|
+
export const clipId = (scene, index) => `${scene.id}-clip${index}`;
|
|
92
|
+
/** What goes in `<defs>` for a scene: its clips, gradients and patterns. */
|
|
93
|
+
export function defsOf(scene) {
|
|
94
|
+
const defs = [];
|
|
95
|
+
scene.clips.forEach((clip, i) => {
|
|
96
|
+
defs.push({ tag: "clipPath", attrs: { id: clipId(scene, i) }, children: clip.shapes.map((shape) => itemSpec(shape, multiply(clip.matrix, shape.matrix), false)) });
|
|
97
|
+
});
|
|
98
|
+
for (const g of scene.gradients) {
|
|
99
|
+
defs.push({
|
|
100
|
+
tag: "linearGradient",
|
|
101
|
+
attrs: { id: g.id, gradientUnits: "userSpaceOnUse", x1: f(g.x1), y1: f(g.y1), x2: f(g.x2), y2: f(g.y2) },
|
|
102
|
+
children: g.stops.map((stop) => ({ tag: "stop", attrs: { offset: f(stop.offset), "stop-color": stop.color, ...(stop.opacity === 1 ? {} : { "stop-opacity": f(stop.opacity) }) } })),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
for (const p of scene.patterns) {
|
|
106
|
+
defs.push({ tag: "pattern", attrs: { id: p.id, width: f(p.width), height: f(p.height), patternUnits: "userSpaceOnUse" }, children: p.shapes.map((shape) => itemSpec(shape, shape.matrix, true)) });
|
|
107
|
+
}
|
|
108
|
+
return defs;
|
|
109
|
+
}
|
|
110
|
+
/** The shapes of a scene as elements in paint order, those inside a clip in a group that clips them. */
|
|
111
|
+
export function bodyOf(scene) {
|
|
112
|
+
const nest = (items, depth) => {
|
|
113
|
+
const out = [];
|
|
114
|
+
let i = 0;
|
|
115
|
+
while (i < items.length) {
|
|
116
|
+
const clip = items[i].clips[depth];
|
|
117
|
+
if (clip === undefined) {
|
|
118
|
+
out.push(itemSpec(items[i], items[i].matrix, true));
|
|
119
|
+
i += 1;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
let j = i;
|
|
123
|
+
while (j < items.length && items[j].clips[depth] === clip)
|
|
124
|
+
j += 1;
|
|
125
|
+
out.push({ tag: "g", attrs: { "clip-path": `url(#${clipId(scene, clip)})` }, children: nest(items.slice(i, j), depth + 1) });
|
|
126
|
+
i = j;
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
};
|
|
130
|
+
return nest(scene.items, 0);
|
|
131
|
+
}
|
|
132
|
+
/** An element as text. */
|
|
133
|
+
export function specToSvg(spec) {
|
|
134
|
+
const attrs = Object.entries(spec.attrs).map(([key, value]) => ` ${key}="${encode(value)}"`).join("");
|
|
135
|
+
const inner = (spec.text === undefined ? "" : encode(spec.text).replace(/"/g, '"')) + (spec.children ?? []).map(specToSvg).join("");
|
|
136
|
+
return inner === "" && spec.text === undefined ? `<${spec.tag}${attrs}/>` : `<${spec.tag}${attrs}>${inner}</${spec.tag}>`;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* A drawing as an SVG that needs no stylesheet: every colour, width and font worked out for the theme, so that a tool that does not read CSS variables
|
|
140
|
+
* (an image converter, an email, a slide) draws what a browser does. Light by default, or the drawing's own `data-theme`.
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* import { sceneToSvg } from "@johnmorrisdotca/kyogijo-stadiums/scene";
|
|
144
|
+
* const svg = sceneToSvg({ sport: "nhl", id: "rink" }, { theme: "dark", width: 800 });
|
|
145
|
+
* console.log(svg.includes("var("), svg.includes("<style"), svg.startsWith("<svg"));
|
|
146
|
+
* // false false true
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
export function sceneToSvg(source, options = {}) {
|
|
150
|
+
const scene = sceneOf(source, options);
|
|
151
|
+
const { x, y, w, h } = scene.viewBox;
|
|
152
|
+
const size = options.width !== undefined || options.height !== undefined ? { width: options.width ?? Math.round((options.height * w) / h), height: options.height ?? Math.round(options.width * (h / w)) } : undefined;
|
|
153
|
+
const defs = defsOf(scene);
|
|
154
|
+
const children = [
|
|
155
|
+
{ tag: "title", attrs: {}, text: scene.title },
|
|
156
|
+
{ tag: "desc", attrs: {}, text: scene.description },
|
|
157
|
+
...(options.background === undefined ? [] : [{ tag: "rect", attrs: { x: f(x), y: f(y), width: f(w), height: f(h), fill: options.background } }]),
|
|
158
|
+
...(defs.length === 0 ? [] : [{ tag: "defs", attrs: {}, children: defs }]),
|
|
159
|
+
...bodyOf(scene),
|
|
160
|
+
];
|
|
161
|
+
const root = {
|
|
162
|
+
tag: "svg",
|
|
163
|
+
attrs: { xmlns: "http://www.w3.org/2000/svg", viewBox: `${f(x)} ${f(y)} ${f(w)} ${f(h)}`, ...(size === undefined ? {} : { width: String(size.width), height: String(size.height) }), role: "img" },
|
|
164
|
+
children,
|
|
165
|
+
};
|
|
166
|
+
return specToSvg(root);
|
|
167
|
+
}
|