@johnmorrisdotca/dougu-sports 0.1.0 → 0.2.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 (184) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/NOTICE.md +9 -58
  3. package/README.md +168 -32
  4. package/dist/emblems/award/badge.d.ts +2 -2
  5. package/dist/emblems/award/badge.js +14 -5
  6. package/dist/emblems/award/crown.d.ts +1 -1
  7. package/dist/emblems/award/crown.js +14 -4
  8. package/dist/emblems/award/medal.d.ts +2 -2
  9. package/dist/emblems/award/medal.js +14 -5
  10. package/dist/emblems/award/shield.d.ts +1 -1
  11. package/dist/emblems/award/shield.js +10 -4
  12. package/dist/emblems/award/star.d.ts +1 -1
  13. package/dist/emblems/award/star.js +10 -4
  14. package/dist/emblems/ball/ball.afl.js +1 -1
  15. package/dist/emblems/ball/ball.basketball6.js +1 -1
  16. package/dist/emblems/ball/ball.core.d.ts +9 -3
  17. package/dist/emblems/ball/ball.core.js +13 -0
  18. package/dist/emblems/ball/ball.cricket.js +1 -1
  19. package/dist/emblems/ball/ball.engine.js +3 -3
  20. package/dist/emblems/ball/ball.football.js +3 -3
  21. package/dist/emblems/ball/ball.gaelic.js +1 -1
  22. package/dist/emblems/ball/ball.object.js +2 -2
  23. package/dist/emblems/ball/ball.shuttlecock.js +2 -2
  24. package/dist/emblems/ball/ball.squash.js +5 -5
  25. package/dist/emblems/emblem.api.d.ts +39 -1
  26. package/dist/emblems/emblem.api.js +20 -2
  27. package/dist/emblems/emblem.compose.d.ts +31 -0
  28. package/dist/emblems/emblem.compose.js +91 -0
  29. package/dist/emblems/emblem.core.d.ts +48 -2
  30. package/dist/emblems/emblem.core.js +30 -0
  31. package/dist/emblems/emblem.descriptions.d.ts +7 -0
  32. package/dist/emblems/emblem.descriptions.js +60 -0
  33. package/dist/emblems/emblem.display.d.ts +75 -0
  34. package/dist/emblems/emblem.display.js +214 -0
  35. package/dist/emblems/emblem.engrave.d.ts +50 -0
  36. package/dist/emblems/emblem.engrave.js +145 -0
  37. package/dist/emblems/emblem.equipment.d.ts +24 -0
  38. package/dist/emblems/emblem.equipment.js +57 -0
  39. package/dist/emblems/emblem.flip.d.ts +10 -0
  40. package/dist/emblems/emblem.flip.js +45 -0
  41. package/dist/emblems/emblem.ids.d.ts +19 -11
  42. package/dist/emblems/emblem.ids.js +21 -6
  43. package/dist/emblems/emblem.lathe.d.ts +3 -0
  44. package/dist/emblems/emblem.lathe.js +26 -4
  45. package/dist/emblems/emblem.medal.js +4 -4
  46. package/dist/emblems/emblem.pennant.d.ts +3 -0
  47. package/dist/emblems/emblem.pennant.js +79 -0
  48. package/dist/emblems/emblem.registry.js +20 -0
  49. package/dist/emblems/emblem.shapes.d.ts +19 -2
  50. package/dist/emblems/emblem.shapes.js +53 -12
  51. package/dist/emblems/emblem.sprite.d.ts +38 -0
  52. package/dist/emblems/emblem.sprite.js +65 -0
  53. package/dist/emblems/emblem.uri.d.ts +22 -0
  54. package/dist/emblems/emblem.uri.js +29 -0
  55. package/dist/emblems/gear/gear.badmintonracket.d.ts +1 -1
  56. package/dist/emblems/gear/gear.badmintonracket.js +18 -14
  57. package/dist/emblems/gear/gear.baseballglove.d.ts +1 -1
  58. package/dist/emblems/gear/gear.baseballglove.js +20 -14
  59. package/dist/emblems/gear/gear.budo.d.ts +17 -0
  60. package/dist/emblems/gear/gear.budo.js +60 -0
  61. package/dist/emblems/gear/gear.core.d.ts +12 -31
  62. package/dist/emblems/gear/gear.core.js +61 -54
  63. package/dist/emblems/gear/gear.cricketbat.d.ts +1 -1
  64. package/dist/emblems/gear/gear.cricketbat.js +16 -14
  65. package/dist/emblems/gear/gear.fieldhockeystick.d.ts +1 -1
  66. package/dist/emblems/gear/gear.fieldhockeystick.js +15 -14
  67. package/dist/emblems/gear/gear.footballhelmet.d.ts +1 -1
  68. package/dist/emblems/gear/gear.footballhelmet.js +15 -14
  69. package/dist/emblems/gear/gear.goaliemask.d.ts +1 -1
  70. package/dist/emblems/gear/gear.goaliemask.js +22 -16
  71. package/dist/emblems/gear/gear.goalnet.d.ts +1 -1
  72. package/dist/emblems/gear/gear.goalnet.js +24 -14
  73. package/dist/emblems/gear/gear.hockeystick.d.ts +1 -1
  74. package/dist/emblems/gear/gear.hockeystick.js +14 -14
  75. package/dist/emblems/gear/gear.index.js +5 -1
  76. package/dist/emblems/gear/gear.kendo.d.ts +2 -0
  77. package/dist/emblems/gear/gear.kendo.js +41 -0
  78. package/dist/emblems/gear/gear.kyudo.d.ts +2 -0
  79. package/dist/emblems/gear/gear.kyudo.js +31 -0
  80. package/dist/emblems/gear/gear.lacrossestick.d.ts +1 -1
  81. package/dist/emblems/gear/gear.lacrossestick.js +25 -14
  82. package/dist/emblems/gear/gear.skates.js +1 -1
  83. package/dist/emblems/gear/gear.sticks.js +2 -2
  84. package/dist/emblems/gear/gear.sumo.d.ts +2 -0
  85. package/dist/emblems/gear/gear.sumo.js +43 -0
  86. package/dist/emblems/gear/gear.tabletennispaddle.d.ts +1 -1
  87. package/dist/emblems/gear/gear.tabletennispaddle.js +15 -14
  88. package/dist/emblems/gear/gear.tennisracket.d.ts +1 -1
  89. package/dist/emblems/gear/gear.tennisracket.js +16 -14
  90. package/dist/emblems/pennant/championship.d.ts +2 -0
  91. package/dist/emblems/pennant/championship.js +4 -0
  92. package/dist/emblems/sport/afl.js +1 -1
  93. package/dist/emblems/sport/artisticswimming.js +1 -1
  94. package/dist/emblems/sport/cfl.d.ts +1 -1
  95. package/dist/emblems/sport/cfl.js +4 -4
  96. package/dist/emblems/sport/cricket.d.ts +1 -1
  97. package/dist/emblems/sport/cricket.js +13 -4
  98. package/dist/emblems/sport/fieldhockey.js +1 -1
  99. package/dist/emblems/sport/gaelic.js +1 -1
  100. package/dist/emblems/sport/handball.d.ts +1 -1
  101. package/dist/emblems/sport/handball.js +14 -4
  102. package/dist/emblems/sport/ifaf.d.ts +1 -1
  103. package/dist/emblems/sport/ifaf.js +4 -4
  104. package/dist/emblems/sport/judo.d.ts +2 -0
  105. package/dist/emblems/sport/judo.js +13 -0
  106. package/dist/emblems/sport/karate.d.ts +2 -0
  107. package/dist/emblems/sport/karate.js +13 -0
  108. package/dist/emblems/sport/kendo.d.ts +2 -0
  109. package/dist/emblems/sport/kendo.js +14 -0
  110. package/dist/emblems/sport/kyudo.d.ts +2 -0
  111. package/dist/emblems/sport/kyudo.js +15 -0
  112. package/dist/emblems/sport/mlb.d.ts +1 -1
  113. package/dist/emblems/sport/mlb.js +27 -4
  114. package/dist/emblems/sport/nba.d.ts +1 -1
  115. package/dist/emblems/sport/nba.js +11 -4
  116. package/dist/emblems/sport/netball.js +1 -1
  117. package/dist/emblems/sport/nfl.d.ts +2 -1
  118. package/dist/emblems/sport/nfl.js +14 -4
  119. package/dist/emblems/sport/nhl.d.ts +1 -1
  120. package/dist/emblems/sport/nhl.js +12 -4
  121. package/dist/emblems/sport/pickleball.js +1 -1
  122. package/dist/emblems/sport/racquetball.js +1 -1
  123. package/dist/emblems/sport/roundnet.js +1 -1
  124. package/dist/emblems/sport/rugby.d.ts +1 -1
  125. package/dist/emblems/sport/rugby.js +11 -4
  126. package/dist/emblems/sport/sepaktakraw.js +1 -1
  127. package/dist/emblems/sport/soccer.d.ts +1 -1
  128. package/dist/emblems/sport/soccer.js +30 -4
  129. package/dist/emblems/sport/squash.js +1 -1
  130. package/dist/emblems/sport/sumo.d.ts +2 -0
  131. package/dist/emblems/sport/sumo.js +15 -0
  132. package/dist/emblems/sport/tabletennis.js +1 -1
  133. package/dist/emblems/sport/tennis.d.ts +1 -1
  134. package/dist/emblems/sport/tennis.js +14 -4
  135. package/dist/emblems/sport/teqball.js +1 -1
  136. package/dist/emblems/sport/volleyball.d.ts +1 -1
  137. package/dist/emblems/sport/volleyball.js +24 -4
  138. package/dist/emblems/trophy/banner.d.ts +2 -0
  139. package/dist/emblems/trophy/banner.js +20 -0
  140. package/dist/emblems/trophy/baseball.js +1 -0
  141. package/dist/emblems/trophy/basketball.js +1 -0
  142. package/dist/emblems/trophy/belt.js +2 -1
  143. package/dist/emblems/trophy/bowl.js +1 -0
  144. package/dist/emblems/trophy/canadian-bowl.js +1 -0
  145. package/dist/emblems/trophy/championship-banner.d.ts +2 -0
  146. package/dist/emblems/trophy/championship-banner.js +67 -0
  147. package/dist/emblems/trophy/cricket-urn.js +1 -0
  148. package/dist/emblems/trophy/cup.d.ts +1 -1
  149. package/dist/emblems/trophy/cup.js +15 -4
  150. package/dist/emblems/trophy/football.js +2 -1
  151. package/dist/emblems/trophy/globe.js +1 -0
  152. package/dist/emblems/trophy/hockey-cup.js +2 -1
  153. package/dist/emblems/trophy/lidded-cup.js +1 -0
  154. package/dist/emblems/trophy/plaque.js +2 -1
  155. package/dist/emblems/trophy/ring.d.ts +2 -0
  156. package/dist/emblems/trophy/ring.js +30 -0
  157. package/dist/emblems/trophy/rugby-cup.js +1 -0
  158. package/dist/emblems/trophy/salver.js +1 -0
  159. package/dist/emblems/trophy/soccer-ball.js +2 -1
  160. package/dist/emblems/trophy/soccer-globe.js +1 -0
  161. package/dist/emblems/trophy/sumo-cup.d.ts +2 -0
  162. package/dist/emblems/trophy/sumo-cup.js +28 -0
  163. package/dist/emblems/trophy/tennis-cup.js +2 -1
  164. package/dist/emblems/trophy/tennis-plate.js +1 -0
  165. package/dist/emblems-entry.d.ts +15 -6
  166. package/dist/emblems-entry.js +7 -2
  167. package/dist/index.d.ts +17 -0
  168. package/dist/index.js +15 -0
  169. package/dist/turn-control.d.ts +101 -0
  170. package/dist/turn-control.js +363 -0
  171. package/dist/turn-entry.d.ts +2 -0
  172. package/dist/turn-entry.js +1 -0
  173. package/dist/version.d.ts +1 -1
  174. package/dist/version.js +1 -1
  175. package/package.json +7 -1
  176. package/LICENSES/Apache-2.0.txt +0 -202
  177. package/LICENSES/CC-BY-3.0.txt +0 -319
  178. package/LICENSES/upstream/game-icons.license.txt +0 -45
  179. package/LICENSES/upstream/noto-emoji-2D-svg.LICENSE +0 -13
  180. package/LICENSES/upstream/pictogrammers-mdi.LICENSE +0 -20
  181. package/dist/emblems/emblem.material.d.ts +0 -9
  182. package/dist/emblems/emblem.material.data.d.ts +0 -34
  183. package/dist/emblems/emblem.material.data.js +0 -85
  184. package/dist/emblems/emblem.material.js +0 -23
@@ -1,2 +1,2 @@
1
- /** Medal. */
2
- export declare const AWARD_MEDAL: import("../emblem.core.ts").Emblem<import("../emblem.material.ts").MaterialModel>;
1
+ /** Medal on a ribbon. */
2
+ export declare const AWARD_MEDAL: import("../emblem.core.ts").Emblem<import("../emblem.shapes.ts").FlatModel>;
@@ -1,5 +1,14 @@
1
- // Medal: the Material Symbols icon `military_tech` (Google, Apache License 2.0; see NOTICE.md), in metal, line and flat colour.
2
- import { M_MILITARY_TECH } from "../emblem.material.data.js";
3
- import { defineMaterial } from "../emblem.material.js";
4
- /** Medal. */
5
- export const AWARD_MEDAL = /* @__PURE__ */ defineMaterial("award:medal", "Medal", "メダル", M_MILITARY_TECH, "gold");
1
+ // Medal: a ribbon with a notched end and a star hanging from it, drawn for this package, in metal, line and flat colour.
2
+ import { defineIcon, starPath } from "../emblem.shapes.js";
3
+ /** Medal on a ribbon. */
4
+ export const AWARD_MEDAL = /* @__PURE__ */ defineIcon("award:medal", "Medal", "メダル", "ours", {
5
+ metal: "gold",
6
+ ribbon: "#c0392b",
7
+ front: [
8
+ ["r", "M21 3h22v26L32 23.4 21 29z"],
9
+ ["e", "M26 3v22M38 3v22", "1.4"],
10
+ ["m", "M32 27.4a2.6 2.6 0 1 1 0 5.2a2.6 2.6 0 1 1 0-5.2z"],
11
+ ["m", starPath(32, 46, 16.4, 7)],
12
+ ["l", "M32 34.6l3.4 8-9.4 1.4z"],
13
+ ],
14
+ });
@@ -1,2 +1,2 @@
1
1
  /** Shield. */
2
- export declare const AWARD_SHIELD: import("../emblem.core.ts").Emblem<import("../emblem.material.ts").MaterialModel>;
2
+ export declare const AWARD_SHIELD: import("../emblem.core.ts").Emblem<import("../emblem.shapes.ts").FlatModel>;
@@ -1,5 +1,11 @@
1
- // Shield: the Material Symbols icon `shield` (Google, Apache License 2.0; see NOTICE.md), in metal, line and flat colour.
2
- import { M_SHIELD } from "../emblem.material.data.js";
3
- import { defineMaterial } from "../emblem.material.js";
1
+ // Shield: a shield with a bevelled face, drawn for this package, in metal, line and flat colour.
2
+ import { defineIcon } from "../emblem.shapes.js";
4
3
  /** Shield. */
5
- export const AWARD_SHIELD = /* @__PURE__ */ defineMaterial("award:shield", "Shield", "盾", M_SHIELD, "silver");
4
+ export const AWARD_SHIELD = /* @__PURE__ */ defineIcon("award:shield", "Shield", "盾", "ours", {
5
+ metal: "silver",
6
+ front: [
7
+ ["m", "M32 4L10 12v18c0 14 9 25 22 30 13-5 22-16 22-30V12z"],
8
+ ["e", "M32 10.4L15.6 16.4V30c0 10.6 6.4 18.8 16.4 22.8 10-4 16.4-12.2 16.4-22.8V16.4z", "2"],
9
+ ["l", "M17 18l13-5v38C21 46 17 38 17 30z"],
10
+ ],
11
+ });
@@ -1,2 +1,2 @@
1
1
  /** Star. */
2
- export declare const AWARD_STAR: import("../emblem.core.ts").Emblem<import("../emblem.material.ts").MaterialModel>;
2
+ export declare const AWARD_STAR: import("../emblem.core.ts").Emblem<import("../emblem.shapes.ts").FlatModel>;
@@ -1,5 +1,11 @@
1
- // Star: the Material Symbols icon `star` (Google, Apache License 2.0; see NOTICE.md), in metal, line and flat colour.
2
- import { M_STAR } from "../emblem.material.data.js";
3
- import { defineMaterial } from "../emblem.material.js";
1
+ // Star: a five-pointed star with a bevelled face, drawn for this package, in metal, line and flat colour.
2
+ import { defineIcon, starPath } from "../emblem.shapes.js";
4
3
  /** Star. */
5
- export const AWARD_STAR = /* @__PURE__ */ defineMaterial("award:star", "Star", "星", M_STAR, "gold");
4
+ export const AWARD_STAR = /* @__PURE__ */ defineIcon("award:star", "Star", "星", "ours", {
5
+ metal: "gold",
6
+ front: [
7
+ ["m", starPath(32, 34, 29, 12.4)],
8
+ ["e", starPath(32, 35, 18.6, 7.8), "1.8"],
9
+ ["l", "M32 8.4l5 14.6-5 3.6-5-3.6z"],
10
+ ],
11
+ });
@@ -6,7 +6,7 @@ import { mix } from "./ball.core.js";
6
6
  export const BALL_AFL = /* @__PURE__ */ defineBall({
7
7
  id: "ball:afl",
8
8
  en: "Australian football",
9
- ja: "オーストラリアンフットボールのボール",
9
+ ja: "オージーボール",
10
10
  size: { extentMm: 280, lengthMm: 280, circumferenceMm: 550.0, longCircumferenceMm: 725.0, rule: "Laws of Australian Football 2026, Law 4 The Football, 4.1 Specifications", note: "The Laws give the two circumferences (720-730 mm and 545-555 mm) and no length: 280 mm is the length of an ellipse of those two girths." },
11
11
  spec: {
12
12
  axes: [1, 0.7, 0.7],
@@ -4,7 +4,7 @@ import { basketballSpec } from "./ball.basketball.js";
4
4
  export const BALL_BASKETBALL_6 = /* @__PURE__ */ defineBall({
5
5
  id: "ball:basketball-6",
6
6
  en: "Basketball, size 6",
7
- ja: "バスケットボール(6号球)",
7
+ ja: "バスケットボール6号球",
8
8
  size: { extentMm: 230.0, diameterMm: 230.0, circumferenceMm: 722.5, massG: [510, 550], rule: "FIBA Basketball Equipment 2026, Rule 2 Basketballs, 2.4 and Table 1 (size 6 = women's competitions)", note: "Diameter worked out from the middle of the circumference range." },
9
9
  spec: basketballSpec("#e57a26"),
10
10
  });
@@ -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" | "noto" | "mdi" | "commons" | "gameicons";
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 (a ball in 3D; gear is drawn as it is). */
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", "クリケットボール(ピンク)", "#e45a8c", "#2a2a2a", SIZE, "#4a0a22");
31
+ export const BALL_CRICKET_PINK = /* @__PURE__ */ cricket("ball:cricket-pink", "Cricket ball, pink", "ピンクのクリケット球", "#e45a8c", "#2a2a2a", SIZE, "#4a0a22");
@@ -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
  }
@@ -46,6 +46,6 @@ function football(id, en, ja, size, stripes, colour) {
46
46
  });
47
47
  }
48
48
  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)", "アメリカンフットボールのボール(NFL)", SIZE, "none", BROWN);
50
- 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" }, "ring", BROWN);
51
- 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." }, "top", "#a8693a");
49
+ export const BALL_FOOTBALL = /* @__PURE__ */ football("ball:football", "Football (NFL)", "アメフトボール(NFL)", SIZE, "none", BROWN);
50
+ 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" }, "ring", BROWN);
51
+ 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." }, "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 },
@@ -1,5 +1,5 @@
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
5
  export function defineObject(def) {
@@ -16,5 +16,5 @@ export function defineObject(def) {
16
16
  const { defs, body } = def.draw({ rot, opts, turning, id, minWidth: 80 / opts.size, at });
17
17
  return wrapPiece({ title: escape(def.en), size: opts.size, theme: opts.theme, defs, body });
18
18
  };
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) };
19
+ return { id: def.id, en: def.en, ja: def.ja, source: "generated", kind: "ball", model: def.model, size: def.size, svg: labelled(def.en, def.ja, (o) => render(o, false)), frame: labelled(def.en, def.ja, (o) => render(o, true)) };
20
20
  }
@@ -72,7 +72,7 @@ const VIEW = { yaw: 0, pitch: 22, roll: -32 };
72
72
  export const BALL_SHUTTLECOCK_FEATHER = /* @__PURE__ */ defineObject({
73
73
  id: "ball:shuttlecock-feather",
74
74
  en: "Shuttlecock, feather",
75
- ja: "シャトルコック(水鳥の羽根)",
75
+ ja: "水鳥シャトル",
76
76
  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
77
  view: VIEW,
78
78
  scale: 0.43,
@@ -83,7 +83,7 @@ export const BALL_SHUTTLECOCK_FEATHER = /* @__PURE__ */ defineObject({
83
83
  export const BALL_SHUTTLECOCK_NYLON = /* @__PURE__ */ defineObject({
84
84
  id: "ball:shuttlecock-nylon",
85
85
  en: "Shuttlecock, nylon",
86
- ja: "シャトルコック(ナイロン)",
86
+ ja: "ナイロンシャトル",
87
87
  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
88
  view: VIEW,
89
89
  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, double yellow dot", "スカッシュボール(ダブルイエロードット)", "#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);
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
- return size === undefined ? { id, en: e.en, ja: e.ja, source: e.source, kind: e.kind } : { id, en: e.en, ja: e.ja, source: e.source, kind: e.kind, size };
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
+ }
@@ -6,6 +6,8 @@ export declare const EMBLEM_STYLES: readonly ["metal", "line", "color"];
6
6
  * one, for an image (an `<img>`, a data address) that cannot inherit. `light` and `dark` force one.
7
7
  */
8
8
  export type EmblemTheme = "inherit" | "auto" | "light" | "dark";
9
+ /** The languages an emblem's `<title>` can be written in. */
10
+ export type EmblemLocale = "en" | "ja";
9
11
  export interface EmblemOptions {
10
12
  /** `metal` (the default), `line` or `color`. */
11
13
  style?: EmblemStyle;
@@ -21,6 +23,27 @@ export interface EmblemOptions {
21
23
  initials?: string;
22
24
  /** A ball: force the grain of its surface on or off. By default it is on for a ball of 48 pixels or more that is not turning. */
23
25
  grain?: boolean;
26
+ /** The language of the `<title>`: `"en"` (the default) or `"ja"`, from the emblem's English or Japanese name. */
27
+ locale?: EmblemLocale;
28
+ /** The `<title>`: a string writes that title (escaped); `false` drops it and marks the drawing `aria-hidden="true"`, for an emblem that sits beside text that says the same. */
29
+ title?: string | false;
30
+ /**
31
+ * Text cut into the face of a medal or place badge, the plaque, the belt, and the plinth of a cup: any text, Japanese included, up to three lines (a line break, or a space between a word and a year, makes lines).
32
+ * It wins over `label`, `year` and `rank`.
33
+ */
34
+ engrave?: string;
35
+ /** The words to engrave ("MVP"), with `year` and `rank` as lines of their own. Ignored when `engrave` is given. */
36
+ label?: string;
37
+ /** A year, engraved small under the label (on a plaque, on its nameplate). A number or a string. */
38
+ year?: number | string;
39
+ /** A place or a number, engraved large ("1"). On a place badge it is the badge's own place unless given. */
40
+ rank?: number | string;
41
+ /** The engraving of a medal's back, shown when it is turned over. Without it the back is plain. */
42
+ engraveBack?: string;
43
+ /** The colour of a pennant's lettering when it is not the second colour. A hex colour or `rgb()`. */
44
+ numberColor?: string;
45
+ /** Which side of a pennant is drawn at rest: `"front"` (the default) or `"back"`. */
46
+ view?: "front" | "back";
24
47
  }
25
48
  /** A turn, in degrees from the resting view (0 and 0 is the still drawing): `yaw` about the vertical axis, `pitch` the tilt toward the eye. */
26
49
  export interface FrameOptions extends EmblemOptions {
@@ -46,8 +69,8 @@ export declare const METALS: {
46
69
  };
47
70
  /** A metal by name, or `flat` for a colour that is no metal (a crest's blue, a sport's plain ink). */
48
71
  export type Metal = keyof typeof METALS | "flat";
49
- /** Where an emblem's art comes from: drawn for this package (`ours`) or taken from a source listed in NOTICE.md. */
50
- export type EmblemSource = "ours" | "material" | "generated" | "drawn" | "noto" | "mdi" | "commons" | "gameicons";
72
+ /** Where an emblem's art comes from: drawn for this package (`ours`, `drawn`) or made by this package's own code (`generated`). Nothing is copied from another project. */
73
+ export type EmblemSource = "ours" | "generated" | "drawn";
51
74
  /** What an emblem is: a flat object (a front and a back, to flip), a ball (a sphere or a solid, turned in 3D), a body of revolution (`lathe`), an icon (one drawing, not turned) or a piece of gear (a flat drawing, not turned). */
52
75
  export type EmblemKind = "flat" | "ball" | "lathe" | "icon" | "gear";
53
76
  export type Rgb = readonly [number, number, number];
@@ -58,6 +81,19 @@ export declare const hexOf: (rgb: Rgb) => string;
58
81
  export declare function mix(rgb: Rgb, amount: number): string;
59
82
  /** Text made safe to write between tags and inside a double-quoted attribute. */
60
83
  export declare const escape: (text: string) => string;
84
+ /**
85
+ * The `<title>` of a finished drawing, as the options ask: a string is the title (escaped), `false` drops it and hides the drawing from a screen reader, and `locale: "ja"` writes the Japanese name. With none
86
+ * of the three the drawing is returned as it is.
87
+ */
88
+ export declare function relabel(svg: string, en: string, ja: string, o: {
89
+ locale?: unknown;
90
+ title?: unknown;
91
+ } | undefined): string;
92
+ /** A drawing function whose result carries the title the options ask for. */
93
+ export declare const labelled: <O extends {
94
+ locale?: unknown;
95
+ title?: unknown;
96
+ }>(en: string, ja: string, draw: (options?: O) => string) => (options?: O) => string;
61
97
  /** A number written for a path: two decimals at most, and never `-0`. */
62
98
  export declare const n2: (value: number) => string;
63
99
  /** The options as the renderers use them. */
@@ -70,6 +106,16 @@ export interface Resolved {
70
106
  initials: string;
71
107
  }
72
108
  export declare function resolve(o: EmblemOptions | undefined): Resolved;
109
+ /** What is to be cut into a face: the free text, or the label, year and rank, and the text of the back. A line is at most 24 letters and has no control character. */
110
+ export interface Engraving {
111
+ free?: string;
112
+ label?: string;
113
+ year?: string;
114
+ rank?: string;
115
+ back?: string;
116
+ }
117
+ /** A text from outside made safe: control characters removed, at most 24 letters a line and `lines` lines, or `undefined` when nothing is left. */
118
+ export declare const clean: (value: unknown, lines?: number) => string | undefined;
73
119
  /** The main colour of a metal, or the tint when there is one. */
74
120
  export declare const mainOf: (metal: Metal, tint: Rgb | undefined) => Rgb;
75
121
  /** The relative luminance of a colour, 0 to 1. */
@@ -29,6 +29,24 @@ export function mix(rgb, amount) {
29
29
  }
30
30
  /** Text made safe to write between tags and inside a double-quoted attribute. */
31
31
  export const escape = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
32
+ /**
33
+ * The `<title>` of a finished drawing, as the options ask: a string is the title (escaped), `false` drops it and hides the drawing from a screen reader, and `locale: "ja"` writes the Japanese name. With none
34
+ * of the three the drawing is returned as it is.
35
+ */
36
+ export function relabel(svg, en, ja, o) {
37
+ const title = o?.title;
38
+ const japanese = o?.locale === "ja";
39
+ if (title === false)
40
+ return svg.replace(/<title>[^<]*<\/title>/, "").replace(' role="img"', ' aria-hidden="true"');
41
+ const custom = typeof title === "string" && title.trim() !== "";
42
+ if (!custom && !japanese)
43
+ return svg;
44
+ const text = custom ? title.trim().slice(0, 300) : ja;
45
+ const titled = svg.replace(/<title>[^<]*<\/title>/, () => `<title>${escape(text)}</title>`);
46
+ return custom ? titled : titled.replace(' role="img"', ' role="img" lang="ja"');
47
+ }
48
+ /** A drawing function whose result carries the title the options ask for. */
49
+ export const labelled = (en, ja, draw) => (options) => relabel(draw(options), en, ja, options);
32
50
  /** A number written for a path: two decimals at most, and never `-0`. */
33
51
  export const n2 = (value) => String(Math.round(value * 100) / 100 + 0);
34
52
  export function resolve(o) {
@@ -39,6 +57,18 @@ export function resolve(o) {
39
57
  const initials = typeof o?.initials === "string" ? [...o.initials.trim()].slice(0, 3).join("").toUpperCase() : "";
40
58
  return { style, size, theme, tint: readColour(o?.tint), accent: accent === undefined ? undefined : hexOf(accent), initials };
41
59
  }
60
+ /** A text from outside made safe: control characters removed, at most 24 letters a line and `lines` lines, or `undefined` when nothing is left. */
61
+ export const clean = (value, lines = 1) => {
62
+ if (typeof value !== "string" && typeof value !== "number")
63
+ return undefined;
64
+ const text = String(value)
65
+ .split(/\r?\n/)
66
+ .slice(0, lines)
67
+ .map((line) => [...line.replace(/\p{Cc}/gu, "").trim()].slice(0, 24).join(""))
68
+ .filter((line) => line !== "")
69
+ .join("\n");
70
+ return text === "" ? undefined : text;
71
+ };
42
72
  /** The main colour of a metal, or the tint when there is one. */
43
73
  export const mainOf = (metal, tint) => tint ?? readColour(metal === "flat" ? "#8a949e" : METALS[metal]);
44
74
  /** The relative luminance of a colour, 0 to 1. */