@johnmorrisdotca/kyogijo-stadiums 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/NOTICE.md +4 -0
  3. package/README.md +71 -16
  4. package/dist/annotate.js +1 -1
  5. package/dist/athletics/describe.d.ts +8 -0
  6. package/dist/athletics/describe.js +41 -0
  7. package/dist/athletics/events.d.ts +17 -0
  8. package/dist/athletics/events.js +48 -0
  9. package/dist/athletics/facilities.d.ts +80 -0
  10. package/dist/athletics/facilities.js +176 -0
  11. package/dist/athletics/infield.d.ts +79 -0
  12. package/dist/athletics/infield.js +166 -0
  13. package/dist/athletics/layout.d.ts +13 -0
  14. package/dist/athletics/layout.js +98 -0
  15. package/dist/athletics/oval.d.ts +57 -0
  16. package/dist/athletics/oval.js +116 -0
  17. package/dist/athletics/plan.d.ts +97 -0
  18. package/dist/athletics/plan.js +355 -0
  19. package/dist/athletics/races.d.ts +102 -0
  20. package/dist/athletics/races.js +355 -0
  21. package/dist/athletics/steeplechase.d.ts +72 -0
  22. package/dist/athletics/steeplechase.js +196 -0
  23. package/dist/athletics/stencil.d.ts +13 -0
  24. package/dist/athletics/stencil.js +85 -0
  25. package/dist/athletics/track.d.ts +54 -0
  26. package/dist/athletics/track.js +105 -0
  27. package/dist/athletics/types.d.ts +77 -0
  28. package/dist/athletics/types.js +1 -0
  29. package/dist/athletics-entry.d.ts +17 -0
  30. package/dist/athletics-entry.js +11 -0
  31. package/dist/athletics.style.d.ts +16 -0
  32. package/dist/athletics.style.js +51 -0
  33. package/dist/atlas-entry.d.ts +4 -2
  34. package/dist/atlas-entry.js +3 -2
  35. package/dist/atlas.d.ts +83 -7
  36. package/dist/atlas.js +108 -8
  37. package/dist/charts-entry.d.ts +6 -1
  38. package/dist/charts-entry.js +3 -0
  39. package/dist/charts.athletics.d.ts +183 -0
  40. package/dist/charts.athletics.js +455 -0
  41. package/dist/charts.curling.heat.d.ts +24 -0
  42. package/dist/charts.curling.heat.js +116 -0
  43. package/dist/charts.curling.js +14 -3
  44. package/dist/charts.pool.d.ts +67 -0
  45. package/dist/charts.pool.js +76 -0
  46. package/dist/charts.skating.d.ts +204 -0
  47. package/dist/charts.skating.js +426 -0
  48. package/dist/charts.types.d.ts +53 -1
  49. package/dist/court.d.ts +85 -2
  50. package/dist/court.js +102 -7
  51. package/dist/court.more.d.ts +46 -0
  52. package/dist/court.more.js +219 -0
  53. package/dist/courts.style.d.ts +14 -2
  54. package/dist/courts.style.js +51 -4
  55. package/dist/crowd.d.ts +154 -0
  56. package/dist/crowd.draw.d.ts +17 -0
  57. package/dist/crowd.draw.js +198 -0
  58. package/dist/crowd.js +465 -0
  59. package/dist/crowd.park.d.ts +19 -0
  60. package/dist/crowd.park.js +153 -0
  61. package/dist/curling.stats.d.ts +271 -0
  62. package/dist/curling.stats.js +301 -0
  63. package/dist/draw.d.ts +16 -0
  64. package/dist/draw.js +318 -35
  65. package/dist/element-entry.d.ts +1 -1
  66. package/dist/element-entry.js +1 -1
  67. package/dist/element.d.ts +13 -2
  68. package/dist/element.js +77 -3
  69. package/dist/features.js +131 -0
  70. package/dist/fields.kit.d.ts +2 -2
  71. package/dist/fields.types.d.ts +18 -4
  72. package/dist/frames.providers.js +63 -8
  73. package/dist/geojson.js +3 -0
  74. package/dist/geometry-entry.d.ts +2 -1
  75. package/dist/geometry-entry.js +2 -1
  76. package/dist/geometry.d.ts +20 -0
  77. package/dist/geometry.js +78 -1
  78. package/dist/grounds.d.ts +4 -0
  79. package/dist/grounds.data.js +12 -0
  80. package/dist/hardwood.d.ts +31 -0
  81. package/dist/hardwood.js +157 -0
  82. package/dist/heat.js +4 -3
  83. package/dist/heat.types.d.ts +5 -0
  84. package/dist/heat.zones.d.ts +2 -2
  85. package/dist/heat.zones.js +154 -6
  86. package/dist/homeside.js +17 -0
  87. package/dist/index.d.ts +16 -2
  88. package/dist/index.js +10 -1
  89. package/dist/layers.d.ts +1 -1
  90. package/dist/layers.env.d.ts +3 -1
  91. package/dist/layers.env.js +4 -2
  92. package/dist/layers.js +4 -4
  93. package/dist/leagues/athletics.d.ts +63 -0
  94. package/dist/leagues/athletics.js +87 -0
  95. package/dist/leagues/courts.d.ts +269 -4
  96. package/dist/leagues/courts.js +155 -1
  97. package/dist/leagues/skating.d.ts +145 -0
  98. package/dist/leagues/skating.js +73 -0
  99. package/dist/leagues/sources.js +20 -0
  100. package/dist/leagues/types.d.ts +1 -1
  101. package/dist/leagues-entry.d.ts +4 -2
  102. package/dist/leagues-entry.js +2 -1
  103. package/dist/leagues.d.ts +14 -4
  104. package/dist/leagues.js +146 -5
  105. package/dist/look.d.ts +3 -3
  106. package/dist/look.js +39 -6
  107. package/dist/outline.d.ts +1 -1
  108. package/dist/outline.js +30 -2
  109. package/dist/pageant.d.ts +120 -0
  110. package/dist/pageant.draw.d.ts +21 -0
  111. package/dist/pageant.draw.js +171 -0
  112. package/dist/pageant.js +207 -0
  113. package/dist/pageant.style.d.ts +8 -0
  114. package/dist/pageant.style.js +42 -0
  115. package/dist/png.d.ts +8 -1
  116. package/dist/png.js +21 -1
  117. package/dist/pool.d.ts +176 -0
  118. package/dist/pool.figures.d.ts +304 -0
  119. package/dist/pool.figures.js +144 -0
  120. package/dist/pool.js +304 -0
  121. package/dist/pool.kit.d.ts +48 -0
  122. package/dist/pool.kit.js +141 -0
  123. package/dist/pool.labels.d.ts +20 -0
  124. package/dist/pool.labels.js +257 -0
  125. package/dist/pool.race.d.ts +65 -0
  126. package/dist/pool.race.js +140 -0
  127. package/dist/pool.replay.d.ts +46 -0
  128. package/dist/pool.replay.js +127 -0
  129. package/dist/pool.section.d.ts +13 -0
  130. package/dist/pool.section.js +83 -0
  131. package/dist/pool.style.d.ts +20 -0
  132. package/dist/pool.style.js +169 -0
  133. package/dist/pool.types.d.ts +145 -0
  134. package/dist/pool.types.js +3 -0
  135. package/dist/pool.words.d.ts +13 -0
  136. package/dist/pool.words.js +101 -0
  137. package/dist/ranges.names.js +47 -3
  138. package/dist/replay-entry.d.ts +3 -0
  139. package/dist/replay-entry.js +2 -0
  140. package/dist/restyle.js +1 -1
  141. package/dist/score.js +70 -5
  142. package/dist/sheet.d.ts +3 -0
  143. package/dist/sheet.js +7 -3
  144. package/dist/skating/describe.d.ts +7 -0
  145. package/dist/skating/describe.js +44 -0
  146. package/dist/skating/events.d.ts +24 -0
  147. package/dist/skating/events.js +47 -0
  148. package/dist/skating/figure.d.ts +24 -0
  149. package/dist/skating/figure.js +37 -0
  150. package/dist/skating/long.d.ts +54 -0
  151. package/dist/skating/long.js +111 -0
  152. package/dist/skating/parts.d.ts +43 -0
  153. package/dist/skating/parts.js +40 -0
  154. package/dist/skating/plan.d.ts +55 -0
  155. package/dist/skating/plan.js +197 -0
  156. package/dist/skating/short.d.ts +47 -0
  157. package/dist/skating/short.js +89 -0
  158. package/dist/skating/track.d.ts +66 -0
  159. package/dist/skating/track.js +94 -0
  160. package/dist/skating/types.d.ts +57 -0
  161. package/dist/skating/types.js +3 -0
  162. package/dist/skating-entry.d.ts +17 -0
  163. package/dist/skating-entry.js +11 -0
  164. package/dist/skating.style.d.ts +4 -0
  165. package/dist/skating.style.js +24 -0
  166. package/dist/strings.d.ts +296 -0
  167. package/dist/strings.js +328 -11
  168. package/dist/style.d.ts +2 -0
  169. package/dist/style.js +11 -1
  170. package/dist/surfaces/artistic.d.ts +9 -0
  171. package/dist/surfaces/artistic.js +94 -0
  172. package/dist/surfaces/athletics.d.ts +16 -0
  173. package/dist/surfaces/athletics.js +132 -0
  174. package/dist/surfaces/baseball.js +15 -0
  175. package/dist/surfaces/basketball.floor.d.ts +27 -0
  176. package/dist/surfaces/basketball.floor.js +83 -0
  177. package/dist/surfaces/basketball.gear.d.ts +27 -0
  178. package/dist/surfaces/basketball.gear.js +90 -0
  179. package/dist/surfaces/basketball.js +43 -21
  180. package/dist/surfaces/curling.d.ts +16 -0
  181. package/dist/surfaces/curling.fold.d.ts +67 -0
  182. package/dist/surfaces/curling.fold.js +107 -0
  183. package/dist/surfaces/curling.js +37 -3
  184. package/dist/surfaces/dive.d.ts +9 -0
  185. package/dist/surfaces/dive.js +128 -0
  186. package/dist/surfaces/field.d.ts +7 -2
  187. package/dist/surfaces/field.js +15 -9
  188. package/dist/surfaces/hockey.js +1 -0
  189. package/dist/surfaces/net.d.ts +2 -1
  190. package/dist/surfaces/net.js +160 -15
  191. package/dist/surfaces/polo.d.ts +12 -0
  192. package/dist/surfaces/polo.js +159 -0
  193. package/dist/surfaces/pool.d.ts +27 -0
  194. package/dist/surfaces/pool.js +198 -0
  195. package/dist/surfaces/skating.d.ts +6 -0
  196. package/dist/surfaces/skating.js +62 -0
  197. package/dist/surfaces/soccer.js +2 -1
  198. package/dist/surfaces/surface.d.ts +44 -0
  199. package/dist/surfaces/venue.d.ts +15 -0
  200. package/dist/surfaces/venue.js +95 -0
  201. package/dist/tenants.d.ts +77 -0
  202. package/dist/tenants.data.d.ts +8 -0
  203. package/dist/tenants.data.js +945 -0
  204. package/dist/tenants.js +46 -0
  205. package/dist/turn-entry.d.ts +12 -0
  206. package/dist/turn-entry.js +7 -0
  207. package/dist/turn.camera.d.ts +105 -0
  208. package/dist/turn.camera.js +168 -0
  209. package/dist/turn.dom.d.ts +40 -0
  210. package/dist/turn.dom.js +351 -0
  211. package/dist/turn.kit.d.ts +86 -0
  212. package/dist/turn.kit.js +110 -0
  213. package/dist/turn.markup.d.ts +48 -0
  214. package/dist/turn.markup.js +108 -0
  215. package/dist/turn.model.d.ts +78 -0
  216. package/dist/turn.model.js +91 -0
  217. package/dist/turn.style.d.ts +5 -0
  218. package/dist/turn.style.js +55 -0
  219. package/dist/types.d.ts +183 -18
  220. package/dist/units.d.ts +1 -1
  221. package/dist/units.js +15 -1
  222. package/dist/uprights.board.d.ts +17 -0
  223. package/dist/uprights.board.js +59 -0
  224. package/dist/uprights.d.ts +37 -0
  225. package/dist/uprights.fields.d.ts +32 -0
  226. package/dist/uprights.fields.js +172 -0
  227. package/dist/uprights.goal.d.ts +49 -0
  228. package/dist/uprights.goal.js +69 -0
  229. package/dist/uprights.js +160 -0
  230. package/dist/uprights.more.d.ts +7 -0
  231. package/dist/uprights.more.js +183 -0
  232. package/dist/uprights.net.d.ts +12 -0
  233. package/dist/uprights.net.js +109 -0
  234. package/dist/uprights.netkit.d.ts +28 -0
  235. package/dist/uprights.netkit.js +67 -0
  236. package/dist/uprights.pools.d.ts +8 -0
  237. package/dist/uprights.pools.js +190 -0
  238. package/dist/uprights.rinks.d.ts +27 -0
  239. package/dist/uprights.rinks.js +106 -0
  240. package/dist/uprights.track.d.ts +50 -0
  241. package/dist/uprights.track.js +246 -0
  242. package/dist/version.d.ts +1 -1
  243. package/dist/version.js +1 -1
  244. package/package.json +19 -2
@@ -0,0 +1,207 @@
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, safeLogo } from "./colour.js";
6
+ import { isNetSport } from "./court.js";
7
+ /** The border band of a carpet, as a share of its shorter side: the least, the most and the usual. */
8
+ export const PAGEANT_BORDER = { min: 0.02, max: 0.15, usual: 0.06 };
9
+ /** The least and the most a carpet or a stage is scaled to, as a share of the room its half gives it. */
10
+ export const PAGEANT_SCALE = { min: 0.4, max: 1 };
11
+ /** 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. */
12
+ export const PAGEANT_RATIO = { min: 1, max: 3, usual: 1.5 };
13
+ /** 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. */
14
+ export const PAGEANT_FILL_LIMIT = 1.75;
15
+ /** What the plate is shrunk to when it stays in the middle of a pageant, so that the pictures have room. */
16
+ const PLATE_SCALE = 0.85;
17
+ const oneOf = (value, allowed) => allowed.includes(value) ? value : undefined;
18
+ /** A colour or one of the words a choice also takes. */
19
+ const colourOrWord = (value, words, fallback) => {
20
+ if (typeof value === "string" && words.includes(value))
21
+ return value;
22
+ return safeColour(value) ?? fallback;
23
+ };
24
+ const finite = (value, least, most, fallback) => (typeof value === "number" && Number.isFinite(value) ? Math.min(most, Math.max(least, value)) : fallback);
25
+ /** 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. */
26
+ const BOARDS = (sport) => isNetSport(sport) || sport === "cricket";
27
+ /** 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. */
28
+ export const plateShrinks = (sport) => !BOARDS(sport) && sport !== "curling";
29
+ /**
30
+ * 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
31
+ * the values is the default's; a colour must pass `safeColour`, the emblem `safeLogo`, a number is held to its range.
32
+ */
33
+ export function pageantOf(o) {
34
+ const given = o.pageant;
35
+ if (given === undefined || given === false || given === null)
36
+ return undefined;
37
+ if (o.home === undefined || o.away === undefined || o.size === "badge" || o.host !== undefined)
38
+ return undefined;
39
+ const p = given === true ? {} : typeof given === "string" ? { style: given } : typeof given === "object" ? given : {};
40
+ const style = oneOf(p.style, ["carpet", "stage"]) ?? "carpet";
41
+ const href = safeLogo(p.emblem);
42
+ const asked = oneOf(p.score, ["auto", "centre", "strip"]) ?? "auto";
43
+ // 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.
44
+ const score = asked === "auto" && (href !== undefined || BOARDS(o.sport)) ? "strip" : asked;
45
+ return {
46
+ style,
47
+ border: colourOrWord(p.border, ["dark", "team"], "dark"),
48
+ borderWidth: finite(p.borderWidth, PAGEANT_BORDER.min, PAGEANT_BORDER.max, PAGEANT_BORDER.usual),
49
+ fabric: p.fabric !== false,
50
+ lines: oneOf(p.lines, ["over", "under", "through"]) ?? "over",
51
+ ring: colourOrWord(p.ring, ["gold", "team"], "gold"),
52
+ ...(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 } }),
53
+ score,
54
+ scale: finite(p.scale, PAGEANT_SCALE.min, PAGEANT_SCALE.max, PAGEANT_SCALE.max),
55
+ fit: oneOf(p.fit, ["fill", "ratio"]) ?? "fill",
56
+ names: typeof p.names === "boolean" ? p.names : undefined,
57
+ animate: oneOf(p.animate, ["none", "unfurl", "fade"]) ?? "none",
58
+ };
59
+ }
60
+ /** The ratio of an SVG document's picture: its `viewBox`, else its `width` and `height`. */
61
+ function svgRatio(text) {
62
+ const head = text.slice(0, 2000);
63
+ const open = /<svg\b[^>]*>/i.exec(head)?.[0] ?? head;
64
+ const box = /viewBox\s*=\s*["']\s*[-\d.e]+[\s,]+[-\d.e]+[\s,]+([\d.e]+)[\s,]+([\d.e]+)/i.exec(open);
65
+ if (box !== null && Number(box[1]) > 0 && Number(box[2]) > 0)
66
+ return Number(box[1]) / Number(box[2]);
67
+ const w = /\swidth\s*=\s*["']\s*([\d.]+)/i.exec(open);
68
+ const h = /\sheight\s*=\s*["']\s*([\d.]+)/i.exec(open);
69
+ return w !== null && h !== null && Number(w[1]) > 0 && Number(h[1]) > 0 ? Number(w[1]) / Number(h[1]) : undefined;
70
+ }
71
+ /** 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. */
72
+ export function ratioOf(team) {
73
+ const clamp = (value) => Math.min(PAGEANT_RATIO.max, Math.max(PAGEANT_RATIO.min, value));
74
+ if (typeof team.logoRatio === "number" && Number.isFinite(team.logoRatio) && team.logoRatio > 0)
75
+ return clamp(team.logoRatio);
76
+ const logo = typeof team.logo === "string" ? team.logo.trim() : "";
77
+ let found;
78
+ if (/^(?:<\?xml|<svg)\b/i.test(logo))
79
+ found = svgRatio(logo);
80
+ else if (/^data:image\/svg\+xml/i.test(logo)) {
81
+ const comma = logo.indexOf(",");
82
+ try {
83
+ const body = /;base64/i.test(logo.slice(0, comma)) ? atob(logo.slice(comma + 1)) : decodeURIComponent(logo.slice(comma + 1));
84
+ found = svgRatio(body);
85
+ }
86
+ catch {
87
+ found = undefined;
88
+ }
89
+ }
90
+ return clamp(found ?? PAGEANT_RATIO.usual);
91
+ }
92
+ /** A rectangle's outline, as points the outline's `contains` is asked about: corners, then more along each side. */
93
+ function ring(cx, cy, w, h, grow, to) {
94
+ const out = [];
95
+ const hw = w / 2 + grow;
96
+ const hh = h / 2 + grow;
97
+ const steps = 6;
98
+ for (let i = 0; i <= steps; i += 1) {
99
+ const t = -1 + (2 * i) / steps;
100
+ for (const [ex, ey] of [[t * hw, -hh], [t * hw, hh], [-hw, t * hh], [hw, t * hh]]) {
101
+ const [dx, dy] = to(ex, ey);
102
+ out.push([cx + dx, cy + dy]);
103
+ }
104
+ }
105
+ return out;
106
+ }
107
+ 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)]);
108
+ /**
109
+ * 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
110
+ * (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.
111
+ * @returns `undefined` when the surface has no playing area to lay a picture on.
112
+ */
113
+ export function pageantLayout(input) {
114
+ const { pageant: p, surface, outline, turned } = input;
115
+ const box = surface.whole ?? surface.box ?? outline.box;
116
+ if (!(box.w > 0 && box.h > 0))
117
+ return undefined;
118
+ const minDim = Math.min(box.w, box.h);
119
+ const margin = (p.fit === "fill" ? 0.05 : 0.03) * minDim;
120
+ const mid = box.x + box.w / 2;
121
+ const plan = surface.pageant;
122
+ const centre = plan?.centre ?? { cx: mid, cy: box.y + box.h / 2, r: surface.centreRadius ?? 0.14 * minDim };
123
+ const emblemR = p.emblem === undefined ? 0 : centre.r * 0.92;
124
+ const scale = plateShrinks(input.sport) ? PLATE_SCALE : 1;
125
+ // What the middle holds: the emblem's disc, the plate's length along the surface, the cricket pitch.
126
+ const plateAlong = input.plate && plateShrinks(input.sport) && plan?.left === undefined ? ((turned ? surface.plate.h : surface.plate.w) * scale) / 2 : 0;
127
+ const clear = Math.max(emblemR, plateAlong, plan?.clear ?? 0);
128
+ const eye = (ex, ey) => (turned ? [ey, -ex] : [ex, ey]);
129
+ const place = (team, which, spot) => {
130
+ const along = spot.x1 - spot.x0;
131
+ const across = spot.y1 - spot.y0;
132
+ const cx = (spot.x0 + spot.x1) / 2;
133
+ const cy = (spot.y0 + spot.y1) / 2;
134
+ // The room as the eye sees it: a tall drawing's halves lie one above the other.
135
+ const roomW = turned ? across : along;
136
+ const roomH = turned ? along : across;
137
+ if (p.style === "stage") {
138
+ let r = (spot.disc === undefined ? Math.min(roomW, roomH) : Math.min(spot.disc, roomW, roomH)) / 2 * p.scale;
139
+ for (let i = 0; i < 50 && !around(cx, cy, r, margin * 0.5).every(([x, y]) => outline.contains(x, y)); i += 1)
140
+ r *= 0.96;
141
+ return { team, spot: which, cx, cy, w: 2 * r, h: 2 * r, r };
142
+ }
143
+ const ratio = team.ratio;
144
+ let ratioOfCarpet = ratio;
145
+ let filled = false;
146
+ let w;
147
+ // 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.
148
+ if (spot.disc !== undefined)
149
+ w = Math.min(((spot.disc * ratio) / Math.sqrt(1 + ratio * ratio)) * 0.94, roomW, roomH * ratio) * p.scale;
150
+ else if (p.fit === "fill") {
151
+ // The carpet is the room's own shape, as far as its flag's ratio allows: not more than PAGEANT_FILL_LIMIT either way.
152
+ const shape = Math.min(Math.max(roomW / roomH, ratio / PAGEANT_FILL_LIMIT), ratio * PAGEANT_FILL_LIMIT);
153
+ w = Math.min(roomW, roomH * shape) * p.scale;
154
+ filled = true;
155
+ ratioOfCarpet = shape;
156
+ }
157
+ else
158
+ w = Math.min(roomW, roomH * ratio) * p.scale;
159
+ let h = w / ratioOfCarpet;
160
+ let px = cx;
161
+ for (let i = 0; i < 60 && !ring(px, cy, w, h, margin * 0.5, eye).every(([x, y]) => outline.contains(x, y)); i += 1) {
162
+ if (filled) {
163
+ // 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.
164
+ const cut = (turned ? h : w) * 0.03;
165
+ if (turned)
166
+ h -= cut;
167
+ else
168
+ w -= cut;
169
+ px += ((which === "left" ? 1 : -1) * cut) / 2;
170
+ }
171
+ else {
172
+ w *= 0.96;
173
+ h *= 0.96;
174
+ }
175
+ }
176
+ return { team, spot: which, cx: px, cy, w, h, ...(filled ? { filled } : {}) };
177
+ };
178
+ const spotOf = (side) => {
179
+ const given = side === "left" ? plan?.left : plan?.right;
180
+ if (given !== undefined)
181
+ 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 }) };
182
+ const y0 = box.y + margin;
183
+ const y1 = box.y + box.h - margin;
184
+ 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 };
185
+ };
186
+ const pictures = [place(input.left, "left", spotOf("left")), place(input.right, "right", spotOf("right"))];
187
+ if (!pictures.every((picture) => picture.w > 0 && picture.h > 0 && Number.isFinite(picture.cx + picture.cy)))
188
+ return undefined;
189
+ const layout = { pictures, plateScale: scale };
190
+ if (p.emblem !== undefined && emblemR > 0)
191
+ layout.emblem = { cx: centre.cx, cy: centre.cy, r: emblemR };
192
+ return layout;
193
+ }
194
+ /** 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. */
195
+ export const PLATE_COST = 0.7;
196
+ /**
197
+ * 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
198
+ * goes to the strip under the drawing and the pictures take the room.
199
+ */
200
+ export function autoScore(input) {
201
+ const withPlate = pageantLayout({ ...input, plate: true });
202
+ const without = pageantLayout({ ...input, plate: false });
203
+ if (withPlate === undefined || without === undefined)
204
+ return "centre";
205
+ const size = (layout) => Math.min(...layout.pictures.map((picture) => picture.w));
206
+ return size(withPlate) >= PLATE_COST * size(without) ? "centre" : "strip";
207
+ }
@@ -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 }),
package/dist/pool.d.ts ADDED
@@ -0,0 +1,176 @@
1
+ import { type RacePlan } from "./pool.race.ts";
2
+ import type { DeckLook, PoolKind, PoolLook, PoolSport, PoolStyle, VenueOptions, VenuePool } from "./pool.types.ts";
3
+ import type { SurfaceOptions } from "./types.ts";
4
+ /** The sports of the pool, in the order a menu lists them. */
5
+ export declare const POOL_SPORTS: readonly PoolSport[];
6
+ /** True for a sport whose surface is a pool. */
7
+ export declare const isPoolSport: (sport: unknown) => sport is PoolSport;
8
+ /** Metres as feet, the unit a drawing is made in. */
9
+ export declare const feet: (metres: number) => number;
10
+ /** The colours of the ropes of a pool, by name. */
11
+ export type RopeColour = "green" | "blue" | "yellow";
12
+ /** A lane rope: where it lies across the pool (metres from the top wall) and its colour. */
13
+ export interface Rope {
14
+ y: number;
15
+ colour: RopeColour;
16
+ }
17
+ /** A lane: its number, the middle of its marking on the floor, and its two edges, in metres from the top wall. */
18
+ export interface LanePlan {
19
+ number: number;
20
+ y: number;
21
+ top: number;
22
+ bottom: number;
23
+ }
24
+ /** The look and the extras every pool has. */
25
+ export interface Common {
26
+ look: PoolLook;
27
+ deck: DeckLook;
28
+ seed: number;
29
+ }
30
+ /** A swimming pool to draw. */
31
+ export interface SwimPlan extends Common {
32
+ sport: "swimming";
33
+ kind: PoolKind;
34
+ course: "long" | "short";
35
+ /** The length between the touch panels and the width across the lanes and the spaces, in metres. */
36
+ length: number;
37
+ width: number;
38
+ /** The lanes from the top of the drawing down: lane 1 (or 0) is at the bottom, on the right when one faces the course from the start (CR 16.1.9). */
39
+ lanes: LanePlan[];
40
+ /** The metres of space outside the first and the last lane. */
41
+ space: number;
42
+ ropes: Rope[];
43
+ blocks: boolean;
44
+ pads: boolean;
45
+ flags: boolean;
46
+ falseStart: boolean;
47
+ depth: number;
48
+ section: {
49
+ exaggerate: number;
50
+ } | undefined;
51
+ race: RacePlan | undefined;
52
+ }
53
+ /** A water polo field to draw. */
54
+ export interface PoloPlan extends Common {
55
+ sport: "waterpolo";
56
+ length: number;
57
+ width: number;
58
+ /** The side of the official table, as the field lies wide. */
59
+ table: "top" | "bottom";
60
+ }
61
+ /** One board of a diving well: a springboard or a platform, with its plummet (metres from the wall, then across from the top) and its size. */
62
+ export interface Board {
63
+ kind: "springboard" | "platform";
64
+ height: number;
65
+ /** The plummet: x from the pool wall the boards stand at, y across from the top wall. */
66
+ x: number;
67
+ y: number;
68
+ length: number;
69
+ width: number;
70
+ }
71
+ /** A diving well to draw. */
72
+ export interface DivePlan extends Common {
73
+ sport: "diving";
74
+ length: number;
75
+ width: number;
76
+ boards: Board[];
77
+ /** The metres behind the wall that the stands reach, for the view. */
78
+ behind: number;
79
+ }
80
+ /** An artistic swimming area to draw. */
81
+ export interface ArtisticPlan extends Common {
82
+ sport: "artisticswimming";
83
+ area: "olympic" | "minimum";
84
+ length: number;
85
+ width: number;
86
+ /** For the minimum area: the deep part, in metres from the left wall and the top wall, and its size. */
87
+ deep: {
88
+ x: number;
89
+ y: number;
90
+ length: number;
91
+ width: number;
92
+ } | undefined;
93
+ }
94
+ export type PoolPlan = SwimPlan | PoloPlan | DivePlan | ArtisticPlan;
95
+ /** The size of the water of a pool in metres: `length` along the drawing and `width` across it. */
96
+ export declare const poolSize: (plan: PoolPlan) => {
97
+ length: number;
98
+ width: number;
99
+ };
100
+ /** The look the options ask for. @throws Error for a look or a deck the pool does not have. */
101
+ export declare function commonOf(style: PoolStyle): Common;
102
+ /** The colour of rope `i` of `n + 1`: green outside, yellow round the middle, blue between (CR 16.1.7.1 for eight lanes; 16.2.7.1.2 for ten). */
103
+ export declare function ropeColour(i: number, lanes: number): RopeColour;
104
+ /** The width of a swimming pool of this length and this many lanes, the space outside the first and the last lane, and the width of those two lanes. */
105
+ export declare function laneWidths(course: "long" | "short", lanes: number, temporary: boolean): {
106
+ width: number;
107
+ space: number;
108
+ edge: number;
109
+ };
110
+ /** The lanes and the ropes of a pool of this width, from the top. */
111
+ export declare function lanesOf(width: number, lanes: number, space: number): {
112
+ lanes: LanePlan[];
113
+ ropes: Rope[];
114
+ };
115
+ /**
116
+ * The swimming pool the options ask for: `pool.course` (`long`, 50 m, or `short`, 25 m) and `pool.lanes` (8 by default). The width is the rules' for that many lanes: 25 m for eight in a 50 m
117
+ * pool (CR 16.2.6.2), 25 m for ten with lanes 0 and 9 of 2.4 m, or 26 m for a temporary pool (CR 16.2.6.3), and lanes of 2.5 m with spaces of 0.5 m for the other pools.
118
+ * @throws RangeError for a lane count that is not 1 to 10, @throws Error for an unknown look.
119
+ */
120
+ export declare function swimPlan(o: Pick<SurfaceOptions, "pool" | "sport">, defaults?: Partial<Pick<VenuePool, "course" | "kind" | "lanes">>): SwimPlan;
121
+ /** The water polo field the options ask for: 25 by 20 m (CR 18.1.2). */
122
+ export declare function poloPlan(o: Pick<SurfaceOptions, "pool">): PoloPlan;
123
+ /**
124
+ * The diving well the options ask for. The springboards (1 m and 3 m, `pool.boards` of each, 2 by default so that a synchronised pair stands side by side) stand along the left wall, 3 m boards outside, each the preferred
125
+ * distance (Appendix 2, B and C) from the wall and from the next; the platforms (5, 7.5 and 10 m by default) stand towards the right wall, the tallest the distance B from it and each lower one the distance C of the
126
+ * platform above it from that one. The well is 25 m across, wider if the boards need more room than that.
127
+ * @throws RangeError for a platform the rules do not have or a count of boards that is not 1 to 4.
128
+ */
129
+ export declare function divePlan(o: Pick<SurfaceOptions, "pool">): DivePlan;
130
+ /** The artistic swimming area the options ask for: 30 by 20 m for the Olympic Games and the World Championships, or the least, 25 by 15 m with a 12 by 12 m deep part (CR 27.2.2, 27.1.2). */
131
+ export declare function artisticPlan(o: Pick<SurfaceOptions, "pool">): ArtisticPlan;
132
+ /** The pool the options ask for, of the sport's kind. */
133
+ export declare function poolPlan(o: Pick<SurfaceOptions, "sport" | "pool">): PoolPlan;
134
+ /** The hall of a natatorium asked for, with `natatorium` standing for the usual three pools. */
135
+ export declare function venueOf(style: PoolStyle): VenueOptions | undefined;
136
+ /** What each plan reaches beyond its water, in metres on each side: the blocks and flag standards, the platforms and table, the stands behind the diving boards, the judges' platform. */
137
+ export declare function reachOf(plan: PoolPlan): {
138
+ left: number;
139
+ right: number;
140
+ top: number;
141
+ bottom: number;
142
+ };
143
+ /** One pool of a hall: its plan and where its water's top left corner is, in metres from the corner of the water of the whole hall. */
144
+ export interface VenueItem {
145
+ pool: VenuePool;
146
+ plan: PoolPlan;
147
+ x: number;
148
+ y: number;
149
+ }
150
+ /** A hall laid out: its pools, and the size of the box round their water, in metres. */
151
+ export interface VenueLayout {
152
+ items: VenueItem[];
153
+ width: number;
154
+ height: number;
155
+ apart: number;
156
+ labels: boolean;
157
+ seating: boolean;
158
+ /** How far the pools' fixtures reach beyond the box, in metres. */
159
+ reach: {
160
+ left: number;
161
+ right: number;
162
+ top: number;
163
+ bottom: number;
164
+ };
165
+ }
166
+ /**
167
+ * The pools of a hall (`pool.venue`) laid out to scale. A pool with an `x` and a `y` stands there; the others are set in rows, left to right, each `apart` metres (10, the distance the rules prefer between
168
+ * a swimming pool and a diving well, CR 16.2.17; never less than the 8 they require) from the one before, and a new row when a row would be longer than 125 m.
169
+ * @throws RangeError when two pools are nearer each other than `apart`, for the pools given places.
170
+ */
171
+ export declare function venueLayout(style: PoolStyle): VenueLayout | undefined;
172
+ /** The box round the water the options draw, in feet: the pool's, or the whole hall's. */
173
+ export declare function poolBox(o: Pick<SurfaceOptions, "sport" | "pool">): {
174
+ w: number;
175
+ h: number;
176
+ };