@johnmorrisdotca/dougu-sports 0.1.0 → 0.2.1
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 +53 -1
- package/NOTICE.md +9 -58
- package/README.md +174 -37
- package/dist/emblems/award/badge.d.ts +2 -2
- package/dist/emblems/award/badge.js +14 -5
- package/dist/emblems/award/crown.d.ts +1 -1
- package/dist/emblems/award/crown.js +14 -4
- package/dist/emblems/award/medal.d.ts +2 -2
- package/dist/emblems/award/medal.js +14 -5
- package/dist/emblems/award/shield.d.ts +1 -1
- package/dist/emblems/award/shield.js +10 -4
- package/dist/emblems/award/star.d.ts +1 -1
- package/dist/emblems/award/star.js +10 -4
- package/dist/emblems/ball/ball.afl.js +3 -2
- package/dist/emblems/ball/ball.basketball6.js +1 -1
- package/dist/emblems/ball/ball.core.d.ts +9 -3
- package/dist/emblems/ball/ball.core.js +13 -0
- package/dist/emblems/ball/ball.cricket.js +1 -1
- package/dist/emblems/ball/ball.curlingstone.d.ts +2 -0
- package/dist/emblems/ball/ball.curlingstone.js +11 -7
- package/dist/emblems/ball/ball.discus.js +1 -1
- package/dist/emblems/ball/ball.engine.js +3 -3
- package/dist/emblems/ball/ball.football.js +9 -7
- package/dist/emblems/ball/ball.gaelic.js +1 -1
- package/dist/emblems/ball/ball.object.d.ts +5 -2
- package/dist/emblems/ball/ball.object.js +3 -2
- package/dist/emblems/ball/ball.puck.d.ts +2 -0
- package/dist/emblems/ball/ball.puck.js +2 -2
- package/dist/emblems/ball/ball.rugby.js +3 -2
- package/dist/emblems/ball/ball.shuttlecock.d.ts +4 -0
- package/dist/emblems/ball/ball.shuttlecock.js +6 -5
- package/dist/emblems/ball/ball.squash.js +5 -5
- package/dist/emblems/emblem.api.d.ts +39 -1
- package/dist/emblems/emblem.api.js +20 -2
- package/dist/emblems/emblem.compose.d.ts +31 -0
- package/dist/emblems/emblem.compose.js +91 -0
- package/dist/emblems/emblem.core.d.ts +48 -2
- package/dist/emblems/emblem.core.js +30 -0
- package/dist/emblems/emblem.descriptions.d.ts +7 -0
- package/dist/emblems/emblem.descriptions.js +60 -0
- package/dist/emblems/emblem.display.d.ts +75 -0
- package/dist/emblems/emblem.display.js +214 -0
- package/dist/emblems/emblem.engrave.d.ts +50 -0
- package/dist/emblems/emblem.engrave.js +145 -0
- package/dist/emblems/emblem.equipment.d.ts +24 -0
- package/dist/emblems/emblem.equipment.js +57 -0
- package/dist/emblems/emblem.flip.d.ts +10 -0
- package/dist/emblems/emblem.flip.js +46 -0
- package/dist/emblems/emblem.ids.d.ts +19 -11
- package/dist/emblems/emblem.ids.js +21 -6
- package/dist/emblems/emblem.lathe.d.ts +3 -0
- package/dist/emblems/emblem.lathe.js +26 -4
- package/dist/emblems/emblem.medal.js +4 -4
- package/dist/emblems/emblem.pennant.d.ts +3 -0
- package/dist/emblems/emblem.pennant.js +79 -0
- package/dist/emblems/emblem.registry.js +20 -0
- package/dist/emblems/emblem.shapes.d.ts +19 -2
- package/dist/emblems/emblem.shapes.js +55 -13
- package/dist/emblems/emblem.sprite.d.ts +38 -0
- package/dist/emblems/emblem.sprite.js +65 -0
- package/dist/emblems/emblem.uri.d.ts +22 -0
- package/dist/emblems/emblem.uri.js +29 -0
- package/dist/emblems/gear/gear.badmintonracket.d.ts +1 -1
- package/dist/emblems/gear/gear.badmintonracket.js +18 -14
- package/dist/emblems/gear/gear.baseballglove.d.ts +1 -1
- package/dist/emblems/gear/gear.baseballglove.js +20 -14
- package/dist/emblems/gear/gear.budo.d.ts +17 -0
- package/dist/emblems/gear/gear.budo.js +60 -0
- package/dist/emblems/gear/gear.core.d.ts +12 -31
- package/dist/emblems/gear/gear.core.js +61 -54
- package/dist/emblems/gear/gear.cricketbat.d.ts +1 -1
- package/dist/emblems/gear/gear.cricketbat.js +16 -14
- package/dist/emblems/gear/gear.fieldhockeystick.d.ts +1 -1
- package/dist/emblems/gear/gear.fieldhockeystick.js +15 -14
- package/dist/emblems/gear/gear.footballhelmet.d.ts +1 -1
- package/dist/emblems/gear/gear.footballhelmet.js +15 -14
- package/dist/emblems/gear/gear.goaliemask.d.ts +1 -1
- package/dist/emblems/gear/gear.goaliemask.js +22 -16
- package/dist/emblems/gear/gear.goalnet.d.ts +1 -1
- package/dist/emblems/gear/gear.goalnet.js +24 -14
- package/dist/emblems/gear/gear.hockeystick.d.ts +1 -1
- package/dist/emblems/gear/gear.hockeystick.js +14 -14
- package/dist/emblems/gear/gear.index.js +5 -1
- package/dist/emblems/gear/gear.kendo.d.ts +2 -0
- package/dist/emblems/gear/gear.kendo.js +41 -0
- package/dist/emblems/gear/gear.kyudo.d.ts +2 -0
- package/dist/emblems/gear/gear.kyudo.js +31 -0
- package/dist/emblems/gear/gear.lacrossestick.d.ts +1 -1
- package/dist/emblems/gear/gear.lacrossestick.js +25 -14
- package/dist/emblems/gear/gear.skates.js +1 -1
- package/dist/emblems/gear/gear.sticks.js +2 -2
- package/dist/emblems/gear/gear.sumo.d.ts +2 -0
- package/dist/emblems/gear/gear.sumo.js +43 -0
- package/dist/emblems/gear/gear.tabletennispaddle.d.ts +1 -1
- package/dist/emblems/gear/gear.tabletennispaddle.js +15 -14
- package/dist/emblems/gear/gear.tennisracket.d.ts +1 -1
- package/dist/emblems/gear/gear.tennisracket.js +16 -14
- package/dist/emblems/pennant/championship.d.ts +2 -0
- package/dist/emblems/pennant/championship.js +4 -0
- package/dist/emblems/sport/afl.js +1 -1
- package/dist/emblems/sport/artisticswimming.js +1 -1
- package/dist/emblems/sport/cfl.d.ts +1 -1
- package/dist/emblems/sport/cfl.js +4 -4
- package/dist/emblems/sport/cricket.d.ts +1 -1
- package/dist/emblems/sport/cricket.js +13 -4
- package/dist/emblems/sport/fieldhockey.js +1 -1
- package/dist/emblems/sport/gaelic.js +1 -1
- package/dist/emblems/sport/handball.d.ts +1 -1
- package/dist/emblems/sport/handball.js +14 -4
- package/dist/emblems/sport/ifaf.d.ts +1 -1
- package/dist/emblems/sport/ifaf.js +4 -4
- package/dist/emblems/sport/judo.d.ts +2 -0
- package/dist/emblems/sport/judo.js +13 -0
- package/dist/emblems/sport/karate.d.ts +2 -0
- package/dist/emblems/sport/karate.js +13 -0
- package/dist/emblems/sport/kendo.d.ts +2 -0
- package/dist/emblems/sport/kendo.js +14 -0
- package/dist/emblems/sport/kyudo.d.ts +2 -0
- package/dist/emblems/sport/kyudo.js +15 -0
- package/dist/emblems/sport/mlb.d.ts +1 -1
- package/dist/emblems/sport/mlb.js +27 -4
- package/dist/emblems/sport/nba.d.ts +1 -1
- package/dist/emblems/sport/nba.js +11 -4
- package/dist/emblems/sport/netball.js +1 -1
- package/dist/emblems/sport/nfl.d.ts +2 -1
- package/dist/emblems/sport/nfl.js +14 -4
- package/dist/emblems/sport/nhl.d.ts +1 -1
- package/dist/emblems/sport/nhl.js +12 -4
- package/dist/emblems/sport/pickleball.js +1 -1
- package/dist/emblems/sport/racquetball.js +1 -1
- package/dist/emblems/sport/roundnet.js +1 -1
- package/dist/emblems/sport/rugby.d.ts +1 -1
- package/dist/emblems/sport/rugby.js +11 -4
- package/dist/emblems/sport/sepaktakraw.js +1 -1
- package/dist/emblems/sport/soccer.d.ts +1 -1
- package/dist/emblems/sport/soccer.js +30 -4
- package/dist/emblems/sport/squash.js +1 -1
- package/dist/emblems/sport/sumo.d.ts +2 -0
- package/dist/emblems/sport/sumo.js +15 -0
- package/dist/emblems/sport/tabletennis.js +1 -1
- package/dist/emblems/sport/tennis.d.ts +1 -1
- package/dist/emblems/sport/tennis.js +14 -4
- package/dist/emblems/sport/teqball.js +1 -1
- package/dist/emblems/sport/volleyball.d.ts +1 -1
- package/dist/emblems/sport/volleyball.js +24 -4
- package/dist/emblems/trophy/banner.d.ts +2 -0
- package/dist/emblems/trophy/banner.js +20 -0
- package/dist/emblems/trophy/baseball.js +1 -0
- package/dist/emblems/trophy/basketball.js +1 -0
- package/dist/emblems/trophy/belt.js +2 -1
- package/dist/emblems/trophy/bowl.js +1 -0
- package/dist/emblems/trophy/canadian-bowl.js +1 -0
- package/dist/emblems/trophy/championship-banner.d.ts +2 -0
- package/dist/emblems/trophy/championship-banner.js +67 -0
- package/dist/emblems/trophy/cricket-urn.js +1 -0
- package/dist/emblems/trophy/cup.d.ts +1 -1
- package/dist/emblems/trophy/cup.js +15 -4
- package/dist/emblems/trophy/football.js +2 -1
- package/dist/emblems/trophy/globe.js +1 -0
- package/dist/emblems/trophy/hockey-cup.js +2 -1
- package/dist/emblems/trophy/lidded-cup.js +1 -0
- package/dist/emblems/trophy/plaque.js +2 -1
- package/dist/emblems/trophy/ring.d.ts +2 -0
- package/dist/emblems/trophy/ring.js +30 -0
- package/dist/emblems/trophy/rugby-cup.js +1 -0
- package/dist/emblems/trophy/salver.js +1 -0
- package/dist/emblems/trophy/soccer-ball.js +2 -1
- package/dist/emblems/trophy/soccer-globe.js +1 -0
- package/dist/emblems/trophy/sumo-cup.d.ts +2 -0
- package/dist/emblems/trophy/sumo-cup.js +28 -0
- package/dist/emblems/trophy/tennis-cup.js +2 -1
- package/dist/emblems/trophy/tennis-plate.js +1 -0
- package/dist/emblems-entry.d.ts +15 -6
- package/dist/emblems-entry.js +7 -2
- package/dist/index.d.ts +17 -0
- package/dist/index.js +15 -0
- package/dist/turn-control.d.ts +101 -0
- package/dist/turn-control.js +363 -0
- package/dist/turn-entry.d.ts +2 -0
- package/dist/turn-entry.js +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -1
- package/LICENSES/Apache-2.0.txt +0 -202
- package/LICENSES/CC-BY-3.0.txt +0 -319
- package/LICENSES/upstream/game-icons.license.txt +0 -45
- package/LICENSES/upstream/noto-emoji-2D-svg.LICENSE +0 -13
- package/LICENSES/upstream/pictogrammers-mdi.LICENSE +0 -20
- package/dist/emblems/emblem.material.d.ts +0 -9
- package/dist/emblems/emblem.material.data.d.ts +0 -34
- package/dist/emblems/emblem.material.data.js +0 -85
- package/dist/emblems/emblem.material.js +0 -23
|
@@ -14,6 +14,10 @@ export interface PieceOptions {
|
|
|
14
14
|
accent?: string;
|
|
15
15
|
/** Force the grain on or off. By default it is on for a colour ball of 48 pixels or more that is not turning. */
|
|
16
16
|
grain?: boolean;
|
|
17
|
+
/** The language of the `<title>`: `"en"` (the default) or `"ja"`. */
|
|
18
|
+
locale?: "en" | "ja";
|
|
19
|
+
/** A string is the `<title>` (escaped); `false` drops it and marks the drawing `aria-hidden="true"`. */
|
|
20
|
+
title?: string | false;
|
|
17
21
|
}
|
|
18
22
|
/** A turn: `yaw` degrees about the vertical axis (0 is the default view), `pitch` the tilt toward you, `roll` the turn about the line of sight. */
|
|
19
23
|
export interface BallFrameOptions extends PieceOptions {
|
|
@@ -22,8 +26,8 @@ export interface BallFrameOptions extends PieceOptions {
|
|
|
22
26
|
roll?: number;
|
|
23
27
|
}
|
|
24
28
|
/** Where a drawing comes from. */
|
|
25
|
-
export type PieceSource = "generated" | "drawn"
|
|
26
|
-
/** `ball` is turned in 3D; `gear` is a flat drawing in a colour and a line style. */
|
|
29
|
+
export type PieceSource = "generated" | "drawn";
|
|
30
|
+
/** `ball` is turned in 3D; `gear` is a flat drawing in a metal, a colour and a line style, which flips like a coin when turned. */
|
|
27
31
|
export type PieceKind = "ball" | "gear";
|
|
28
32
|
/** The official size of a ball or an implement, from the rulebook that governs it. Lengths in millimetres, mass in grams. */
|
|
29
33
|
export interface PieceSize {
|
|
@@ -57,7 +61,7 @@ export interface Piece<Model = unknown> {
|
|
|
57
61
|
readonly size?: PieceSize;
|
|
58
62
|
/** The still drawing: a document of SVG. */
|
|
59
63
|
svg: (options?: PieceOptions) => string;
|
|
60
|
-
/** The piece turned
|
|
64
|
+
/** The piece turned: a ball in 3D, gear flipped like a coin. */
|
|
61
65
|
frame: (options?: BallFrameOptions) => string;
|
|
62
66
|
}
|
|
63
67
|
type Rgb = readonly [number, number, number];
|
|
@@ -98,6 +102,8 @@ export declare function wrapPiece(o: {
|
|
|
98
102
|
box?: string;
|
|
99
103
|
desc?: string;
|
|
100
104
|
}): string;
|
|
105
|
+
/** A piece's drawing function, with the `<title>` its options ask for: the Japanese name, a title of the caller's own, or none (the same rule as the emblems', in emblem.core.ts). */
|
|
106
|
+
export declare const labelled: <O extends PieceOptions>(en: string, ja: string, draw: (options?: O) => string) => (options?: O) => string;
|
|
101
107
|
/** Degrees to radians, for a value that may be missing or not a number. */
|
|
102
108
|
export declare const radians: (degrees: number | undefined, fallback: number) => number;
|
|
103
109
|
/** The definition of the grain filter: noise lit from the upper left and multiplied into what it is applied to. It works in the box's own units, so the grain stays where it is while a ball turns. */
|
|
@@ -58,6 +58,19 @@ export function wrapPiece(o) {
|
|
|
58
58
|
return (`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${o.box ?? "0 0 100 100"}" width="${o.size}" height="${o.size}" role="img"${colour === "" ? "" : ` color="${colour}"`}>` +
|
|
59
59
|
`<title>${escape(o.title)}</title>${o.desc === undefined ? "" : `<desc>${escape(o.desc)}</desc>`}${auto}${o.defs === undefined || o.defs === "" ? "" : `<defs>${o.defs}</defs>`}${o.body}</svg>`);
|
|
60
60
|
}
|
|
61
|
+
/** A piece's drawing function, with the `<title>` its options ask for: the Japanese name, a title of the caller's own, or none (the same rule as the emblems', in emblem.core.ts). */
|
|
62
|
+
export const labelled = (en, ja, draw) => (options) => {
|
|
63
|
+
const svg = draw(options);
|
|
64
|
+
const title = options?.title;
|
|
65
|
+
const custom = typeof title === "string" && title.trim() !== "";
|
|
66
|
+
if (title === false)
|
|
67
|
+
return svg.replace(/<title>[^<]*<\/title>/, "").replace(' role="img"', ' aria-hidden="true"');
|
|
68
|
+
if (!custom && options?.locale !== "ja")
|
|
69
|
+
return svg;
|
|
70
|
+
const text = custom ? title.trim().slice(0, 300) : ja;
|
|
71
|
+
const titled = svg.replace(/<title>[^<]*<\/title>/, () => `<title>${escape(text)}</title>`);
|
|
72
|
+
return custom ? titled : titled.replace(' role="img"', ' role="img" lang="ja"');
|
|
73
|
+
};
|
|
61
74
|
/** Degrees to radians, for a value that may be missing or not a number. */
|
|
62
75
|
export const radians = (degrees, fallback) => ((typeof degrees === "number" && Number.isFinite(degrees) ? degrees : fallback) * Math.PI) / 180;
|
|
63
76
|
/** The definition of the grain filter: noise lit from the upper left and multiplied into what it is applied to. It works in the box's own units, so the grain stays where it is while a ball turns. */
|
|
@@ -28,4 +28,4 @@ function cricket(id, en, ja, base, thread, size, shade) {
|
|
|
28
28
|
const SIZE = { extentMm: 72.1, diameterMm: 72.1, circumferenceMm: 226.5, massG: [156, 163], rule: "Laws of Cricket, Law 4 The Ball, 4.1 Weight and size (Size 1, Size 2, Size 3)", note: "Size 1 (men's). Diameter worked out from the middle of the circumference range. The Laws do not set a colour." };
|
|
29
29
|
export const BALL_CRICKET_RED = /* @__PURE__ */ cricket("ball:cricket-red", "Cricket ball, red", "クリケットボール(赤)", "#a9161e", "#f2e8d2", SIZE, "#2a0408");
|
|
30
30
|
export const BALL_CRICKET_WHITE = /* @__PURE__ */ cricket("ball:cricket-white", "Cricket ball, white", "クリケットボール(白)", "#f3f0e8", "#2a2a2a", SIZE, "#4a4a58");
|
|
31
|
-
export const BALL_CRICKET_PINK = /* @__PURE__ */ cricket("ball:cricket-pink", "Cricket ball, pink", "
|
|
31
|
+
export const BALL_CRICKET_PINK = /* @__PURE__ */ cricket("ball:cricket-pink", "Cricket ball, pink", "ピンクのクリケット球", "#e45a8c", "#2a2a2a", SIZE, "#4a0a22");
|
|
@@ -7,6 +7,10 @@ import { num } from "./ball.core.js";
|
|
|
7
7
|
const GRANITE = "#8d929a";
|
|
8
8
|
const POLISHED = "#4d525b";
|
|
9
9
|
const HANDLE = "#d4202c";
|
|
10
|
+
// The stack's y is in units of the radius, so the half height is the height of the stone (114 mm at least) over its largest diameter (291 mm, a 914 mm circumference): 0.392, and the stone runs from -H to H. The slices
|
|
11
|
+
// below are where the profile changes, as a fraction of H.
|
|
12
|
+
const H = 114 / 290.9;
|
|
13
|
+
const y = (t) => t * H;
|
|
10
14
|
export const BALL_CURLING_STONE = /* @__PURE__ */ defineObject({
|
|
11
15
|
id: "ball:curling-stone",
|
|
12
16
|
en: "Curling stone",
|
|
@@ -16,11 +20,11 @@ export const BALL_CURLING_STONE = /* @__PURE__ */ defineObject({
|
|
|
16
20
|
model: { slices: 6 },
|
|
17
21
|
draw: (c) => drawStack(c, {
|
|
18
22
|
slices: [
|
|
19
|
-
{ y0: -
|
|
20
|
-
{ y0: -0.
|
|
21
|
-
{ y0: -0.
|
|
22
|
-
{ y0: 0.
|
|
23
|
-
{ y0: 0.
|
|
23
|
+
{ y0: y(-1), y1: y(-0.75), r: [0.74, 0.8], colour: POLISHED, outline: true },
|
|
24
|
+
{ y0: y(-0.75), y1: y(-0.2), r: [0.8, 0.94], colour: GRANITE },
|
|
25
|
+
{ y0: y(-0.2), y1: y(0.4), r: 1, colour: POLISHED, outline: true },
|
|
26
|
+
{ y0: y(0.4), y1: y(0.75), r: [1, 0.9], colour: GRANITE },
|
|
27
|
+
{ y0: y(0.75), y1: y(1), r: [0.9, 0.78], colour: GRANITE, outline: true },
|
|
24
28
|
],
|
|
25
29
|
top: "#9a9fa7",
|
|
26
30
|
bottom: "#6a6f78",
|
|
@@ -30,8 +34,8 @@ export const BALL_CURLING_STONE = /* @__PURE__ */ defineObject({
|
|
|
30
34
|
dish: { r: 0.74, colour: "#555a63" },
|
|
31
35
|
extra: (ctx) => {
|
|
32
36
|
// The handle: a post and a bar, a capsule in 3D (a thick line with round ends).
|
|
33
|
-
const post0 = ctx.at([0,
|
|
34
|
-
const a = [-0.42, 0.
|
|
37
|
+
const post0 = ctx.at([0, H, 0]), post1 = ctx.at([0, H + 0.14, 0]);
|
|
38
|
+
const a = [-0.42, H + 0.2, 0], b = [0.42, H + 0.2, 0];
|
|
35
39
|
const p = ctx.at(a), q = ctx.at(b);
|
|
36
40
|
const colour = ctx.opts.style === "line" ? "currentColor" : (ctx.opts.tint ?? HANDLE);
|
|
37
41
|
const line = ctx.opts.style === "line";
|
|
@@ -7,7 +7,7 @@ export const BALL_DISCUS = /* @__PURE__ */ defineBall({
|
|
|
7
7
|
ja: "円盤",
|
|
8
8
|
size: { extentMm: 220, diameterMm: 220, thicknessMm: 45, massG: [2000, 2000], rule: "World Athletics Technical Rules (C2.1), Rule 34 Discus Throw - 'The discus shall conform to the following specifications'", note: "Men's discus, 219 to 221 mm across the rim and 44 to 46 mm thick at the middle; 2 kg is a minimum." },
|
|
9
9
|
spec: {
|
|
10
|
-
axes: [1,
|
|
10
|
+
axes: [1, 45 / 220, 1],
|
|
11
11
|
view: { yaw: 25, pitch: 52, roll: -12 },
|
|
12
12
|
base: "#3b3f48",
|
|
13
13
|
mat: { gloss: 0.7, shade: "#050608", grain: { f: 1.4, a: 0.12 } },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The ball renderer: one procedural engine for every sphere and every prolate ball. A ball is drawn in true 3D: a rotation, an orthographic projection, back faces dropped, panels cut at the limb
|
|
2
2
|
// along the limb itself, then radial-gradient shading and a grain that stays where it is while the ball turns. A ball is a `BallSpec`: its shape (the semi-axes of an ellipsoid), its
|
|
3
3
|
// view, its material and a `draw` function that lays panels, seams and dots on the unit sphere; the engine does the rest.
|
|
4
|
-
import { grainDef, escape, hashOf, mix, num, radians, resolveOptions, wrapPiece } from "./ball.core.js";
|
|
4
|
+
import { grainDef, escape, hashOf, labelled, mix, num, radians, resolveOptions, wrapPiece } from "./ball.core.js";
|
|
5
5
|
import { add, apply, basis, cross, dot, ellipseOf, len, mul, norm, rotation, sampleRing, sub } from "./ball.math.js";
|
|
6
6
|
const EPS = 1e-6;
|
|
7
7
|
const STEP = Math.PI / 18;
|
|
@@ -336,7 +336,7 @@ export function defineBall(def) {
|
|
|
336
336
|
kind: "ball",
|
|
337
337
|
model: def.spec,
|
|
338
338
|
size: def.size,
|
|
339
|
-
svg: (o) => render(o, false),
|
|
340
|
-
frame: (o) => render(o, true),
|
|
339
|
+
svg: labelled(def.en, def.ja, (o) => render(o, false)),
|
|
340
|
+
frame: labelled(def.en, def.ja, (o) => render(o, true)),
|
|
341
341
|
};
|
|
342
342
|
}
|
|
@@ -14,18 +14,19 @@ const upperArc = (ring) => {
|
|
|
14
14
|
return run;
|
|
15
15
|
};
|
|
16
16
|
const BROWN = "#8b4a21";
|
|
17
|
-
|
|
17
|
+
// The shape is worked out from the rule's figures (see test/proportions.test.ts): the width over the length is the girth over pi over the length, and `point` is chosen so that the outline, tip round to tip,
|
|
18
|
+
// is the long girth.
|
|
18
19
|
const VIEW = { yaw: -20, pitch: 35, roll: -35 };
|
|
19
20
|
const MAT = { gloss: 0.5, shade: "#2a1006", grain: { f: 0.95, a: 0.42 } };
|
|
20
|
-
function football(id, en, ja, size, stripes, colour) {
|
|
21
|
+
function football(id, en, ja, size, shape, stripes, colour) {
|
|
21
22
|
return defineBall({
|
|
22
23
|
id,
|
|
23
24
|
en,
|
|
24
25
|
ja,
|
|
25
26
|
size,
|
|
26
27
|
spec: {
|
|
27
|
-
axes:
|
|
28
|
-
point:
|
|
28
|
+
axes: [1, shape.width, shape.width],
|
|
29
|
+
point: shape.point,
|
|
29
30
|
view: VIEW,
|
|
30
31
|
base: colour,
|
|
31
32
|
mat: MAT,
|
|
@@ -45,7 +46,8 @@ function football(id, en, ja, size, stripes, colour) {
|
|
|
45
46
|
},
|
|
46
47
|
});
|
|
47
48
|
}
|
|
49
|
+
const NFL_SHAPE = { width: 0.6044, point: 0.541 };
|
|
48
50
|
const SIZE = { extentMm: 282.6, lengthMm: 282.6, circumferenceMm: 536.58, longCircumferenceMm: 717.55, massG: [396.89, 425.24], rule: "NFL Rulebook, Rule 2 The Ball, Section 1 (Dimensions)" };
|
|
49
|
-
export const BALL_FOOTBALL = /* @__PURE__ */ football("ball:football", "Football (NFL)", "
|
|
50
|
-
export const BALL_FOOTBALL_CFL = /* @__PURE__ */ football("ball:football-cfl", "Football (CFL)", "
|
|
51
|
-
export const BALL_FOOTBALL_IFAF = /* @__PURE__ */ football("ball:football-ifaf", "Football (IFAF)", "
|
|
51
|
+
export const BALL_FOOTBALL = /* @__PURE__ */ football("ball:football", "Football (NFL)", "アメフトボール(NFL)", SIZE, NFL_SHAPE, "none", BROWN);
|
|
52
|
+
export const BALL_FOOTBALL_CFL = /* @__PURE__ */ football("ball:football-cfl", "Football (CFL)", "フットボール(CFL)", { extentMm: 282.6, lengthMm: 282.6, circumferenceMm: 536.58, longCircumferenceMm: 717.55, massG: [396.89, 425.24], rule: "CFL Official Playing Rules, Rule 1 Conduct of the Game, Section 2 The Ball" }, NFL_SHAPE, "ring", BROWN);
|
|
53
|
+
export const BALL_FOOTBALL_IFAF = /* @__PURE__ */ football("ball:football-ifaf", "Football (IFAF)", "アメフトボール(IFAF)", { extentMm: 283.4, lengthMm: 283.4, circumferenceMm: 533.4, longCircumferenceMm: 714.38, massG: [396.89, 425.24], rule: "IFAF Football Rules and Interpretations 2026, Rule 1 (The Game, Field, Players and Equipment), Section 3 The Ball, Article 1 a-i", note: "The IFAF figures are in a diagram of the rules, read from the rendered page." }, { width: 0.599, point: 0.567 }, "top", "#a8693a");
|
|
@@ -5,7 +5,7 @@ import { strips18 } from "./ball.family.panelled.js";
|
|
|
5
5
|
export const BALL_GAELIC_FOOTBALL = /* @__PURE__ */ defineBall({
|
|
6
6
|
id: "ball:gaelic-football",
|
|
7
7
|
en: "Gaelic football",
|
|
8
|
-
ja: "
|
|
8
|
+
ja: "ゲーリックフットボール",
|
|
9
9
|
size: { extentMm: 219.6, diameterMm: 219.6, circumferenceMm: 690.0, massG: [480, 500], rule: "GAA Official Guide Part 2, Rules of Specification, Rule 4.4(ii)", note: "Diameter worked out from the middle of the circumference range." },
|
|
10
10
|
spec: {
|
|
11
11
|
view: { yaw: 28, pitch: 22, roll: 0 },
|
|
@@ -18,7 +18,7 @@ export interface ObjectContext {
|
|
|
18
18
|
/** A point of the object in its own space to the box: x, y and depth (positive toward you); one unit is 46 box units. */
|
|
19
19
|
at(p: V3): [number, number, number];
|
|
20
20
|
}
|
|
21
|
-
export interface ObjectDef<Model> {
|
|
21
|
+
export interface ObjectDef<Model extends object> {
|
|
22
22
|
id: string;
|
|
23
23
|
en: string;
|
|
24
24
|
ja: string;
|
|
@@ -35,4 +35,7 @@ export interface ObjectDef<Model> {
|
|
|
35
35
|
body: string;
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
/** An object as a piece. Its `model` is the def's model with the default `view` added, so that a view of it (or a test) can turn it to any aspect from there. */
|
|
39
|
+
export declare function defineObject<Model extends object>(def: ObjectDef<Model>): Piece<Model & {
|
|
40
|
+
view: ObjectView;
|
|
41
|
+
}>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Objects that are not an ellipsoid: a puck, a stone, a shuttlecock. They share what a ball has (the rotation, the options, the shading palette and the `Piece` shape) and draw their own geometry.
|
|
2
|
-
import { escape, hashOf, radians, resolveOptions, wrapPiece } from "./ball.core.js";
|
|
2
|
+
import { escape, hashOf, labelled, radians, resolveOptions, wrapPiece } from "./ball.core.js";
|
|
3
3
|
import { apply, rotation } from "./ball.math.js";
|
|
4
4
|
const finite = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
5
|
+
/** An object as a piece. Its `model` is the def's model with the default `view` added, so that a view of it (or a test) can turn it to any aspect from there. */
|
|
5
6
|
export function defineObject(def) {
|
|
6
7
|
const render = (o, turning) => {
|
|
7
8
|
const opts = resolveOptions(o);
|
|
@@ -16,5 +17,5 @@ export function defineObject(def) {
|
|
|
16
17
|
const { defs, body } = def.draw({ rot, opts, turning, id, minWidth: 80 / opts.size, at });
|
|
17
18
|
return wrapPiece({ title: escape(def.en), size: opts.size, theme: opts.theme, defs, body });
|
|
18
19
|
};
|
|
19
|
-
return { id: def.id, en: def.en, ja: def.ja, source: "generated", kind: "ball", model: def.model, size: def.size, svg: (o) => render(o, false), frame: (o) => render(o, true) };
|
|
20
|
+
return { id: def.id, en: def.en, ja: def.ja, source: "generated", kind: "ball", model: { ...def.model, view: def.view }, size: def.size, svg: labelled(def.en, def.ja, (o) => render(o, false)), frame: labelled(def.en, def.ja, (o) => render(o, true)) };
|
|
20
21
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// Ice hockey puck: vulcanised black rubber, 76.2 mm across and 25.4 mm thick (so 1 : 3), with a ridged edge. Drawn as a cylinder in true 3D, so it turns.
|
|
1
|
+
// Ice hockey puck: vulcanised black rubber, 76.2 mm across and 25.4 mm thick (so 1 : 3), with a ridged edge. Drawn as a cylinder in true 3D, so it turns. The stack's y is in units of the radius, so the half thickness is 12.7 / 38.1, one third.
|
|
2
2
|
import { drawStack } from "./ball.stack.js";
|
|
3
3
|
import { defineObject } from "./ball.object.js";
|
|
4
|
-
const H = 25.4 / 76.2
|
|
4
|
+
const H = 25.4 / 76.2;
|
|
5
5
|
export const BALL_PUCK = /* @__PURE__ */ defineObject({
|
|
6
6
|
id: "ball:puck",
|
|
7
7
|
en: "Hockey puck",
|
|
@@ -13,8 +13,9 @@ export const BALL_RUGBY = /* @__PURE__ */ defineBall({
|
|
|
13
13
|
ja: "ラグビーボール",
|
|
14
14
|
size: { extentMm: 290.0, lengthMm: 290.0, circumferenceMm: 600.0, longCircumferenceMm: 755.0, massG: [410, 460], rule: "World Rugby Laws of the Game (Rugby Union), Law 2 The ball, clauses 1-7" },
|
|
15
15
|
spec: {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// Worked out from the Laws' 600 mm girth, 290 mm length and 755 mm long girth.
|
|
17
|
+
axes: [1, 0.6586, 0.6586],
|
|
18
|
+
point: 0.561,
|
|
18
19
|
view: { yaw: -30, pitch: 20, roll: -30 },
|
|
19
20
|
base: "#1f4e9c",
|
|
20
21
|
ground: WHITE,
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export declare const BALL_SHUTTLECOCK_FEATHER: import("./ball.core.ts").Piece<{
|
|
2
2
|
feathers: number;
|
|
3
|
+
} & {
|
|
4
|
+
view: import("./ball.object.ts").ObjectView;
|
|
3
5
|
}>;
|
|
4
6
|
export declare const BALL_SHUTTLECOCK_NYLON: import("./ball.core.ts").Piece<{
|
|
5
7
|
feathers: number;
|
|
8
|
+
} & {
|
|
9
|
+
view: import("./ball.object.ts").ObjectView;
|
|
6
10
|
}>;
|
|
@@ -4,12 +4,13 @@ import { defineObject } from "./ball.object.js";
|
|
|
4
4
|
import {} from "./ball.math.js";
|
|
5
5
|
import { blend, mix, num } from "./ball.core.js";
|
|
6
6
|
const FEATHERS = 16;
|
|
7
|
-
// One unit is 32 mm: the skirt is 2 units across at the
|
|
7
|
+
// One unit is 32 mm (the scale below makes 92 box units 150 mm): the skirt is 2 units across at the tips (63 mm), the base 0.84 across (27 mm) and the feathers 2.05 long from the tips to the top of the base
|
|
8
|
+
// (66 mm; the Law measures a feather's length that way, 62 to 70 mm).
|
|
8
9
|
const ROOT_R = 0.42;
|
|
9
10
|
const TIP_R = 1;
|
|
10
|
-
const ROOT_Y = -
|
|
11
|
+
const ROOT_Y = -1.05;
|
|
11
12
|
const TIP_Y = 1.0;
|
|
12
|
-
const SLOPE = 0.
|
|
13
|
+
const SLOPE = 0.56;
|
|
13
14
|
const drawShuttle = (c, p) => {
|
|
14
15
|
const line = c.opts.style === "line";
|
|
15
16
|
const tip = (a, r, y, k = 0) => [Math.cos(a) * r, y, Math.sin(a) * r + k];
|
|
@@ -72,7 +73,7 @@ const VIEW = { yaw: 0, pitch: 22, roll: -32 };
|
|
|
72
73
|
export const BALL_SHUTTLECOCK_FEATHER = /* @__PURE__ */ defineObject({
|
|
73
74
|
id: "ball:shuttlecock-feather",
|
|
74
75
|
en: "Shuttlecock, feather",
|
|
75
|
-
ja: "
|
|
76
|
+
ja: "水鳥シャトル",
|
|
76
77
|
size: { extentMm: 150, diameterMm: 63, lengthMm: 66, massG: [4.74, 5.5], rule: "BWF Laws of Badminton, Law 2 Shuttle (2.1 general, 2.2 Feathered shuttle 2.2.1-2.2.6, 2.3 Non-feathered shuttle, 2.4 modifications) and Laws 3 Testing a shuttle for speed", note: "62 to 70 mm long with the tips on a circle 58 to 68 mm across, on a base 25 to 28 mm across; the box is drawn at the scale of the shuttlecock, which fills a part of it." },
|
|
77
78
|
view: VIEW,
|
|
78
79
|
scale: 0.43,
|
|
@@ -83,7 +84,7 @@ export const BALL_SHUTTLECOCK_FEATHER = /* @__PURE__ */ defineObject({
|
|
|
83
84
|
export const BALL_SHUTTLECOCK_NYLON = /* @__PURE__ */ defineObject({
|
|
84
85
|
id: "ball:shuttlecock-nylon",
|
|
85
86
|
en: "Shuttlecock, nylon",
|
|
86
|
-
ja: "
|
|
87
|
+
ja: "ナイロンシャトル",
|
|
87
88
|
size: { extentMm: 150, diameterMm: 63, lengthMm: 66, massG: [4.74, 5.5], rule: "BWF Laws of Badminton, Law 2 Shuttle (2.1 general, 2.2 Feathered shuttle 2.2.1-2.2.6, 2.3 Non-feathered shuttle, 2.4 modifications) and Laws 3 Testing a shuttle for speed", note: "A non-feathered shuttle follows the same measures, with a tolerance of 10 per cent (Law 2.3); the box is drawn at the scale of the shuttlecock, which fills a part of it." },
|
|
88
89
|
view: VIEW,
|
|
89
90
|
scale: 0.43,
|
|
@@ -20,8 +20,8 @@ function squash(id, en, ja, colour, count, size) {
|
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
const SIZE = { extentMm: 40.0, diameterMm: 40.0, massG: [23.0, 25.0], rule: "World Squash 'Specifications for Squash Balls' (Recommended standards approved by the WSF; updated WSF AGM 2021, rebranded 2025), sections 1-3 and Notes 2-4", note: "Size and weight are set for the double yellow, yellow and green dot balls; the red and blue dot balls carry no limits in the specification." };
|
|
23
|
-
export const BALL_SQUASH_DOUBLE_YELLOW = /* @__PURE__ */ squash("ball:squash", "Squash ball,
|
|
24
|
-
export const BALL_SQUASH_YELLOW = /* @__PURE__ */ squash("ball:squash-yellow", "Squash ball, yellow dot", "
|
|
25
|
-
export const BALL_SQUASH_RED = /* @__PURE__ */ squash("ball:squash-red", "Squash ball, red dot", "
|
|
26
|
-
export const BALL_SQUASH_BLUE = /* @__PURE__ */ squash("ball:squash-blue", "Squash ball, blue dot", "
|
|
27
|
-
export const BALL_SQUASH_GREEN = /* @__PURE__ */ squash("ball:squash-green", "Squash ball, green dot", "
|
|
23
|
+
export const BALL_SQUASH_DOUBLE_YELLOW = /* @__PURE__ */ squash("ball:squash", "Squash ball, 2 yellow dots", "スカッシュ球(ダブルイエロー)", "#f4d41c", 2, SIZE);
|
|
24
|
+
export const BALL_SQUASH_YELLOW = /* @__PURE__ */ squash("ball:squash-yellow", "Squash ball, yellow dot", "スカッシュ球(黄色)", "#f4d41c", 1, SIZE);
|
|
25
|
+
export const BALL_SQUASH_RED = /* @__PURE__ */ squash("ball:squash-red", "Squash ball, red dot", "スカッシュ球(赤)", "#d9262c", 1, SIZE);
|
|
26
|
+
export const BALL_SQUASH_BLUE = /* @__PURE__ */ squash("ball:squash-blue", "Squash ball, blue dot", "スカッシュ球(青)", "#2b6fd6", 1, SIZE);
|
|
27
|
+
export const BALL_SQUASH_GREEN = /* @__PURE__ */ squash("ball:squash-green", "Squash ball, green dot", "スカッシュ球(緑)", "#2aa84a", 1, SIZE);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { Emblem, EmblemOptions, EmblemStyle, EmblemTheme, FrameOptions } from "./emblem.core.ts";
|
|
1
|
+
import type { Emblem, EmblemLocale, EmblemOptions, EmblemStyle, EmblemTheme, FrameOptions } from "./emblem.core.ts";
|
|
2
2
|
import { type BallId, type CrestShape, type EmblemId, type MedalKind, type SportName } from "./emblem.ids.ts";
|
|
3
3
|
import type { PieceSize } from "./ball/ball.core.ts";
|
|
4
|
+
import { type EmblemDescription } from "./emblem.descriptions.ts";
|
|
4
5
|
/** What can name an emblem: an id, a short name (`"cup"` for `"trophy:cup"`), either with a style (`"trophy:cup/line"`), or the emblem itself. */
|
|
5
6
|
export type EmblemRef = EmblemId | `${EmblemId}/${EmblemStyle}` | (string & {}) | Emblem;
|
|
6
7
|
/** The id and style a string names, or `undefined` when it is not an emblem. An id may end in `/metal`, `/line` or `/color`, and a trophy or an award may be written without its group (`cup`, `crown`). */
|
|
@@ -54,6 +55,8 @@ export interface EmblemInfo {
|
|
|
54
55
|
kind: Emblem["kind"];
|
|
55
56
|
/** A ball's or an implement's official size, with the rule it is from, where the rulebook gives one. */
|
|
56
57
|
size?: PieceSize;
|
|
58
|
+
/** What is drawn, in more words than the short name (which is the accessible name), for the docs and a gallery; not every emblem has one. */
|
|
59
|
+
description?: EmblemDescription;
|
|
57
60
|
}
|
|
58
61
|
/** The names, source and kind of an emblem. */
|
|
59
62
|
export declare function emblemInfo(id: EmblemId): EmblemInfo;
|
|
@@ -72,6 +75,10 @@ export interface CrestOptions {
|
|
|
72
75
|
size?: number;
|
|
73
76
|
style?: EmblemStyle;
|
|
74
77
|
theme?: EmblemTheme;
|
|
78
|
+
/** The language of the `<title>`, `"en"` (the default) or `"ja"`. */
|
|
79
|
+
locale?: EmblemLocale;
|
|
80
|
+
/** A string is the `<title>`; `false` drops it and hides the drawing from a screen reader. */
|
|
81
|
+
title?: string | false;
|
|
75
82
|
}
|
|
76
83
|
/**
|
|
77
84
|
* A generic crest tinted by a team's colours, with its letters if it has any: what a team with no logo gets. The letters are in the ink that reads on the colour.
|
|
@@ -88,3 +95,34 @@ export declare function crest(options?: CrestOptions): string;
|
|
|
88
95
|
export declare function medal(kind: MedalKind, options?: EmblemOptions): string;
|
|
89
96
|
/** The icon of a sport, by the names of `SPORT_NAMES`: `sportIcon("nhl")`. An unknown sport is an error. */
|
|
90
97
|
export declare function sportIcon(sport: SportName, options?: EmblemOptions): string;
|
|
98
|
+
/** What `pennant` takes. */
|
|
99
|
+
export interface PennantOptions {
|
|
100
|
+
/** The felt, the stitching, and the lettering (the stitching's colour unless a third is given), as hex colours or `rgb()`. */
|
|
101
|
+
colors?: string | readonly [felt?: string, stitching?: string, lettering?: string];
|
|
102
|
+
/** The words, in capitals ("Champions", "優勝"); more than one line with line breaks. The engraving options `label`, `rank` and `year` work too. */
|
|
103
|
+
text?: string;
|
|
104
|
+
/** The label and rank, as for any engraving: ignored when `text` is given. */
|
|
105
|
+
label?: string;
|
|
106
|
+
rank?: number | string;
|
|
107
|
+
/** The year, in smaller letters under the words. */
|
|
108
|
+
year?: number | string;
|
|
109
|
+
/** `"front"` (the default) or `"back"`: plain felt, with the dowel on the other side. */
|
|
110
|
+
view?: "front" | "back";
|
|
111
|
+
size?: number;
|
|
112
|
+
style?: EmblemStyle;
|
|
113
|
+
theme?: EmblemTheme;
|
|
114
|
+
/** A turn from the resting view, in degrees. */
|
|
115
|
+
yaw?: number;
|
|
116
|
+
pitch?: number;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A felt championship pennant with a stitched edge, lettered with its words and its year.
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* import { pennant } from "@johnmorrisdotca/dougu-sports/emblems";
|
|
123
|
+
* const svg = pennant({ colors: ["#1f4e8c", "#f2c230"], text: "Champions", year: 2026 });
|
|
124
|
+
* console.log(svg.includes(">CHAMPIONS<"), svg.includes(">2026<"));
|
|
125
|
+
* // true true
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export declare function pennant(options?: PennantOptions): string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EMBLEM_STYLES } from "./emblem.core.js";
|
|
2
2
|
import { BALL_IDS, CREST_SHAPES, EMBLEM_ALIASES, EMBLEM_IDS } from "./emblem.ids.js";
|
|
3
|
+
import { EMBLEM_DESCRIPTIONS } from "./emblem.descriptions.js";
|
|
3
4
|
import { EMBLEM_TABLE } from "./emblem.registry.js";
|
|
4
5
|
/** The id and style a string names, or `undefined` when it is not an emblem. An id may end in `/metal`, `/line` or `/color`, and a trophy or an award may be written without its group (`cup`, `crown`). */
|
|
5
6
|
export function parseEmblem(value) {
|
|
@@ -78,7 +79,8 @@ export const EMBLEMS = Object.freeze(Object.fromEntries(EMBLEM_IDS.map((id) => [
|
|
|
78
79
|
export function emblemInfo(id) {
|
|
79
80
|
const e = EMBLEM_TABLE[id];
|
|
80
81
|
const size = e.size;
|
|
81
|
-
|
|
82
|
+
const description = EMBLEM_DESCRIPTIONS[id];
|
|
83
|
+
return { id, en: e.en, ja: e.ja, source: e.source, kind: e.kind, ...(size === undefined ? {} : { size }), ...(description === undefined ? {} : { description }) };
|
|
82
84
|
}
|
|
83
85
|
/** An emblem's name in a language. */
|
|
84
86
|
export function emblemName(id, language = "en") {
|
|
@@ -101,7 +103,7 @@ export function emblemModel(id) {
|
|
|
101
103
|
export function crest(options = {}) {
|
|
102
104
|
const colors = typeof options.colors === "string" ? [options.colors] : (options.colors ?? []);
|
|
103
105
|
const shape = CREST_SHAPES.includes(options.shape) ? options.shape : "shield";
|
|
104
|
-
return EMBLEM_TABLE[`crest:${shape}`].svg({ tint: colors[0], accent: colors[1], initials: options.initials, size: options.size, style: options.style, theme: options.theme });
|
|
106
|
+
return EMBLEM_TABLE[`crest:${shape}`].svg({ tint: colors[0], accent: colors[1], initials: options.initials, size: options.size, style: options.style, theme: options.theme, locale: options.locale, title: options.title });
|
|
105
107
|
}
|
|
106
108
|
/** A medal on a ribbon: `medal("gold")`, `"silver"` or `"bronze"`; any other kind is the gold one. */
|
|
107
109
|
export function medal(kind, options) {
|
|
@@ -114,3 +116,19 @@ export function sportIcon(sport, options) {
|
|
|
114
116
|
throw new Error(`Unknown sport "${String(sport).slice(0, 40)}": the icons are for the names in SPORT_NAMES.`);
|
|
115
117
|
return e.svg(options);
|
|
116
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* A felt championship pennant with a stitched edge, lettered with its words and its year.
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* import { pennant } from "@johnmorrisdotca/dougu-sports/emblems";
|
|
124
|
+
* const svg = pennant({ colors: ["#1f4e8c", "#f2c230"], text: "Champions", year: 2026 });
|
|
125
|
+
* console.log(svg.includes(">CHAMPIONS<"), svg.includes(">2026<"));
|
|
126
|
+
* // true true
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export function pennant(options = {}) {
|
|
130
|
+
const colors = typeof options.colors === "string" ? [options.colors] : (options.colors ?? []);
|
|
131
|
+
const o = { tint: colors[0], accent: colors[1], numberColor: colors[2], engrave: options.text, label: options.label, rank: options.rank, year: options.year, view: options.view, size: options.size, style: options.style, theme: options.theme, yaw: options.yaw, pitch: options.pitch };
|
|
132
|
+
const e = EMBLEM_TABLE["pennant:championship"];
|
|
133
|
+
return options.yaw === undefined && options.pitch === undefined ? e.svg(o) : e.frame(o);
|
|
134
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** An emblem's document taken apart. */
|
|
2
|
+
export interface Parts {
|
|
3
|
+
/** The view box, `0 0 64 64` unless the drawing came from elsewhere. */
|
|
4
|
+
box: string;
|
|
5
|
+
/** The definitions, one string for each top-level element of the `<defs>` (a gradient, a clip path, a filter), with the id it carries. */
|
|
6
|
+
defs: readonly {
|
|
7
|
+
id: string;
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
/** Everything that draws: the body, with the `<title>`, `<style>` and `<defs>` taken out. A `<desc>` stays, because it carries the credit of copied art. */
|
|
11
|
+
body: string;
|
|
12
|
+
}
|
|
13
|
+
/** Takes an emblem's document apart. */
|
|
14
|
+
export declare function partsOf(svg: string): Parts;
|
|
15
|
+
/**
|
|
16
|
+
* Documents put together: the definitions of all of them in one list with none twice, and every id renamed so that two different definitions never share one. With a `prefix` the ids are the prefix and a short
|
|
17
|
+
* counter, so a sheet's ids cannot meet the page's own; with none an id keeps its name until a different definition wants it.
|
|
18
|
+
*/
|
|
19
|
+
export declare class IdSpace {
|
|
20
|
+
private readonly named;
|
|
21
|
+
private readonly taken;
|
|
22
|
+
private readonly prefix;
|
|
23
|
+
private count;
|
|
24
|
+
readonly defs: string[];
|
|
25
|
+
constructor(prefix?: string);
|
|
26
|
+
private fresh;
|
|
27
|
+
/** Takes one document's parts in: its definitions join the list, and its body comes back with the ids it uses. */
|
|
28
|
+
add(parts: Parts): string;
|
|
29
|
+
}
|
|
30
|
+
/** A document nested inside another: its box becomes the place the parent gives it. */
|
|
31
|
+
export declare function nest(box: string, body: string, x: number, y: number, width: number, height: number): string;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// The pieces that the sprite, the podium and the award case share: an emblem's document taken apart (its view box, its definitions, its body), and put together again inside another document with
|
|
2
|
+
// every id unique. The emblems are written by this package, so the parts are found by their shape, not by a general XML parser.
|
|
3
|
+
/** The top-level elements of a run of markup, each with its `id` attribute (an empty string when it has none). */
|
|
4
|
+
function topLevel(markup) {
|
|
5
|
+
const out = [];
|
|
6
|
+
const tag = /<(\/?)[A-Za-z][\w:-]*[^>]*?(\/?)>/g;
|
|
7
|
+
let depth = 0;
|
|
8
|
+
let start = 0;
|
|
9
|
+
for (let m = tag.exec(markup); m !== null; m = tag.exec(markup)) {
|
|
10
|
+
if (m[1] === "/") {
|
|
11
|
+
depth--;
|
|
12
|
+
if (depth === 0)
|
|
13
|
+
out.push({ id: "", text: markup.slice(start, m.index + m[0].length) });
|
|
14
|
+
}
|
|
15
|
+
else if (m[2] === "/") {
|
|
16
|
+
if (depth === 0)
|
|
17
|
+
out.push({ id: "", text: m[0] });
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
if (depth === 0)
|
|
21
|
+
start = m.index;
|
|
22
|
+
depth++;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return out.map((e) => ({ id: /\sid="([^"]*)"/.exec(e.text)?.[1] ?? "", text: e.text }));
|
|
26
|
+
}
|
|
27
|
+
/** Takes an emblem's document apart. */
|
|
28
|
+
export function partsOf(svg) {
|
|
29
|
+
const open = /^<svg\s[^>]*>/.exec(svg);
|
|
30
|
+
const box = (open === null ? undefined : /\sviewBox="([^"]+)"/.exec(open[0])?.[1]) ?? "0 0 64 64";
|
|
31
|
+
let inner = open === null ? svg : svg.slice(open[0].length, svg.lastIndexOf("</svg>"));
|
|
32
|
+
inner = inner.replace(/<title>[^<]*<\/title>/, "").replace(/<style>[^<]*<\/style>/, "");
|
|
33
|
+
const defs = [];
|
|
34
|
+
inner = inner.replace(/<defs>([\s\S]*?)<\/defs>/g, (_, content) => {
|
|
35
|
+
defs.push(...topLevel(content));
|
|
36
|
+
return "";
|
|
37
|
+
});
|
|
38
|
+
return { box, defs, body: inner };
|
|
39
|
+
}
|
|
40
|
+
/** The ids a document of ours refers to: `id="x"`, `url(#x)` and `href="#x"`. */
|
|
41
|
+
const REFERENCE = /(\sid="|url\(#|href="#)([^")]+)/g;
|
|
42
|
+
/**
|
|
43
|
+
* Documents put together: the definitions of all of them in one list with none twice, and every id renamed so that two different definitions never share one. With a `prefix` the ids are the prefix and a short
|
|
44
|
+
* counter, so a sheet's ids cannot meet the page's own; with none an id keeps its name until a different definition wants it.
|
|
45
|
+
*/
|
|
46
|
+
export class IdSpace {
|
|
47
|
+
named = new Map();
|
|
48
|
+
taken = new Set();
|
|
49
|
+
prefix;
|
|
50
|
+
count = 0;
|
|
51
|
+
defs = [];
|
|
52
|
+
constructor(prefix = "") {
|
|
53
|
+
this.prefix = prefix;
|
|
54
|
+
}
|
|
55
|
+
fresh(id) {
|
|
56
|
+
if (this.prefix === "") {
|
|
57
|
+
let candidate = id;
|
|
58
|
+
while (this.taken.has(candidate))
|
|
59
|
+
candidate = `${id}-${(++this.count).toString(36)}`;
|
|
60
|
+
return candidate;
|
|
61
|
+
}
|
|
62
|
+
return `${this.prefix}_${(this.count++).toString(36)}`;
|
|
63
|
+
}
|
|
64
|
+
/** Takes one document's parts in: its definitions join the list, and its body comes back with the ids it uses. */
|
|
65
|
+
add(parts) {
|
|
66
|
+
const rename = new Map();
|
|
67
|
+
const added = [];
|
|
68
|
+
for (const def of parts.defs) {
|
|
69
|
+
if (def.id === "")
|
|
70
|
+
continue;
|
|
71
|
+
const key = `${def.id}\u0000${def.text}`;
|
|
72
|
+
let name = this.named.get(key);
|
|
73
|
+
if (name === undefined) {
|
|
74
|
+
name = this.fresh(def.id);
|
|
75
|
+
this.taken.add(name);
|
|
76
|
+
this.named.set(key, name);
|
|
77
|
+
added.push(def.text);
|
|
78
|
+
}
|
|
79
|
+
rename.set(def.id, name);
|
|
80
|
+
}
|
|
81
|
+
const apply = (text) => text.replace(REFERENCE, (all, lead, id) => (rename.has(id) ? `${lead}${rename.get(id)}` : all));
|
|
82
|
+
for (const text of added)
|
|
83
|
+
this.defs.push(apply(text));
|
|
84
|
+
return apply(parts.body);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** A document nested inside another: its box becomes the place the parent gives it. */
|
|
88
|
+
export function nest(box, body, x, y, width, height) {
|
|
89
|
+
const n = (v) => String(Math.round(v * 100) / 100 + 0);
|
|
90
|
+
return `<svg x="${n(x)}" y="${n(y)}" width="${n(width)}" height="${n(height)}" viewBox="${box}">${body}</svg>`;
|
|
91
|
+
}
|