@johnmorrisdotca/kyogijo-stadiums 0.4.0 → 0.6.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 +133 -0
- package/NOTICE.md +182 -2
- package/README.md +106 -19
- package/dist/annotate.js +1 -1
- package/dist/arenas-entry.d.ts +4 -0
- package/dist/arenas-entry.js +3 -0
- package/dist/arenas.d.ts +125 -0
- package/dist/arenas.data.d.ts +2 -0
- package/dist/arenas.data.js +3763 -0
- package/dist/arenas.js +72 -0
- package/dist/athletics/describe.d.ts +8 -0
- package/dist/athletics/describe.js +41 -0
- package/dist/athletics/events.d.ts +17 -0
- package/dist/athletics/events.js +48 -0
- package/dist/athletics/facilities.d.ts +80 -0
- package/dist/athletics/facilities.js +176 -0
- package/dist/athletics/infield.d.ts +79 -0
- package/dist/athletics/infield.js +166 -0
- package/dist/athletics/layout.d.ts +13 -0
- package/dist/athletics/layout.js +98 -0
- package/dist/athletics/oval.d.ts +57 -0
- package/dist/athletics/oval.js +116 -0
- package/dist/athletics/plan.d.ts +97 -0
- package/dist/athletics/plan.js +355 -0
- package/dist/athletics/races.d.ts +102 -0
- package/dist/athletics/races.js +355 -0
- package/dist/athletics/steeplechase.d.ts +72 -0
- package/dist/athletics/steeplechase.js +196 -0
- package/dist/athletics/stencil.d.ts +13 -0
- package/dist/athletics/stencil.js +85 -0
- package/dist/athletics/track.d.ts +54 -0
- package/dist/athletics/track.js +105 -0
- package/dist/athletics/types.d.ts +77 -0
- package/dist/athletics/types.js +1 -0
- package/dist/athletics-entry.d.ts +17 -0
- package/dist/athletics-entry.js +11 -0
- package/dist/athletics.style.d.ts +16 -0
- package/dist/athletics.style.js +51 -0
- package/dist/atlas-entry.d.ts +6 -2
- package/dist/atlas-entry.js +4 -2
- package/dist/atlas.d.ts +117 -23
- package/dist/atlas.js +197 -12
- package/dist/background.js +2 -1
- package/dist/budo/describe.d.ts +13 -0
- package/dist/budo/describe.js +120 -0
- package/dist/budo/dojo.api.d.ts +64 -0
- package/dist/budo/dojo.api.js +46 -0
- package/dist/budo/dojo.d.ts +72 -0
- package/dist/budo/dojo.js +300 -0
- package/dist/budo/judo.d.ts +61 -0
- package/dist/budo/judo.js +109 -0
- package/dist/budo/karate.d.ts +29 -0
- package/dist/budo/karate.js +80 -0
- package/dist/budo/kendo.d.ts +18 -0
- package/dist/budo/kendo.js +44 -0
- package/dist/budo/kimarite.d.ts +19 -0
- package/dist/budo/kimarite.js +33 -0
- package/dist/budo/kit.d.ts +16 -0
- package/dist/budo/kit.js +24 -0
- package/dist/budo/kyudo.d.ts +28 -0
- package/dist/budo/kyudo.js +66 -0
- package/dist/budo/looks.d.ts +28 -0
- package/dist/budo/looks.js +58 -0
- package/dist/budo/parts.d.ts +32 -0
- package/dist/budo/parts.js +10 -0
- package/dist/budo/plan.d.ts +64 -0
- package/dist/budo/plan.js +139 -0
- package/dist/budo/room.d.ts +133 -0
- package/dist/budo/room.js +111 -0
- package/dist/budo/sumo.d.ts +31 -0
- package/dist/budo/sumo.js +71 -0
- package/dist/budo/tatami.d.ts +55 -0
- package/dist/budo/tatami.js +156 -0
- package/dist/budo/types.d.ts +192 -0
- package/dist/budo/types.js +2 -0
- package/dist/budo-entry.d.ts +22 -0
- package/dist/budo-entry.js +13 -0
- package/dist/budo.board.d.ts +28 -0
- package/dist/budo.board.js +81 -0
- package/dist/budo.score.d.ts +18 -0
- package/dist/budo.score.js +52 -0
- package/dist/budo.style.d.ts +6 -0
- package/dist/budo.style.js +67 -0
- package/dist/charts-entry.d.ts +6 -1
- package/dist/charts-entry.js +3 -0
- package/dist/charts.athletics.d.ts +183 -0
- package/dist/charts.athletics.js +455 -0
- package/dist/charts.baseball.js +1 -1
- package/dist/charts.curling.heat.d.ts +24 -0
- package/dist/charts.curling.heat.js +116 -0
- package/dist/charts.curling.js +14 -3
- package/dist/charts.js +4 -4
- package/dist/charts.pool.d.ts +67 -0
- package/dist/charts.pool.js +76 -0
- package/dist/charts.skating.d.ts +204 -0
- package/dist/charts.skating.js +426 -0
- package/dist/charts.soccer.js +3 -3
- package/dist/charts.types.d.ts +63 -1
- package/dist/colour.d.ts +4 -4
- package/dist/colour.js +4 -4
- package/dist/court.d.ts +85 -2
- package/dist/court.js +102 -7
- package/dist/court.more.d.ts +46 -0
- package/dist/court.more.js +219 -0
- package/dist/courts.style.d.ts +14 -2
- package/dist/courts.style.js +51 -4
- package/dist/crowd.d.ts +164 -0
- package/dist/crowd.draw.d.ts +17 -0
- package/dist/crowd.draw.js +204 -0
- package/dist/crowd.js +480 -0
- package/dist/crowd.park.d.ts +19 -0
- package/dist/crowd.park.js +153 -0
- package/dist/curling.stats.d.ts +271 -0
- package/dist/curling.stats.js +301 -0
- package/dist/draw.d.ts +16 -0
- package/dist/draw.js +462 -54
- package/dist/element-entry.d.ts +3 -1
- package/dist/element-entry.js +2 -1
- package/dist/element.d.ts +13 -2
- package/dist/element.js +79 -4
- package/dist/emblems-source.d.ts +50 -0
- package/dist/emblems-source.js +56 -0
- package/dist/eras.d.ts +3 -1
- package/dist/eras.js +8 -3
- package/dist/features.js +204 -0
- package/dist/field.data.d.ts +16 -0
- package/dist/field.data.js +50 -0
- package/dist/fields.kit.d.ts +2 -2
- package/dist/fields.types.d.ts +24 -4
- package/dist/formations.js +18 -7
- package/dist/formations.sports.d.ts +8 -0
- package/dist/formations.sports.js +251 -0
- package/dist/frames.providers.js +71 -8
- package/dist/geojson.js +3 -0
- package/dist/geometry-entry.d.ts +2 -1
- package/dist/geometry-entry.js +2 -1
- package/dist/geometry.d.ts +20 -0
- package/dist/geometry.js +78 -1
- package/dist/grounds.d.ts +4 -0
- package/dist/grounds.data.js +12 -0
- package/dist/halls.d.ts +65 -0
- package/dist/halls.js +122 -0
- package/dist/hardwood.d.ts +31 -0
- package/dist/hardwood.js +157 -0
- package/dist/heat.js +21 -13
- package/dist/heat.types.d.ts +5 -0
- package/dist/heat.zones.d.ts +4 -2
- package/dist/heat.zones.js +207 -7
- package/dist/homeside.js +24 -0
- package/dist/index.d.ts +30 -4
- package/dist/index.js +18 -2
- package/dist/layers-entry.d.ts +2 -1
- package/dist/layers-entry.js +1 -0
- package/dist/layers.d.ts +2 -1
- package/dist/layers.env.d.ts +12 -1
- package/dist/layers.env.js +36 -4
- package/dist/layers.js +32 -13
- package/dist/layers.types.d.ts +9 -2
- package/dist/leagues/athletics.d.ts +63 -0
- package/dist/leagues/athletics.js +87 -0
- package/dist/leagues/budo.d.ts +196 -0
- package/dist/leagues/budo.js +105 -0
- package/dist/leagues/courts.d.ts +269 -4
- package/dist/leagues/courts.js +155 -1
- package/dist/leagues/hockey.js +41 -19
- package/dist/leagues/skating.d.ts +145 -0
- package/dist/leagues/skating.js +73 -0
- package/dist/leagues/sources.js +41 -0
- package/dist/leagues/types.d.ts +1 -1
- package/dist/leagues-entry.d.ts +4 -2
- package/dist/leagues-entry.js +2 -1
- package/dist/leagues.d.ts +17 -4
- package/dist/leagues.js +246 -5
- package/dist/lineup.codes.d.ts +23 -0
- package/dist/lineup.codes.js +122 -0
- package/dist/logo.d.ts +25 -0
- package/dist/logo.js +47 -0
- package/dist/look.d.ts +3 -3
- package/dist/look.js +39 -6
- package/dist/outline.d.ts +1 -1
- package/dist/outline.js +46 -4
- package/dist/pageant.d.ts +120 -0
- package/dist/pageant.draw.d.ts +21 -0
- package/dist/pageant.draw.js +171 -0
- package/dist/pageant.js +211 -0
- package/dist/pageant.style.d.ts +8 -0
- package/dist/pageant.style.js +42 -0
- package/dist/png.d.ts +8 -1
- package/dist/png.js +21 -1
- package/dist/pointer-entry.d.ts +2 -0
- package/dist/pointer-entry.js +2 -0
- package/dist/pointer.d.ts +113 -0
- package/dist/pointer.js +151 -0
- package/dist/pool.d.ts +176 -0
- package/dist/pool.figures.d.ts +306 -0
- package/dist/pool.figures.js +148 -0
- package/dist/pool.js +304 -0
- package/dist/pool.kit.d.ts +48 -0
- package/dist/pool.kit.js +141 -0
- package/dist/pool.labels.d.ts +20 -0
- package/dist/pool.labels.js +258 -0
- package/dist/pool.race.d.ts +65 -0
- package/dist/pool.race.js +140 -0
- package/dist/pool.replay.d.ts +46 -0
- package/dist/pool.replay.js +127 -0
- package/dist/pool.section.d.ts +13 -0
- package/dist/pool.section.js +83 -0
- package/dist/pool.style.d.ts +20 -0
- package/dist/pool.style.js +169 -0
- package/dist/pool.types.d.ts +145 -0
- package/dist/pool.types.js +3 -0
- package/dist/pool.words.d.ts +13 -0
- package/dist/pool.words.js +101 -0
- package/dist/ranges.names.js +53 -3
- package/dist/replay-entry.d.ts +3 -0
- package/dist/replay-entry.js +2 -0
- package/dist/restyle.js +1 -1
- package/dist/road.draw.js +3 -2
- package/dist/road.js +2 -1
- package/dist/road.types.d.ts +3 -0
- package/dist/rules.d.ts +138 -0
- package/dist/rules.js +511 -0
- package/dist/rules.types.d.ts +4 -0
- package/dist/rules.types.js +64 -0
- package/dist/score.d.ts +3 -0
- package/dist/score.js +93 -6
- package/dist/sheet.d.ts +3 -0
- package/dist/sheet.js +7 -3
- package/dist/situation.d.ts +135 -0
- package/dist/situation.draw.d.ts +34 -0
- package/dist/situation.draw.js +202 -0
- package/dist/situation.js +337 -0
- package/dist/situation.style.d.ts +2 -0
- package/dist/situation.style.js +45 -0
- package/dist/situation.types.d.ts +77 -0
- package/dist/situation.types.js +3 -0
- package/dist/situation.widgets.d.ts +39 -0
- package/dist/situation.widgets.js +129 -0
- package/dist/skating/describe.d.ts +7 -0
- package/dist/skating/describe.js +44 -0
- package/dist/skating/events.d.ts +24 -0
- package/dist/skating/events.js +47 -0
- package/dist/skating/figure.d.ts +24 -0
- package/dist/skating/figure.js +37 -0
- package/dist/skating/long.d.ts +54 -0
- package/dist/skating/long.js +111 -0
- package/dist/skating/parts.d.ts +43 -0
- package/dist/skating/parts.js +40 -0
- package/dist/skating/plan.d.ts +55 -0
- package/dist/skating/plan.js +197 -0
- package/dist/skating/short.d.ts +47 -0
- package/dist/skating/short.js +89 -0
- package/dist/skating/track.d.ts +66 -0
- package/dist/skating/track.js +94 -0
- package/dist/skating/types.d.ts +57 -0
- package/dist/skating/types.js +3 -0
- package/dist/skating-entry.d.ts +17 -0
- package/dist/skating-entry.js +11 -0
- package/dist/skating.style.d.ts +4 -0
- package/dist/skating.style.js +24 -0
- package/dist/strings.d.ts +470 -0
- package/dist/strings.js +674 -13
- package/dist/style.d.ts +2 -0
- package/dist/style.js +19 -2
- package/dist/surfaces/artistic.d.ts +9 -0
- package/dist/surfaces/artistic.js +94 -0
- package/dist/surfaces/athletics.d.ts +16 -0
- package/dist/surfaces/athletics.js +132 -0
- package/dist/surfaces/baseball.js +17 -0
- package/dist/surfaces/basketball.floor.d.ts +27 -0
- package/dist/surfaces/basketball.floor.js +83 -0
- package/dist/surfaces/basketball.gear.d.ts +27 -0
- package/dist/surfaces/basketball.gear.js +90 -0
- package/dist/surfaces/basketball.js +44 -22
- package/dist/surfaces/budo.d.ts +6 -0
- package/dist/surfaces/budo.js +104 -0
- package/dist/surfaces/curling.d.ts +16 -0
- package/dist/surfaces/curling.fold.d.ts +67 -0
- package/dist/surfaces/curling.fold.js +107 -0
- package/dist/surfaces/curling.js +37 -3
- package/dist/surfaces/dive.d.ts +9 -0
- package/dist/surfaces/dive.js +128 -0
- package/dist/surfaces/field.d.ts +7 -2
- package/dist/surfaces/field.js +15 -9
- package/dist/surfaces/football.js +7 -0
- package/dist/surfaces/hockey.js +4 -2
- package/dist/surfaces/net.d.ts +2 -1
- package/dist/surfaces/net.js +160 -15
- package/dist/surfaces/polo.d.ts +12 -0
- package/dist/surfaces/polo.js +159 -0
- package/dist/surfaces/pool.d.ts +27 -0
- package/dist/surfaces/pool.js +198 -0
- package/dist/surfaces/skating.d.ts +6 -0
- package/dist/surfaces/skating.js +62 -0
- package/dist/surfaces/soccer.js +2 -1
- package/dist/surfaces/surface.d.ts +76 -0
- package/dist/surfaces/venue.d.ts +15 -0
- package/dist/surfaces/venue.js +95 -0
- package/dist/target-entry.d.ts +2 -0
- package/dist/target-entry.js +2 -0
- package/dist/target.d.ts +200 -0
- package/dist/target.js +525 -0
- package/dist/team.common.d.ts +1 -1
- package/dist/team.common.js +3 -3
- package/dist/team.formation.d.ts +3 -1
- package/dist/team.formation.js +3 -3
- package/dist/team.layers.d.ts +2 -2
- package/dist/team.layers.js +4 -1
- package/dist/team.lineup.d.ts +27 -0
- package/dist/team.lineup.js +207 -0
- package/dist/team.network.js +6 -5
- package/dist/team.shape.js +5 -5
- package/dist/team.types.d.ts +40 -1
- package/dist/tenants.d.ts +77 -0
- package/dist/tenants.data.d.ts +8 -0
- package/dist/tenants.data.js +1793 -0
- package/dist/tenants.js +46 -0
- package/dist/turn-entry.d.ts +12 -0
- package/dist/turn-entry.js +7 -0
- package/dist/turn.camera.d.ts +116 -0
- package/dist/turn.camera.js +184 -0
- package/dist/turn.dom.d.ts +40 -0
- package/dist/turn.dom.js +353 -0
- package/dist/turn.kit.d.ts +86 -0
- package/dist/turn.kit.js +110 -0
- package/dist/turn.markup.d.ts +48 -0
- package/dist/turn.markup.js +108 -0
- package/dist/turn.model.d.ts +78 -0
- package/dist/turn.model.js +91 -0
- package/dist/turn.style.d.ts +5 -0
- package/dist/turn.style.js +56 -0
- package/dist/types.d.ts +221 -20
- package/dist/units.d.ts +1 -1
- package/dist/units.js +22 -1
- package/dist/uprights.board.d.ts +17 -0
- package/dist/uprights.board.js +59 -0
- package/dist/uprights.budo.d.ts +17 -0
- package/dist/uprights.budo.js +87 -0
- package/dist/uprights.d.ts +37 -0
- package/dist/uprights.fields.d.ts +32 -0
- package/dist/uprights.fields.js +172 -0
- package/dist/uprights.goal.d.ts +49 -0
- package/dist/uprights.goal.js +69 -0
- package/dist/uprights.js +169 -0
- package/dist/uprights.more.d.ts +7 -0
- package/dist/uprights.more.js +183 -0
- package/dist/uprights.net.d.ts +12 -0
- package/dist/uprights.net.js +109 -0
- package/dist/uprights.netkit.d.ts +28 -0
- package/dist/uprights.netkit.js +67 -0
- package/dist/uprights.pools.d.ts +8 -0
- package/dist/uprights.pools.js +190 -0
- package/dist/uprights.rinks.d.ts +27 -0
- package/dist/uprights.rinks.js +106 -0
- package/dist/uprights.track.d.ts +50 -0
- package/dist/uprights.track.js +246 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/zones-entry.d.ts +2 -0
- package/dist/zones-entry.js +2 -0
- package/package.json +56 -3
package/dist/pageant.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// The pageant: each team's flag or logo laid out large over its half of the surface, as a stadium lays giant flags and crests on the pitch for a final's ceremony
|
|
2
|
+
// (a `carpet`: a rectangle at the flag's own ratio with a border; a `stage`: a crest on a large round disc with a ring), and an emblem or a trophy in the centre circle. This file
|
|
3
|
+
// decides what is asked (`pageantOf`) and where each picture goes (`pageantLayout`); `pageant.draw.ts` draws them. Written for this package; the idea is a broadcast's, and no
|
|
4
|
+
// flag, crest or emblem is drawn or supplied here.
|
|
5
|
+
import { safeColour } from "./colour.js";
|
|
6
|
+
import { renderedEmblem } from "./emblems-source.js";
|
|
7
|
+
import { safeLogo } from "./logo.js";
|
|
8
|
+
import { isNetSport } from "./court.js";
|
|
9
|
+
/** The border band of a carpet, as a share of its shorter side: the least, the most and the usual. */
|
|
10
|
+
export const PAGEANT_BORDER = { min: 0.02, max: 0.15, usual: 0.06 };
|
|
11
|
+
/** The least and the most a carpet or a stage is scaled to, as a share of the room its half gives it. */
|
|
12
|
+
export const PAGEANT_SCALE = { min: 0.4, max: 1 };
|
|
13
|
+
/** The ratio of a carpet's width over its height: a flag of 3 to 2 unless the team or its SVG says otherwise, held between a square and 3 to 1. */
|
|
14
|
+
export const PAGEANT_RATIO = { min: 1, max: 3, usual: 1.5 };
|
|
15
|
+
/** How far a filled carpet's ratio may differ from its flag's, as a factor either way: the art is stretched and cropped by its square root. */
|
|
16
|
+
export const PAGEANT_FILL_LIMIT = 1.75;
|
|
17
|
+
/** What the plate is shrunk to when it stays in the middle of a pageant, so that the pictures have room. */
|
|
18
|
+
const PLATE_SCALE = 0.85;
|
|
19
|
+
const oneOf = (value, allowed) => allowed.includes(value) ? value : undefined;
|
|
20
|
+
/** A colour or one of the words a choice also takes. */
|
|
21
|
+
const colourOrWord = (value, words, fallback) => {
|
|
22
|
+
if (typeof value === "string" && words.includes(value))
|
|
23
|
+
return value;
|
|
24
|
+
return safeColour(value) ?? fallback;
|
|
25
|
+
};
|
|
26
|
+
const finite = (value, least, most, fallback) => (typeof value === "number" && Number.isFinite(value) ? Math.min(most, Math.max(least, value)) : fallback);
|
|
27
|
+
/** The sports whose scoreboard covers the middle of the surface (a table of sets and games, an innings board): their score goes to the strip in a pageant. */
|
|
28
|
+
const BOARDS = (sport) => isNetSport(sport) || sport === "cricket";
|
|
29
|
+
/** The sports whose score is a plate that a pageant can shrink: a curling sheet's and a net court's boards are drawn to the room they are given and are not. */
|
|
30
|
+
export const plateShrinks = (sport) => !BOARDS(sport) && sport !== "curling";
|
|
31
|
+
/**
|
|
32
|
+
* What the options ask of a pageant, or `undefined` for none: `pageant` unset or `false`, a drawing with no score, one of the badge size, or a special game. A choice that is not one of
|
|
33
|
+
* the values is the default's; a colour must pass `safeColour`, the emblem `safeLogo`, a number is held to its range.
|
|
34
|
+
*/
|
|
35
|
+
export function pageantOf(o) {
|
|
36
|
+
const given = o.pageant;
|
|
37
|
+
if (given === undefined || given === false || given === null)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (o.home === undefined || o.away === undefined || o.size === "badge" || o.host !== undefined)
|
|
40
|
+
return undefined;
|
|
41
|
+
const p = given === true ? {} : typeof given === "string" ? { style: given } : typeof given === "object" ? given : {};
|
|
42
|
+
const style = oneOf(p.style, ["carpet", "stage"]) ?? "carpet";
|
|
43
|
+
const href = safeLogo(p.emblem, { theme: o.theme === "light" || o.theme === "dark" ? o.theme : undefined });
|
|
44
|
+
const asked = oneOf(p.score, ["auto", "centre", "strip"]) ?? "auto";
|
|
45
|
+
// The emblem takes the middle, and a board is as wide as the middle: both send the score to the strip, unless the options say otherwise.
|
|
46
|
+
const score = asked === "auto" && (href !== undefined || BOARDS(o.sport)) ? "strip" : asked;
|
|
47
|
+
return {
|
|
48
|
+
style,
|
|
49
|
+
border: colourOrWord(p.border, ["dark", "team"], "dark"),
|
|
50
|
+
borderWidth: finite(p.borderWidth, PAGEANT_BORDER.min, PAGEANT_BORDER.max, PAGEANT_BORDER.usual),
|
|
51
|
+
fabric: p.fabric !== false,
|
|
52
|
+
lines: oneOf(p.lines, ["over", "under", "through"]) ?? "over",
|
|
53
|
+
ring: colourOrWord(p.ring, ["gold", "team"], "gold"),
|
|
54
|
+
...(href === undefined ? {} : { emblem: { href, shape: oneOf(p.emblemShape, ["round", "plain"]) ?? "round", fit: oneOf(p.emblemFit, ["contain", "cover"]) ?? "contain", name: typeof p.emblemName === "string" && p.emblemName.trim() !== "" ? p.emblemName.trim().slice(0, 60) : undefined } }),
|
|
55
|
+
score,
|
|
56
|
+
scale: finite(p.scale, PAGEANT_SCALE.min, PAGEANT_SCALE.max, PAGEANT_SCALE.max),
|
|
57
|
+
fit: oneOf(p.fit, ["fill", "ratio"]) ?? "fill",
|
|
58
|
+
names: typeof p.names === "boolean" ? p.names : undefined,
|
|
59
|
+
animate: oneOf(p.animate, ["none", "unfurl", "fade"]) ?? "none",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** The ratio of an SVG document's picture: its `viewBox`, else its `width` and `height`. */
|
|
63
|
+
function svgRatio(text) {
|
|
64
|
+
const head = text.slice(0, 2000);
|
|
65
|
+
const open = /<svg\b[^>]*>/i.exec(head)?.[0] ?? head;
|
|
66
|
+
const box = /viewBox\s*=\s*["']\s*[-\d.e]+[\s,]+[-\d.e]+[\s,]+([\d.e]+)[\s,]+([\d.e]+)/i.exec(open);
|
|
67
|
+
if (box !== null && Number(box[1]) > 0 && Number(box[2]) > 0)
|
|
68
|
+
return Number(box[1]) / Number(box[2]);
|
|
69
|
+
const w = /\swidth\s*=\s*["']\s*([\d.]+)/i.exec(open);
|
|
70
|
+
const h = /\sheight\s*=\s*["']\s*([\d.]+)/i.exec(open);
|
|
71
|
+
return w !== null && h !== null && Number(w[1]) > 0 && Number(h[1]) > 0 ? Number(w[1]) / Number(h[1]) : undefined;
|
|
72
|
+
}
|
|
73
|
+
/** How wide a team's carpet is for its height: the team's `logoRatio`, else its SVG logo's own, else 3 to 2; held between a square and 3 to 1. */
|
|
74
|
+
export function ratioOf(team) {
|
|
75
|
+
const clamp = (value) => Math.min(PAGEANT_RATIO.max, Math.max(PAGEANT_RATIO.min, value));
|
|
76
|
+
if (typeof team.logoRatio === "number" && Number.isFinite(team.logoRatio) && team.logoRatio > 0)
|
|
77
|
+
return clamp(team.logoRatio);
|
|
78
|
+
const logo = typeof team.logo === "string" ? team.logo.trim() : "";
|
|
79
|
+
let found;
|
|
80
|
+
if (renderedEmblem(logo) !== undefined)
|
|
81
|
+
found = 1;
|
|
82
|
+
else if (/^(?:<\?xml|<svg)\b/i.test(logo))
|
|
83
|
+
found = svgRatio(logo);
|
|
84
|
+
else if (/^data:image\/svg\+xml/i.test(logo)) {
|
|
85
|
+
const comma = logo.indexOf(",");
|
|
86
|
+
try {
|
|
87
|
+
const body = /;base64/i.test(logo.slice(0, comma)) ? atob(logo.slice(comma + 1)) : decodeURIComponent(logo.slice(comma + 1));
|
|
88
|
+
found = svgRatio(body);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
found = undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return clamp(found ?? PAGEANT_RATIO.usual);
|
|
95
|
+
}
|
|
96
|
+
/** A rectangle's outline, as points the outline's `contains` is asked about: corners, then more along each side. */
|
|
97
|
+
function ring(cx, cy, w, h, grow, to) {
|
|
98
|
+
const out = [];
|
|
99
|
+
const hw = w / 2 + grow;
|
|
100
|
+
const hh = h / 2 + grow;
|
|
101
|
+
const steps = 6;
|
|
102
|
+
for (let i = 0; i <= steps; i += 1) {
|
|
103
|
+
const t = -1 + (2 * i) / steps;
|
|
104
|
+
for (const [ex, ey] of [[t * hw, -hh], [t * hw, hh], [-hw, t * hh], [hw, t * hh]]) {
|
|
105
|
+
const [dx, dy] = to(ex, ey);
|
|
106
|
+
out.push([cx + dx, cy + dy]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
const around = (cx, cy, r, grow) => Array.from({ length: 16 }, (_, i) => [cx + (r + grow) * Math.cos((i * Math.PI) / 8), cy + (r + grow) * Math.sin((i * Math.PI) / 8)]);
|
|
112
|
+
/**
|
|
113
|
+
* Where each team's picture and the emblem go. A picture is sized to the room its half gives it (less a margin) at its own ratio, and is moved in until it lies inside the surface's outline
|
|
114
|
+
* (a rink's rounded corners, a ballpark's fence, an oval's curve). The middle is kept clear of the emblem and of the plate: the pictures are narrower where they must be.
|
|
115
|
+
* @returns `undefined` when the surface has no playing area to lay a picture on.
|
|
116
|
+
*/
|
|
117
|
+
export function pageantLayout(input) {
|
|
118
|
+
const { pageant: p, surface, outline, turned } = input;
|
|
119
|
+
const box = surface.whole ?? surface.box ?? outline.box;
|
|
120
|
+
if (!(box.w > 0 && box.h > 0))
|
|
121
|
+
return undefined;
|
|
122
|
+
const minDim = Math.min(box.w, box.h);
|
|
123
|
+
const margin = (p.fit === "fill" ? 0.05 : 0.03) * minDim;
|
|
124
|
+
const mid = box.x + box.w / 2;
|
|
125
|
+
const plan = surface.pageant;
|
|
126
|
+
const centre = plan?.centre ?? { cx: mid, cy: box.y + box.h / 2, r: surface.centreRadius ?? 0.14 * minDim };
|
|
127
|
+
const emblemR = p.emblem === undefined ? 0 : centre.r * 0.92;
|
|
128
|
+
const scale = plateShrinks(input.sport) ? PLATE_SCALE : 1;
|
|
129
|
+
// What the middle holds: the emblem's disc, the plate's length along the surface, the cricket pitch.
|
|
130
|
+
const plateAlong = input.plate && plateShrinks(input.sport) && plan?.left === undefined ? ((turned ? surface.plate.h : surface.plate.w) * scale) / 2 : 0;
|
|
131
|
+
const clear = Math.max(emblemR, plateAlong, plan?.clear ?? 0);
|
|
132
|
+
const eye = (ex, ey) => (turned ? [ey, -ex] : [ex, ey]);
|
|
133
|
+
const place = (team, which, spot) => {
|
|
134
|
+
const along = spot.x1 - spot.x0;
|
|
135
|
+
const across = spot.y1 - spot.y0;
|
|
136
|
+
const cx = (spot.x0 + spot.x1) / 2;
|
|
137
|
+
const cy = (spot.y0 + spot.y1) / 2;
|
|
138
|
+
// The room as the eye sees it: a tall drawing's halves lie one above the other.
|
|
139
|
+
const roomW = turned ? across : along;
|
|
140
|
+
const roomH = turned ? along : across;
|
|
141
|
+
if (p.style === "stage") {
|
|
142
|
+
let r = (spot.disc === undefined ? Math.min(roomW, roomH) : Math.min(spot.disc, roomW, roomH)) / 2 * p.scale;
|
|
143
|
+
for (let i = 0; i < 50 && !around(cx, cy, r, margin * 0.5).every(([x, y]) => outline.contains(x, y)); i += 1)
|
|
144
|
+
r *= 0.96;
|
|
145
|
+
return { team, spot: which, cx, cy, w: 2 * r, h: 2 * r, r };
|
|
146
|
+
}
|
|
147
|
+
const ratio = team.ratio;
|
|
148
|
+
let ratioOfCarpet = ratio;
|
|
149
|
+
let filled = false;
|
|
150
|
+
let w;
|
|
151
|
+
// In a round room (a curling house) the rectangle's corners lie on the circle; in a box it is the box's own shape that limits it.
|
|
152
|
+
if (spot.disc !== undefined)
|
|
153
|
+
w = Math.min(((spot.disc * ratio) / Math.sqrt(1 + ratio * ratio)) * 0.94, roomW, roomH * ratio) * p.scale;
|
|
154
|
+
else if (p.fit === "fill") {
|
|
155
|
+
// The carpet is the room's own shape, as far as its flag's ratio allows: not more than PAGEANT_FILL_LIMIT either way.
|
|
156
|
+
const shape = Math.min(Math.max(roomW / roomH, ratio / PAGEANT_FILL_LIMIT), ratio * PAGEANT_FILL_LIMIT);
|
|
157
|
+
w = Math.min(roomW, roomH * shape) * p.scale;
|
|
158
|
+
filled = true;
|
|
159
|
+
ratioOfCarpet = shape;
|
|
160
|
+
}
|
|
161
|
+
else
|
|
162
|
+
w = Math.min(roomW, roomH * ratio) * p.scale;
|
|
163
|
+
let h = w / ratioOfCarpet;
|
|
164
|
+
let px = cx;
|
|
165
|
+
for (let i = 0; i < 60 && !ring(px, cy, w, h, margin * 0.5, eye).every(([x, y]) => outline.contains(x, y)); i += 1) {
|
|
166
|
+
if (filled) {
|
|
167
|
+
// Pull in the outer edge, along the surface, so that a rink's rounded corner or a fence is cleared and the carpet keeps its full breadth across.
|
|
168
|
+
const cut = (turned ? h : w) * 0.03;
|
|
169
|
+
if (turned)
|
|
170
|
+
h -= cut;
|
|
171
|
+
else
|
|
172
|
+
w -= cut;
|
|
173
|
+
px += ((which === "left" ? 1 : -1) * cut) / 2;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
w *= 0.96;
|
|
177
|
+
h *= 0.96;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { team, spot: which, cx: px, cy, w, h, ...(filled ? { filled } : {}) };
|
|
181
|
+
};
|
|
182
|
+
const spotOf = (side) => {
|
|
183
|
+
const given = side === "left" ? plan?.left : plan?.right;
|
|
184
|
+
if (given !== undefined)
|
|
185
|
+
return { x0: given.cx - given.w / 2, x1: given.cx + given.w / 2, y0: given.cy - given.h / 2, y1: given.cy + given.h / 2, ...(given.disc === undefined ? {} : { disc: given.disc }) };
|
|
186
|
+
const y0 = box.y + margin;
|
|
187
|
+
const y1 = box.y + box.h - margin;
|
|
188
|
+
return side === "left" ? { x0: box.x + margin, x1: mid - clear - margin, y0, y1 } : { x0: mid + clear + margin, x1: box.x + box.w - margin, y0, y1 };
|
|
189
|
+
};
|
|
190
|
+
const pictures = [place(input.left, "left", spotOf("left")), place(input.right, "right", spotOf("right"))];
|
|
191
|
+
if (!pictures.every((picture) => picture.w > 0 && picture.h > 0 && Number.isFinite(picture.cx + picture.cy)))
|
|
192
|
+
return undefined;
|
|
193
|
+
const layout = { pictures, plateScale: scale };
|
|
194
|
+
if (p.emblem !== undefined && emblemR > 0)
|
|
195
|
+
layout.emblem = { cx: centre.cx, cy: centre.cy, r: emblemR };
|
|
196
|
+
return layout;
|
|
197
|
+
}
|
|
198
|
+
/** How much of their width the pictures must keep with the plate in the middle, against without it, before the plate in the middle gives way to the strip. */
|
|
199
|
+
export const PLATE_COST = 0.7;
|
|
200
|
+
/**
|
|
201
|
+
* Where the score goes when the options say `auto` and there is no emblem or board: the plate stays in the middle if the pictures lose little of their size to it, otherwise the score
|
|
202
|
+
* goes to the strip under the drawing and the pictures take the room.
|
|
203
|
+
*/
|
|
204
|
+
export function autoScore(input) {
|
|
205
|
+
const withPlate = pageantLayout({ ...input, plate: true });
|
|
206
|
+
const without = pageantLayout({ ...input, plate: false });
|
|
207
|
+
if (withPlate === undefined || without === undefined)
|
|
208
|
+
return "centre";
|
|
209
|
+
const size = (layout) => Math.min(...layout.pictures.map((picture) => picture.w));
|
|
210
|
+
return size(withPlate) >= PLATE_COST * size(without) ? "centre" : "strip";
|
|
211
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The pageant's colours, light and dark. */
|
|
2
|
+
export declare const KYOGIJO_PAGEANT_STYLE: string;
|
|
3
|
+
/**
|
|
4
|
+
* The entrance (`animate: "unfurl"` or `"fade"`), in CSS only, and only for a reader who has not asked for less motion: a carpet rolls out from its own end, a stage and the emblem
|
|
5
|
+
* grow, and `fade` brings each in. The end state is the drawing's own, so a reader who has asked for less sees it at once, and a renderer that reads no CSS animation (the canvas,
|
|
6
|
+
* the PNG) draws it finished. The second team's picture and then the emblem come a little later.
|
|
7
|
+
*/
|
|
8
|
+
export declare const KYOGIJO_PAGEANT_MOTION: string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// The pageant's rules: the colours of a carpet's border and fabric, of a stage's disc and ring, and the entrance. The colours are custom properties with fallbacks (`--ky-pg-border`,
|
|
2
|
+
// `--ky-pg-edge`, `--ky-pg-seam`, `--ky-pg-shadow`, `--ky-pg-disc`, `--ky-pg-groove`, `--ky-pg-light`), so a page can set them; the dark ones are the dark theme's. A drawing with a
|
|
3
|
+
// pageant writes these in; `KYOGIJO_STYLE` has them for a page that draws with `style: "none"`.
|
|
4
|
+
/** The pageant's colours, light and dark. */
|
|
5
|
+
export const KYOGIJO_PAGEANT_STYLE = `
|
|
6
|
+
.kyogijo .ky-pg-border{fill:var(--ky-pg-border,#14181a)}
|
|
7
|
+
.kyogijo .ky-pg-edge{fill:none;stroke:var(--ky-pg-edge,rgba(255,255,255,.55))}
|
|
8
|
+
.kyogijo .ky-pg-seam{fill:none;stroke:var(--ky-pg-seam,rgba(0,0,0,.4))}
|
|
9
|
+
.kyogijo .ky-pg-shadow{fill:var(--ky-pg-shadow,rgba(0,0,0,.26))}
|
|
10
|
+
.kyogijo .ky-pg-ground{fill:var(--c,#d9dde0)}
|
|
11
|
+
.kyogijo .ky-pg-plain{fill:var(--c,var(--ky-mark-plain,#f4f5f6))}
|
|
12
|
+
.kyogijo .ky-pg-band{fill:var(--ci,#fff)}
|
|
13
|
+
.kyogijo .ky-pg-name{fill:var(--c,#14181a);text-anchor:middle}
|
|
14
|
+
.kyogijo .ky-pg-disc{fill:var(--ky-pg-disc,#101417)}
|
|
15
|
+
.kyogijo .ky-pg-groove{fill:none;stroke:var(--ky-pg-groove,rgba(0,0,0,.5))}
|
|
16
|
+
.kyogijo .ky-pg-light{fill:var(--ky-pg-light,#f7f4ea)}
|
|
17
|
+
.kyogijo .ky-pg-pill{fill:var(--ky-plate,rgba(18,24,21,.9))}
|
|
18
|
+
.kyogijo .ky-pg-pilltext{fill:var(--ky-plate-ink,#fff);text-anchor:middle}
|
|
19
|
+
@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .kyogijo:not([data-theme=light])){--ky-pg-border:#050709;--ky-pg-edge:rgba(255,255,255,.42);--ky-pg-seam:rgba(0,0,0,.6);--ky-pg-shadow:rgba(0,0,0,.45);--ky-pg-disc:#07090b;--ky-pg-light:#e9e5d6}}
|
|
20
|
+
:where(html[data-theme=dark] .kyogijo,.kyogijo[data-theme=dark]){--ky-pg-border:#050709;--ky-pg-edge:rgba(255,255,255,.42);--ky-pg-seam:rgba(0,0,0,.6);--ky-pg-shadow:rgba(0,0,0,.45);--ky-pg-disc:#07090b;--ky-pg-light:#e9e5d6}`.trim();
|
|
21
|
+
/**
|
|
22
|
+
* The entrance (`animate: "unfurl"` or `"fade"`), in CSS only, and only for a reader who has not asked for less motion: a carpet rolls out from its own end, a stage and the emblem
|
|
23
|
+
* grow, and `fade` brings each in. The end state is the drawing's own, so a reader who has asked for less sees it at once, and a renderer that reads no CSS animation (the canvas,
|
|
24
|
+
* the PNG) draws it finished. The second team's picture and then the emblem come a little later.
|
|
25
|
+
*/
|
|
26
|
+
export const KYOGIJO_PAGEANT_MOTION = `
|
|
27
|
+
@media (prefers-reduced-motion:no-preference){
|
|
28
|
+
.kyogijo .ky-pg-fade .ky-pg-art{animation:ky-pg-fade .8s ease-out both}
|
|
29
|
+
.kyogijo .ky-pg-unfurl .ky-pg-art{transform-box:fill-box;animation:ky-pg-unfurl-x .95s cubic-bezier(.22,.8,.28,1) both}
|
|
30
|
+
.kyogijo .ky-pg-unfurl.ky-pg-left .ky-pg-art{transform-origin:0 50%}
|
|
31
|
+
.kyogijo .ky-pg-unfurl.ky-pg-right .ky-pg-art{transform-origin:100% 50%}
|
|
32
|
+
.kyogijo .ky-pg-unfurl.ky-pg-tall .ky-pg-art{animation-name:ky-pg-unfurl-y}
|
|
33
|
+
.kyogijo .ky-pg-unfurl.ky-pg-tall.ky-pg-left .ky-pg-art{transform-origin:50% 0}
|
|
34
|
+
.kyogijo .ky-pg-unfurl.ky-pg-tall.ky-pg-right .ky-pg-art{transform-origin:50% 100%}
|
|
35
|
+
.kyogijo .ky-pg-unfurl.ky-pg-stage .ky-pg-art,.kyogijo .ky-pg-unfurl.ky-pg-emblem .ky-pg-art{animation-name:ky-pg-grow;transform-origin:50% 50%}
|
|
36
|
+
.kyogijo .ky-pg-right .ky-pg-art{animation-delay:.16s}
|
|
37
|
+
.kyogijo .ky-pg-emblem .ky-pg-art{animation-delay:.34s}
|
|
38
|
+
@keyframes ky-pg-fade{from{opacity:0}to{opacity:1}}
|
|
39
|
+
@keyframes ky-pg-unfurl-x{from{transform:scaleX(.02);opacity:.3}to{transform:none;opacity:1}}
|
|
40
|
+
@keyframes ky-pg-unfurl-y{from{transform:scaleY(.02);opacity:.3}to{transform:none;opacity:1}}
|
|
41
|
+
@keyframes ky-pg-grow{from{transform:scale(.55);opacity:0}to{transform:none;opacity:1}}
|
|
42
|
+
}`.trim();
|
package/dist/png.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SceneOptions, SceneSource } from "./scene.types.ts";
|
|
1
|
+
import type { Scene, SceneItem, SceneOptions, SceneSource } from "./scene.types.ts";
|
|
2
2
|
/** What `toPNG` takes besides the drawing. */
|
|
3
3
|
export interface PngOptions extends SceneOptions {
|
|
4
4
|
/** The width of the picture in pixels. 1200 when neither a width nor a height is given. */
|
|
@@ -9,6 +9,13 @@ export interface PngOptions extends SceneOptions {
|
|
|
9
9
|
/** Options for resvg's font handling, passed as its `font` option (`{ fontFiles: [...], loadSystemFonts: false }`). */
|
|
10
10
|
font?: Record<string, unknown>;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* A shape wholly outside the drawing's frame, stroke included. resvg (the PNG renderer) stops the whole process, with a panic in its geometry code, on a shape with an opacity below 1 that lies wholly outside the
|
|
14
|
+
* viewBox (a goal's net beyond a soccer pitch's penalty area, seen through a range), and such a shape paints nothing, so it is left out of the picture. Text and images are kept.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isOutside(item: SceneItem, view: Scene["viewBox"]): boolean;
|
|
17
|
+
/** The scene without the shapes that lie wholly outside its frame. */
|
|
18
|
+
export declare function cullOutside(scene: Scene): Scene;
|
|
12
19
|
/** The message of the error thrown when the peer is not there. */
|
|
13
20
|
export declare const PNG_PEER_MISSING = "toPNG needs the optional package @resvg/resvg-js: install it with `npm install @resvg/resvg-js`. It runs in Node only; in a browser draw to a canvas with drawToCanvas and call canvas.toBlob.";
|
|
14
21
|
/**
|
package/dist/png.js
CHANGED
|
@@ -9,6 +9,26 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
// A drawing as a PNG, in Node, through resvg (the optional peer `@resvg/resvg-js`). The SVG is first written without a stylesheet (see `sceneToSvg`),
|
|
10
10
|
// because resvg does not read CSS variables. In a browser, draw to a canvas (`drawToCanvas`) and ask it for a PNG instead.
|
|
11
11
|
import { sceneToSvg } from "./scene.flat.js";
|
|
12
|
+
import { boundsOf } from "./scene.path.js";
|
|
13
|
+
import { sceneOf } from "./scene.js";
|
|
14
|
+
/**
|
|
15
|
+
* A shape wholly outside the drawing's frame, stroke included. resvg (the PNG renderer) stops the whole process, with a panic in its geometry code, on a shape with an opacity below 1 that lies wholly outside the
|
|
16
|
+
* viewBox (a goal's net beyond a soccer pitch's penalty area, seen through a range), and such a shape paints nothing, so it is left out of the picture. Text and images are kept.
|
|
17
|
+
*/
|
|
18
|
+
export function isOutside(item, view) {
|
|
19
|
+
if (item.kind === "text" || item.kind === "image")
|
|
20
|
+
return false;
|
|
21
|
+
const box = boundsOf(item, item.matrix);
|
|
22
|
+
if (box === undefined)
|
|
23
|
+
return false;
|
|
24
|
+
const [a, b, c, d] = item.matrix;
|
|
25
|
+
const pad = (item.style.stroke === "none" ? 0 : item.style.strokeWidth * Math.sqrt(Math.abs(a * d - b * c)) * 1.5) + 0.5;
|
|
26
|
+
return box.x + box.w + pad < view.x || box.x - pad > view.x + view.w || box.y + box.h + pad < view.y || box.y - pad > view.y + view.h;
|
|
27
|
+
}
|
|
28
|
+
/** The scene without the shapes that lie wholly outside its frame. */
|
|
29
|
+
export function cullOutside(scene) {
|
|
30
|
+
return { ...scene, items: scene.items.filter((item) => !isOutside(item, scene.viewBox)) };
|
|
31
|
+
}
|
|
12
32
|
/** The message of the error thrown when the peer is not there. */
|
|
13
33
|
export const PNG_PEER_MISSING = "toPNG needs the optional package @resvg/resvg-js: install it with `npm install @resvg/resvg-js`. It runs in Node only; in a browser draw to a canvas with drawToCanvas and call canvas.toBlob.";
|
|
14
34
|
/**
|
|
@@ -36,7 +56,7 @@ export async function toPNG(source, options = {}) {
|
|
|
36
56
|
throw new Error(PNG_PEER_MISSING);
|
|
37
57
|
}
|
|
38
58
|
const wide = options.width === undefined && options.height === undefined ? 1200 : options.width;
|
|
39
|
-
const svg = sceneToSvg(source, { theme: options.theme, css: options.css });
|
|
59
|
+
const svg = sceneToSvg(cullOutside(sceneOf(source, { theme: options.theme, css: options.css })), { theme: options.theme, css: options.css });
|
|
40
60
|
const resvg = new mod.Resvg(svg, {
|
|
41
61
|
fitTo: wide !== undefined ? { mode: "width", value: wide } : { mode: "height", value: options.height },
|
|
42
62
|
...(options.background === undefined ? {} : { background: options.background }),
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { type Attacking, type Frame } from "./frames.ts";
|
|
2
|
+
import { type PlaceLike, type ZoneOptions } from "./rules.ts";
|
|
3
|
+
import { type Unit } from "./units.ts";
|
|
4
|
+
import type { Language } from "./types.ts";
|
|
5
|
+
/** The six numbers of a matrix, as an SVG's `DOMMatrix` has them. */
|
|
6
|
+
export interface MatrixLike {
|
|
7
|
+
a: number;
|
|
8
|
+
b: number;
|
|
9
|
+
c: number;
|
|
10
|
+
d: number;
|
|
11
|
+
e: number;
|
|
12
|
+
f: number;
|
|
13
|
+
}
|
|
14
|
+
/** What the functions need of an element of the drawing: its attributes, and for the surface group its matrix to the page. */
|
|
15
|
+
export interface ElementLike {
|
|
16
|
+
getAttribute(name: string): string | null;
|
|
17
|
+
getScreenCTM?(): MatrixLike | null;
|
|
18
|
+
}
|
|
19
|
+
/** What the functions need of the drawing's `<svg>`: to find the surface group in it. A real `SVGSVGElement` is one. */
|
|
20
|
+
export interface SvgLike extends ElementLike {
|
|
21
|
+
querySelector(selectors: string): unknown;
|
|
22
|
+
closest?(selectors: string): unknown;
|
|
23
|
+
}
|
|
24
|
+
/** What the pointer functions take besides the position. */
|
|
25
|
+
export interface PointerOptions {
|
|
26
|
+
/** The frame the answer is written in: the name of a frame of the drawing's sport (`nhl-api`, `opta`, `statsbomb`, `mlbam`), a frame, or `"surface"` for the drawing's own numbers (feet, or `units`). `"surface"` when missing. */
|
|
27
|
+
provider?: Frame | string;
|
|
28
|
+
/** The unit of the `"surface"` numbers: feet when missing. */
|
|
29
|
+
units?: Unit;
|
|
30
|
+
/** The end the provider's team attacks, as in `toSurface`: `left` says the provider's numbers are of a team that attacks the left end of the surface as it is drawn wide. */
|
|
31
|
+
attacking?: Attacking;
|
|
32
|
+
/** The language of the zone names. */
|
|
33
|
+
language?: Language;
|
|
34
|
+
/** The size of the surface when the frame is a variable one, in place of the drawing's own. */
|
|
35
|
+
size?: ZoneOptions["size"];
|
|
36
|
+
}
|
|
37
|
+
/** Where the pointer is on the field. */
|
|
38
|
+
export interface FieldPoint {
|
|
39
|
+
/** The place in the provider's frame (feet in the drawing's wide frame for `"surface"`). */
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
/** The frame's name. */
|
|
43
|
+
provider: string;
|
|
44
|
+
/** The place on the surface: feet in the drawing's wide frame (x along the surface from the left, y across it from the top). */
|
|
45
|
+
surface: {
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
};
|
|
49
|
+
/** True when the pointer is on the surface, or on its edge: not on the apron, the stands or the page round it. */
|
|
50
|
+
inBounds: boolean;
|
|
51
|
+
/** The zone and the finer area, by id (`zoneAt` in the zones entry): `undefined` off the surface. */
|
|
52
|
+
zone: string | undefined;
|
|
53
|
+
subzone: string | undefined;
|
|
54
|
+
/** Every id that holds, with its name. */
|
|
55
|
+
zones: string[];
|
|
56
|
+
names: Record<string, string>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Where a page position is on the field of a drawing, in a provider's numbers, with the zone the rules put it in. `svg` is the drawing's `<svg>` element, and
|
|
60
|
+
* the position is a pointer event's `clientX` and `clientY`. It holds whatever the drawing is: wide or tall, any size or padding, a range, a cropped viewBox. It answers `null` for
|
|
61
|
+
* a drawing with no surface to ask (a badge, a special game, or the 3D view, whose floor is laid in CSS 3D). `provider` names a frame of the drawing's sport (`nhl-api`,
|
|
62
|
+
* `opta`, `statsbomb`, `mlbam`...) and a frame of another sport is an error; without one, the numbers are the surface's own in feet.
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts no-run
|
|
65
|
+
* import { clientPointToField } from "@johnmorrisdotca/kyogijo-stadiums/pointer";
|
|
66
|
+
* const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!;
|
|
67
|
+
* svg.addEventListener("click", (event) => {
|
|
68
|
+
* const at = clientPointToField(svg, event.clientX, event.clientY, { provider: "nhl-api" });
|
|
69
|
+
* if (at !== null) console.log(at.x, at.y, at.zone, at.inBounds);
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function clientPointToField(svg: SvgLike | Element, clientX: number, clientY: number, options?: PointerOptions): FieldPoint | null;
|
|
74
|
+
/**
|
|
75
|
+
* The page position of a place on the field: the undoing of `clientPointToField`. The place is in a provider's numbers (or the surface's own, in feet or `units`); the answer is
|
|
76
|
+
* `clientX` and `clientY`, to put a marker over the drawing with. `null` for a drawing with no surface to ask.
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts no-run
|
|
79
|
+
* import { fieldPointToClient } from "@johnmorrisdotca/kyogijo-stadiums/pointer";
|
|
80
|
+
* const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!;
|
|
81
|
+
* const at = fieldPointToClient(svg, [69, 22], { provider: "nhl-api" });
|
|
82
|
+
* declare const marker: HTMLElement;
|
|
83
|
+
* if (at !== null) marker.style.transform = `translate(${at.x}px, ${at.y}px)`;
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare function fieldPointToClient(svg: SvgLike | Element, point: PlaceLike, options?: PointerOptions): {
|
|
87
|
+
x: number;
|
|
88
|
+
y: number;
|
|
89
|
+
} | null;
|
|
90
|
+
/**
|
|
91
|
+
* The same page position in each of several frames at once, with the zone: what a readout that shows a place in every provider asks (the demo's). `providers` defaults to every
|
|
92
|
+
* frame of the drawing's sport.
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts no-run
|
|
95
|
+
* import { clientPointToFrames } from "@johnmorrisdotca/kyogijo-stadiums/pointer";
|
|
96
|
+
* const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!;
|
|
97
|
+
* svg.addEventListener("pointermove", (event) => {
|
|
98
|
+
* const all = clientPointToFrames(svg, event.clientX, event.clientY);
|
|
99
|
+
* console.log(all?.points.map((p) => `${p.provider}: ${p.x}, ${p.y}`), all?.zone);
|
|
100
|
+
* });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export declare function clientPointToFrames(svg: SvgLike | Element, clientX: number, clientY: number, options?: Omit<PointerOptions, "provider"> & {
|
|
104
|
+
providers?: readonly (Frame | string)[];
|
|
105
|
+
}): (Omit<FieldPoint, "x" | "y" | "provider"> & {
|
|
106
|
+
points: {
|
|
107
|
+
provider: string;
|
|
108
|
+
title: string;
|
|
109
|
+
unit: string;
|
|
110
|
+
x: number;
|
|
111
|
+
y: number;
|
|
112
|
+
}[];
|
|
113
|
+
}) | null;
|
package/dist/pointer.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// A click on a drawing, as a place on the field. `clientPointToField(svg, clientX, clientY, { provider })` asks the drawing where the pointer is: the browser's own
|
|
2
|
+
// matrix for the surface group (`getScreenCTM`) carries a page coordinate back through the CSS size, the viewBox and its padding, the preserved aspect ratio, a
|
|
3
|
+
// tall drawing's quarter turn and a range's crop, to the surface's wide frame in feet; the frame of the provider (frames.ts) turns that into the provider's numbers,
|
|
4
|
+
// and the rules engine (rules.ts) names the zone. `fieldPointToClient` is the way back. A drawing writes the box of its surface and the options that decide its rules
|
|
5
|
+
// on its surface group (field.data.ts), so the answer needs nothing but the SVG.
|
|
6
|
+
import { FRAMES, frameByName, fromSurface, toSurface } from "./frames.js";
|
|
7
|
+
import { frameSizeOf } from "./outline.js";
|
|
8
|
+
import { readField } from "./field.data.js";
|
|
9
|
+
import { zoneAtSurface } from "./rules.js";
|
|
10
|
+
import { FEET_PER, isUnit } from "./units.js";
|
|
11
|
+
const invert = (m) => {
|
|
12
|
+
const det = m.a * m.d - m.b * m.c;
|
|
13
|
+
if (!Number.isFinite(det) || Math.abs(det) < 1e-12)
|
|
14
|
+
return undefined;
|
|
15
|
+
return { a: m.d / det, b: -m.b / det, c: -m.c / det, d: m.a / det, e: (m.c * m.f - m.d * m.e) / det, f: (m.b * m.e - m.a * m.f) / det };
|
|
16
|
+
};
|
|
17
|
+
const apply = (m, x, y) => [m.a * x + m.c * y + m.e, m.b * x + m.d * y + m.f];
|
|
18
|
+
/** The surface group of a drawing and what it says of its surface, or `undefined` for one the question does not apply to: a badge, a special game, a 3D view. */
|
|
19
|
+
function find(svg) {
|
|
20
|
+
const root = svg;
|
|
21
|
+
// The 3D view lays the floor in CSS 3D, where the browser's matrix for an SVG element is the flat drawing's: it would answer for a place that is not under the pointer.
|
|
22
|
+
if (typeof root.closest === "function" && root.closest(".ky-turn") !== null)
|
|
23
|
+
return undefined;
|
|
24
|
+
const group = root.querySelector(".ky-surface-group");
|
|
25
|
+
if (group === null || typeof group.getAttribute !== "function")
|
|
26
|
+
return undefined;
|
|
27
|
+
const field = readField((name) => group.getAttribute(name));
|
|
28
|
+
const sport = root.getAttribute("data-sport");
|
|
29
|
+
if (field === undefined || sport === null)
|
|
30
|
+
return undefined;
|
|
31
|
+
return { sport: sport, league: root.getAttribute("data-league") ?? undefined, box: field.box, rules: field.rules, group };
|
|
32
|
+
}
|
|
33
|
+
const unitOf = (units) => (units !== undefined && isUnit(units) ? units : "ft");
|
|
34
|
+
function frameOf(found, provider) {
|
|
35
|
+
if (provider === undefined || provider === "surface")
|
|
36
|
+
return undefined;
|
|
37
|
+
const frame = typeof provider === "string" ? frameByName(provider) : provider;
|
|
38
|
+
if (frame.sport !== found.sport)
|
|
39
|
+
throw new RangeError(`The frame "${frame.name}" is for ${frame.sport}, and this drawing is of ${found.sport}: use one of ${Object.values(FRAMES).filter((f) => f.sport === found.sport).map((f) => f.name).join(", ") || "surface"}`);
|
|
40
|
+
return frame;
|
|
41
|
+
}
|
|
42
|
+
const sizeOf = (found, options) => {
|
|
43
|
+
if (options.size !== undefined)
|
|
44
|
+
return options.size;
|
|
45
|
+
return frameSizeOf({ sport: found.sport, league: found.league }, { w: found.box.w, h: found.box.h });
|
|
46
|
+
};
|
|
47
|
+
/** The place on the surface, in feet, under a page position; `undefined` where the drawing has no matrix to ask (not in the page, or hidden). */
|
|
48
|
+
function toLocal(found, clientX, clientY) {
|
|
49
|
+
const matrix = found.group.getScreenCTM?.();
|
|
50
|
+
if (matrix === null || matrix === undefined)
|
|
51
|
+
return undefined;
|
|
52
|
+
const back = invert(matrix);
|
|
53
|
+
return back === undefined ? undefined : apply(back, clientX, clientY);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Where a page position is on the field of a drawing, in a provider's numbers, with the zone the rules put it in. `svg` is the drawing's `<svg>` element, and
|
|
57
|
+
* the position is a pointer event's `clientX` and `clientY`. It holds whatever the drawing is: wide or tall, any size or padding, a range, a cropped viewBox. It answers `null` for
|
|
58
|
+
* a drawing with no surface to ask (a badge, a special game, or the 3D view, whose floor is laid in CSS 3D). `provider` names a frame of the drawing's sport (`nhl-api`,
|
|
59
|
+
* `opta`, `statsbomb`, `mlbam`...) and a frame of another sport is an error; without one, the numbers are the surface's own in feet.
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts no-run
|
|
62
|
+
* import { clientPointToField } from "@johnmorrisdotca/kyogijo-stadiums/pointer";
|
|
63
|
+
* const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!;
|
|
64
|
+
* svg.addEventListener("click", (event) => {
|
|
65
|
+
* const at = clientPointToField(svg, event.clientX, event.clientY, { provider: "nhl-api" });
|
|
66
|
+
* if (at !== null) console.log(at.x, at.y, at.zone, at.inBounds);
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function clientPointToField(svg, clientX, clientY, options = {}) {
|
|
71
|
+
if (!Number.isFinite(clientX) || !Number.isFinite(clientY))
|
|
72
|
+
throw new TypeError("clientX and clientY are numbers");
|
|
73
|
+
const found = find(svg);
|
|
74
|
+
if (found === undefined)
|
|
75
|
+
return null;
|
|
76
|
+
const local = toLocal(found, clientX, clientY);
|
|
77
|
+
if (local === undefined)
|
|
78
|
+
return null;
|
|
79
|
+
const frame = frameOf(found, options.provider);
|
|
80
|
+
const unit = unitOf(options.units);
|
|
81
|
+
let x;
|
|
82
|
+
let y;
|
|
83
|
+
if (frame === undefined)
|
|
84
|
+
[x, y] = [local[0] / FEET_PER[unit], local[1] / FEET_PER[unit]];
|
|
85
|
+
else
|
|
86
|
+
[x, y] = fromSurface([local], { to: frame, size: sizeOf(found, options), attacking: options.attacking })[0];
|
|
87
|
+
const rules = { ...found.rules, league: found.league ?? found.rules.league, language: options.language };
|
|
88
|
+
const zone = zoneAtSurface(found.sport, local, rules);
|
|
89
|
+
return { x, y, provider: frame?.name ?? "surface", surface: { x: local[0], y: local[1] }, inBounds: zone.inBounds, zone: zone.zone, subzone: zone.subzone, zones: zone.zones, names: zone.names };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The page position of a place on the field: the undoing of `clientPointToField`. The place is in a provider's numbers (or the surface's own, in feet or `units`); the answer is
|
|
93
|
+
* `clientX` and `clientY`, to put a marker over the drawing with. `null` for a drawing with no surface to ask.
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts no-run
|
|
96
|
+
* import { fieldPointToClient } from "@johnmorrisdotca/kyogijo-stadiums/pointer";
|
|
97
|
+
* const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!;
|
|
98
|
+
* const at = fieldPointToClient(svg, [69, 22], { provider: "nhl-api" });
|
|
99
|
+
* declare const marker: HTMLElement;
|
|
100
|
+
* if (at !== null) marker.style.transform = `translate(${at.x}px, ${at.y}px)`;
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export function fieldPointToClient(svg, point, options = {}) {
|
|
104
|
+
const found = find(svg);
|
|
105
|
+
if (found === undefined)
|
|
106
|
+
return null;
|
|
107
|
+
const matrix = found.group.getScreenCTM?.();
|
|
108
|
+
if (matrix === null || matrix === undefined)
|
|
109
|
+
return null;
|
|
110
|
+
const xy = Array.isArray(point) ? point : [point.x, point.y];
|
|
111
|
+
const [px, py] = xy;
|
|
112
|
+
if (typeof px !== "number" || typeof py !== "number" || !Number.isFinite(px) || !Number.isFinite(py))
|
|
113
|
+
throw new TypeError("a point is [x, y] or { x, y }, two finite numbers");
|
|
114
|
+
const frame = frameOf(found, options.provider);
|
|
115
|
+
const unit = unitOf(options.units);
|
|
116
|
+
const local = frame === undefined ? [px * FEET_PER[unit], py * FEET_PER[unit]] : toSurface([[px, py]], { from: frame, size: sizeOf(found, options), attacking: options.attacking })[0];
|
|
117
|
+
const [x, y] = apply(matrix, local[0], local[1]);
|
|
118
|
+
return { x, y };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The same page position in each of several frames at once, with the zone: what a readout that shows a place in every provider asks (the demo's). `providers` defaults to every
|
|
122
|
+
* frame of the drawing's sport.
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts no-run
|
|
125
|
+
* import { clientPointToFrames } from "@johnmorrisdotca/kyogijo-stadiums/pointer";
|
|
126
|
+
* const svg = document.querySelector<SVGSVGElement>("svg.kyogijo")!;
|
|
127
|
+
* svg.addEventListener("pointermove", (event) => {
|
|
128
|
+
* const all = clientPointToFrames(svg, event.clientX, event.clientY);
|
|
129
|
+
* console.log(all?.points.map((p) => `${p.provider}: ${p.x}, ${p.y}`), all?.zone);
|
|
130
|
+
* });
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export function clientPointToFrames(svg, clientX, clientY, options = {}) {
|
|
134
|
+
const found = find(svg);
|
|
135
|
+
if (found === undefined)
|
|
136
|
+
return null;
|
|
137
|
+
const first = clientPointToField(svg, clientX, clientY, { ...options, provider: "surface" });
|
|
138
|
+
if (first === null)
|
|
139
|
+
return null;
|
|
140
|
+
const list = options.providers ?? Object.values(FRAMES).filter((frame) => frame.sport === found.sport);
|
|
141
|
+
const points = list.map((entry) => {
|
|
142
|
+
const frame = typeof entry === "string" ? frameByName(entry) : entry;
|
|
143
|
+
const [x, y] = fromSurface([[first.surface.x, first.surface.y]], { to: frame, size: sizeOf(found, options), attacking: options.attacking })[0];
|
|
144
|
+
return { provider: frame.name, title: frame.title, unit: frame.unit, x, y };
|
|
145
|
+
});
|
|
146
|
+
const { x: _x, y: _y, provider: _provider, ...rest } = first;
|
|
147
|
+
void _x;
|
|
148
|
+
void _y;
|
|
149
|
+
void _provider;
|
|
150
|
+
return { ...rest, points };
|
|
151
|
+
}
|