@johnmorrisdotca/dougu-sports 0.3.2 → 0.4.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/NOTICE.md +1 -0
  3. package/README.md +6 -5
  4. package/dist/emblems/ball/ball.art.d.ts +24 -0
  5. package/dist/emblems/ball/ball.art.js +67 -0
  6. package/dist/emblems/ball/ball.core.d.ts +9 -0
  7. package/dist/emblems/ball/ball.core.js +12 -1
  8. package/dist/emblems/ball/ball.face.d.ts +59 -0
  9. package/dist/emblems/ball/ball.face.js +288 -0
  10. package/dist/emblems/ball/ball.image.d.ts +8 -0
  11. package/dist/emblems/ball/ball.image.js +122 -0
  12. package/dist/emblems/ball/ball.index.d.ts +3 -1
  13. package/dist/emblems/ball/ball.index.js +7 -5
  14. package/dist/emblems/ball/ball.letters.d.ts +32 -0
  15. package/dist/emblems/ball/ball.letters.js +142 -0
  16. package/dist/emblems/ball/ball.object.js +1 -1
  17. package/dist/emblems/ball/ball.puck.d.ts +8 -0
  18. package/dist/emblems/ball/ball.puck.js +19 -12
  19. package/dist/emblems/ball/ball.puck.print.d.ts +76 -0
  20. package/dist/emblems/ball/ball.puck.print.js +46 -0
  21. package/dist/emblems/ball/ball.signature.d.ts +4 -0
  22. package/dist/emblems/ball/ball.signature.js +58 -0
  23. package/dist/emblems/ball/ball.stack.d.ts +10 -0
  24. package/dist/emblems/ball/ball.stack.js +8 -1
  25. package/dist/emblems/emblem.api.d.ts +5 -5
  26. package/dist/emblems/emblem.api.js +27 -8
  27. package/dist/emblems/emblem.core.d.ts +8 -0
  28. package/dist/emblems/emblem.descriptions.js +8 -0
  29. package/dist/emblems/emblem.ids.d.ts +6 -1
  30. package/dist/emblems/emblem.ids.js +4 -1
  31. package/dist/emblems/emblem.registry.js +2 -2
  32. package/dist/emblems-entry.d.ts +3 -2
  33. package/dist/emblems-entry.js +1 -1
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.js +1 -0
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,30 @@ All notable changes to this project are written here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.1] - 2026-10-14
10
+
11
+ The printed pucks take the same flag renderer as taisen-brackets.
12
+
13
+ ### Changed
14
+
15
+ - **The puck's flag renderer is the one taisen-brackets takes.** `flags` is now an option of the call (`renderEmblem`, `emblemFrame`, `ballFrame`), beside `face`, and no longer a field of `face`; it is called `flags(code, { shape: "round" | "square" | "rect", size, theme: "inherit" | "light" | "dark" })` (the puck asks for `"rect"`), may return `null` or `undefined` for a code it has no flag for, and is synchronous, so one adapter over hata serves every package. New types `FlagRequest` and `FlagShape`. `FlagRenderer` changed accordingly; the 0.4.0 form (`face.flags`, shapes `4:3`/`1:1`/`round`) is gone.
16
+
17
+ ## [0.4.0] - 2026-10-14
18
+
19
+ Printed pucks: eight souvenir pucks with a print on the top face that turns with the puck, and a `face` option that takes any picture, emblem or flag code. 194 emblems.
20
+
21
+ ### Added
22
+
23
+ - **Eight printed pucks** ([docs/pucks.md](docs/pucks.md), [日本語](docs/pucks.ja.md)), a new group `puck:*`: `puck:canada` (our own flag of the Canadian pattern with CANADA under it in wide letters, in a ring of words), `puck:canada-red` (a red face with a big white leaf), `puck:canada-white` (a white face with red bands and a red leaf), `puck:usa` (a disc of stars and stripes with USA lettered across it), `puck:usa-navy` (a navy face, a red ring between white hairlines, stars and USA), `puck:player` (a made-up surname and a big number, like a jersey), `puck:signed` (the navy puck with a made-up gold signature) and `puck:souvenir` (a generic one with ring text from the caller). Each is the same puck as `ball:puck` (76.2 by 25.4 mm), in true 3D: the print sits on the top face and turns with the puck, foreshortens as it tips, and is gone when the top is turned away; the grip shows on the rim. `ball:puck` itself stays plain.
24
+ - **A `face` option** on every puck (`renderEmblem`, `emblemFrame`, `ballFrame`): `design`, `image` (any SVG, made safe first), `emblem` (a dougu id), `flag` (`"ca"` and `"us"` are drawn here; any other code goes through the caller's `flags(code, { shape, size, theme })`, the shape of the renderer hata exports), `text`, `ringText`, `number`, `name`, `signature` (a seed, or `false`) and `colors`. `ball:puck` given a face is drawn as `puck:souvenir` with it. New exports: `PUCK_IDS`, the `PUCK_*` constants and the types `PuckFace`, `PuckDesign`, `FaceColors`, `FlagRenderer` and `PuckId`; `isBallId`, `ballFrame` and `lineup` take the puck ids.
25
+ - **Our own lettering**: a stroke alphabet (adapted from senshu-jerseys, no font file), wide, heavy and leaning a little, set on a line or on an arc for the ring, with a made-up signature drawn by a seeded pen (the same seed, the same stroke, never a real hand) and our own eleven-point maple leaf, flags and stars.
26
+ - **A safe image path**: an outside SVG is taken apart and put back through a strict allow-list (shapes, gradients and clip paths; no script, style, text, image, use, foreignObject, filter, link or event handler), with its ids renamed under the puck's own prefix.
27
+ - A "Print a puck" section in the demo, both gallery pages list the eight, and tests for the face, escaping, the signature, the safe image, the turn box and the proportions (the printed pucks are measured against the rule like the plain one).
28
+
29
+ ### Changed
30
+
31
+ - The stack renderer has an `overlay` that is drawn over the grain when the top face is toward you, and the puck's drawing is shared (`puckSpec`), so the plain puck's output is unchanged.
32
+
9
33
  ## [0.3.2] - 2026-10-14
10
34
 
11
35
  The README pictures fill their frames: balls first, then trophies, then gear, each a full grid.
@@ -116,7 +140,9 @@ First preview: the name, the API and two sample emblems. The full set of trophie
116
140
  - Tests for the drawing, the ids, the turns and the bundle size of one item; a package check that installs the packed tarball and bundles one item from it.
117
141
  - The demo (a playground with a turn slider, and a gallery page), `docs/layout.md`, `docs/turning.md`, `docs/gallery.md`, the family header and footer, the CI, Pages and Release workflows, and `LICENSES/` and `NOTICE.md` ready for third-party art.
118
142
 
119
- [Unreleased]: https://github.com/johnmorrisdotca/dougu-sports/compare/v0.3.2...HEAD
143
+ [Unreleased]: https://github.com/johnmorrisdotca/dougu-sports/compare/v0.4.1...HEAD
144
+ [0.4.1]: https://github.com/johnmorrisdotca/dougu-sports/compare/v0.4.0...v0.4.1
145
+ [0.4.0]: https://github.com/johnmorrisdotca/dougu-sports/compare/v0.3.2...v0.4.0
120
146
  [0.3.2]: https://github.com/johnmorrisdotca/dougu-sports/compare/v0.3.1...v0.3.2
121
147
  [0.3.1]: https://github.com/johnmorrisdotca/dougu-sports/compare/v0.3.0...v0.3.1
122
148
  [0.3.0]: https://github.com/johnmorrisdotca/dougu-sports/compare/v0.2.1...v0.3.0
package/NOTICE.md CHANGED
@@ -8,6 +8,7 @@ No emblem carries the name, mark or logo of a league, a team, a federation or an
8
8
 
9
9
  - The trophies, medals, crests, awards and sport pictograms are drawn in SVG by hand in a simplified, generic form. No real trophy, medal, logo, mark or engraving was copied or traced, and no photograph was traced (photographs were looked at, never copied).
10
10
  - The balls (`src/emblems/ball/`) are drawn by this package's own code: a rotation, a projection and our own geometry (a seam curve, the truncated icosahedron, a layout of curved panels, a fitted basketball channel).
11
+ - The printed pucks (`puck:*`, `src/emblems/ball/ball.puck.print.ts`) are our own drawings: the maple leaf, the flags of the Canadian and United States patterns and the stars are drawn from simple geometry, the lettering is a stroke alphabet adapted from the block letters of the author's own senshu-jerseys (same author, same MIT licence, no font file), and the signature is made by a seeded pen, so it is no real person's. Photographs of souvenir pucks were looked at for what such a puck shows (a flag, a name, a number, a signature, a ring of words) and nothing was copied or traced. No team's or league's logo or wordmark, no maker's mark and no real player's name is used.
11
12
  - The gear (`src/emblems/gear/`) is drawn by us from a list of parts (a path, a colour, how it reads as a line); the bow, the arrow and the other tapering shapes are made by a small function of ours (`swell`), and the rest are paths written for this package.
12
13
  - The Japanese words for the Japanese items are the ones the sports' governing bodies use in their own rules and equipment lists (kendo, judo, kyudo and karate federations and the sumo association); the bodies' names and marks are not used in any id, title or drawing.
13
14
 
package/README.md CHANGED
@@ -45,7 +45,7 @@ The ball and the gear of every sport, trophies, medals, crests and the icon of e
45
45
  </tr>
46
46
  </table>
47
47
 
48
- > **Status: preview.** 186 emblems: 24 trophies, 5 awards, 6 medals, 4 crests, a pictogram for each of the 38 sports, 66 balls and implements drawn in 3D, 42 pieces of gear (among them the Japanese sports and budo, [docs/japanese-sports.md](./docs/japanese-sports.md)) and a championship pennant; [docs/layout.md](./docs/layout.md) says where each goes. All of it is drawn for this package: no art is copied from another project.
48
+ > **Status: preview.** 194 emblems: 24 trophies, 5 awards, 6 medals, 4 crests, a pictogram for each of the 38 sports, 66 balls and implements drawn in 3D, 8 printed pucks ([docs/pucks.md](./docs/pucks.md)), 42 pieces of gear (among them the Japanese sports and budo, [docs/japanese-sports.md](./docs/japanese-sports.md)) and a championship pennant; [docs/layout.md](./docs/layout.md) says where each goes. All of it is drawn for this package: no art is copied from another project.
49
49
 
50
50
  ## In 30 seconds
51
51
 
@@ -73,7 +73,7 @@ Put the string in a page (`element.innerHTML = svg`), write it to a file, or sen
73
73
 
74
74
  ## Features
75
75
 
76
- - **Equipment first (道具 is the word for it)**: 66 balls and implements drawn in 3D, from the 32-panel soccer ball to the 26, 18, 14, 12 and 6 panel constructions, futsal, beach soccer and the pucks and stones, each with its official size, and 42 pieces of gear (sticks, rackets, helmets, skates, nets, and the Japanese budo kit) drawn to match; see [docs/gallery.md](./docs/gallery.md).
76
+ - **Equipment first (道具 is the word for it)**: 66 balls and implements drawn in 3D, from the 32-panel soccer ball to the 26, 18, 14, 12 and 6 panel constructions, futsal, beach soccer and the pucks and stones, each with its official size, 8 printed pucks (a flag, a leaf, stars and stripes, a player's number, a made-up signature, your own picture: [docs/pucks.md](./docs/pucks.md)), and 42 pieces of gear (sticks, rackets, helmets, skates, nets, and the Japanese budo kit) drawn to match; see [docs/gallery.md](./docs/gallery.md).
77
77
  - **Three styles** for every emblem: `metal` (polished metal: a gradient, a highlight and a soft rim of reflected light, edged in a darker tone of its own metal, with no heavy outline), `line` (one clean outline in the page's text colour, made to read at 14 pixels) and `color` (flat fills); see [Theming](#theming).
78
78
  - **Size, tint, accent and theme** as options: a tint recolours the main part (a medal's metal, a crest's colour, a ball's patches), an accent the second colour (a ribbon, a crest's band, the pennants), a theme sets the ink for light, dark or either.
79
79
  - **Turning**: `emblemFrame` and `ballFrame` return the emblem at an angle, in plain SVG: every emblem turns, a ball rolls, a cup keeps its outline and moves its light, and everything flat (gear, a bat, a crown, a sport's icon) flips like a coin; `bindTurn` lets a drag, the arrow keys or an auto-turn do the turning in a page; see [docs/turning.md](./docs/turning.md).
@@ -291,7 +291,7 @@ console.log(emblemName("medal:gold", "ja")); // 金メダル
291
291
  ```js
292
292
  import { EMBLEM_GROUPS, EMBLEM_IDS, SPORT_NAMES } from "@johnmorrisdotca/dougu-sports";
293
293
 
294
- console.log(Object.keys(EMBLEM_GROUPS).join(" ")); // award trophy medal crest ball gear sport pennant
294
+ console.log(Object.keys(EMBLEM_GROUPS).join(" ")); // award trophy medal crest ball puck gear sport pennant
295
295
  console.log(EMBLEM_IDS.includes("ball:soccer")); // true
296
296
  console.log(SPORT_NAMES.length); // 38
297
297
  ```
@@ -408,6 +408,7 @@ An emblem is a small object: `id`, `en` and `ja` names, a `source` (who drew it)
408
408
  | `medal` | Gold, silver and bronze medals on a ribbon of five stripes, and a badge for each of the first three places | `src/emblems/medal/` |
409
409
  | `crest` | Four generic crests, tinted by a team's colours, with its letters | `src/emblems/crest/` |
410
410
  | `ball` | 66 balls and implements, in 3D, each with its official size: soccer (in 32, 26, 18, 14, 12 and 6 panels, in three colourways, with futsal and beach soccer; [docs/soccer-balls.md](./docs/soccer-balls.md)), basketball, baseball, tennis, rugby, the footballs, golf, the puck, the curling stone and the rest | `src/emblems/ball/` |
411
+ | `puck` | 8 printed pucks: the same puck with a print on its top face that turns with it (a flag and CANADA, a red leaf, stars and stripes, a player's name and number, a made-up signature, a souvenir), and a `face` option for any picture, emblem or flag code; [docs/pucks.md](./docs/pucks.md) | `src/emblems/ball/ball.puck.print.ts` |
411
412
  | `gear` | 42 pieces of equipment: sticks, rackets, skates, a glove, a helmet, nets and posts, a javelin, starting blocks, and for the Japanese sports a war fan, a sumo belt, a bamboo sword, a kendo helmet, a judo suit, a belt, a long bow, an arrow and a karate suit | `src/emblems/gear/` |
412
413
  | `sport` | One pictogram for each of the thirty-eight sports | `src/emblems/sport/` |
413
414
  | `pennant` | A felt championship pennant on a dowel, with a stitched edge, lettered with its words and its year | `src/emblems/pennant/` |
@@ -441,7 +442,7 @@ The thirty-eight sports the ball, gear and sport emblems are made for are `SPORT
441
442
  | `EMBLEM_TABLE` | Every emblem object, by id |
442
443
  | `TROPHY_HOCKEY_CUP`, `MEDAL_GOLD`, `CREST_SHIELD`, `SPORT_NHL`, `BALL_SOCCER`, `GEAR_HURLEY`... | Each emblem as a constant, named by its id in capitals (`trophy:hockey-cup`); [docs/gallery.md](./docs/gallery.md) ([日本語](./docs/gallery.ja.md)) lists every emblem and states the rule for its constant and the module that takes it alone |
443
444
  | `EMBLEM_IDS`, `EMBLEM_GROUPS`, `EMBLEM_FOLDERS`, `EMBLEM_ALIASES` | Every id, the groups, the folder of each group's art, and the short names (`cup` for `trophy:cup`) |
444
- | `AWARD_IDS`, `TROPHY_IDS`, `MEDAL_IDS`, `CREST_IDS`, `BALL_IDS`, `GEAR_IDS`, `SPORT_IDS`, `PENNANT_IDS` | The ids of each group |
445
+ | `AWARD_IDS`, `TROPHY_IDS`, `MEDAL_IDS`, `CREST_IDS`, `BALL_IDS`, `PUCK_IDS`, `GEAR_IDS`, `SPORT_IDS`, `PENNANT_IDS` | The ids of each group |
445
446
  | `MEDAL_KINDS`, `CREST_SHAPES` | The kinds of medal, and the shapes of crest |
446
447
  | `SPORT_NAMES` | The thirty-eight sports |
447
448
  | `JAPANESE_IDS` | The ids of the Japanese sports and budo, together: sumo, kendo, judo, kyudo and karate, with their trophies and gear ([docs/japanese-sports.md](./docs/japanese-sports.md)) |
@@ -592,7 +593,7 @@ Bug reports and ideas go in the [issues](https://github.com/johnmorrisdotca/doug
592
593
 
593
594
  ## Changes
594
595
 
595
- See [CHANGELOG.md](./CHANGELOG.md). 0.3.0 adds nineteen soccer balls (26, 18, 14, 12 and 6 panels, winter orange and high-visibility yellow, futsal and beach soccer: 186 emblems); 0.2.1 draws every ball and implement to the rulebook's proportions; 0.2.0 is 167 emblems, every one drawn here, with the Japanese sports, engraving, a turn for every emblem and the compose helpers; 0.1.0 brought the first full set of 148; 0.0.1 was the first preview.
596
+ See [CHANGELOG.md](./CHANGELOG.md). 0.4.0 adds eight printed pucks and a `face` option (194 emblems: [docs/pucks.md](./docs/pucks.md)); 0.3.0 adds nineteen soccer balls (26, 18, 14, 12 and 6 panels, winter orange and high-visibility yellow, futsal and beach soccer: 186 emblems); 0.2.1 draws every ball and implement to the rulebook's proportions; 0.2.0 is 167 emblems, every one drawn here, with the Japanese sports, engraving, a turn for every emblem and the compose helpers; 0.1.0 brought the first full set of 148; 0.0.1 was the first preview.
596
597
 
597
598
  ## Credits
598
599
 
@@ -0,0 +1,24 @@
1
+ /** A maple leaf with a stem, as a path about the origin: the blade spans -1 to 1 across and from -1 down to about 0.6, the stem to 1. */
2
+ export declare const LEAF_PATH: string;
3
+ /** A maple leaf: centre (`cx`, `cy`), the width of the blade `w`, and a fill. */
4
+ export declare function leaf(cx: number, cy: number, w: number, fill: string): string;
5
+ /** A five-point star, as a polygon's points, with the given outer radius, centred on (`cx`, `cy`), pointing up (turned by `deg`). */
6
+ export declare function starPoints(cx: number, cy: number, r: number, deg?: number): string;
7
+ export declare const star: (cx: number, cy: number, r: number, fill: string, deg?: number) => string;
8
+ /** The colours of the two flags: a red and a white, a navy and a red. Plain values of the flags' colours; the caller may replace them. */
9
+ export declare const FLAG: {
10
+ readonly red: "#d52b1e";
11
+ readonly white: "#ffffff";
12
+ readonly navy: "#0a3161";
13
+ readonly usRed: "#b31942";
14
+ };
15
+ /** A flag of Canada's pattern, 2 wide and 1 tall about the origin scaled to the width `w`: red, white, red, with the leaf on the white. */
16
+ export declare function canadaFlag(cx: number, cy: number, w: number, red?: "#d52b1e", white?: "#ffffff"): string;
17
+ /**
18
+ * The stars and stripes as a rectangle about the origin, `w` wide and `h` tall: thirteen stripes across, a canton of stars in the upper left (`canton` wide, seven stripes tall), and the stars in staggered rows, four
19
+ * and three to a row, which is what reads at a puck's size (the flag has fifty, in nine rows). A star whose centre lies farther than `reach` from the origin is left out, so a disc cut from the field has no
20
+ * half stars at its edge.
21
+ */
22
+ export declare function starsAndStripes(w: number, h: number, canton: number, reach?: number, red?: string, white?: string, blue?: string): string;
23
+ /** A flag of the United States' pattern, 2 wide about the origin scaled to the width `w` (it is 1.9 to 1): stripes and a canton of stars. */
24
+ export declare function usaFlag(cx: number, cy: number, w: number): string;
@@ -0,0 +1,67 @@
1
+ // The small drawings a printed puck is made of, all our own: an eleven-point maple leaf, a five-point star, a Canadian-style flag (red, white, red, with that leaf) and the stars and stripes. Each is a string of SVG in
2
+ // a unit box (the leaf's blade is 2 wide and 2 tall about the origin) that the face scales and places; the flags take the width they are drawn at. Imports only the core's number writer.
3
+ import { num } from "./ball.core.js";
4
+ /** The right half of the leaf, from the top tip down to the stem: eleven points in all with the left half mirrored (a tip, then a notch, five times over). y runs down. */
5
+ const LEAF_HALF = [
6
+ [0, -1], [0.15, -0.6], [0.36, -0.7], [0.33, -0.36], [0.67, -0.5], [0.55, -0.2], [1, -0.12], [0.74, 0.12], [0.86, 0.36], [0.4, 0.3], [0.42, 0.56], [0.06, 0.42],
7
+ ];
8
+ /** A maple leaf with a stem, as a path about the origin: the blade spans -1 to 1 across and from -1 down to about 0.6, the stem to 1. */
9
+ export const LEAF_PATH = (() => {
10
+ const right = LEAF_HALF.slice(1);
11
+ const left = [...LEAF_HALF.slice(1)].reverse().map(([x, y]) => [-x, y]);
12
+ const pts = [LEAF_HALF[0], ...right, [0.06, 0.92], [-0.06, 0.92], ...left];
13
+ return `M${pts.map(([x, y]) => `${num(x, 3)} ${num(y, 3)}`).join("L")}Z`;
14
+ })();
15
+ /** A maple leaf: centre (`cx`, `cy`), the width of the blade `w`, and a fill. */
16
+ export function leaf(cx, cy, w, fill) {
17
+ const k = w / 2;
18
+ return `<path d="${LEAF_PATH}" transform="translate(${num(cx, 3)} ${num(cy, 3)}) scale(${num(k, 4)})" fill="${fill}"/>`;
19
+ }
20
+ /** A five-point star, as a polygon's points, with the given outer radius, centred on (`cx`, `cy`), pointing up (turned by `deg`). */
21
+ export function starPoints(cx, cy, r, deg = 0) {
22
+ const pts = [];
23
+ for (let i = 0; i < 10; i++) {
24
+ const a = ((deg + i * 36) * Math.PI) / 180;
25
+ const rr = i % 2 === 0 ? r : r * 0.382;
26
+ pts.push(`${num(cx + rr * Math.sin(a), 3)} ${num(cy - rr * Math.cos(a), 3)}`);
27
+ }
28
+ return pts.join(" ");
29
+ }
30
+ export const star = (cx, cy, r, fill, deg = 0) => `<polygon points="${starPoints(cx, cy, r, deg)}" fill="${fill}"/>`;
31
+ /** The colours of the two flags: a red and a white, a navy and a red. Plain values of the flags' colours; the caller may replace them. */
32
+ export const FLAG = { red: "#d52b1e", white: "#ffffff", navy: "#0a3161", usRed: "#b31942" };
33
+ /** A flag of Canada's pattern, 2 wide and 1 tall about the origin scaled to the width `w`: red, white, red, with the leaf on the white. */
34
+ export function canadaFlag(cx, cy, w, red = FLAG.red, white = FLAG.white) {
35
+ const k = w / 2;
36
+ const t = `translate(${num(cx, 3)} ${num(cy, 3)}) scale(${num(k, 4)})`;
37
+ return `<g transform="${t}"><rect x="-1" y="-.5" width="2" height="1" fill="${white}"/><rect x="-1" y="-.5" width=".5" height="1" fill="${red}"/><rect x=".5" y="-.5" width=".5" height="1" fill="${red}"/><path d="${LEAF_PATH}" transform="translate(0 -.03) scale(.3)" fill="${red}"/></g>`;
38
+ }
39
+ /**
40
+ * The stars and stripes as a rectangle about the origin, `w` wide and `h` tall: thirteen stripes across, a canton of stars in the upper left (`canton` wide, seven stripes tall), and the stars in staggered rows, four
41
+ * and three to a row, which is what reads at a puck's size (the flag has fifty, in nine rows). A star whose centre lies farther than `reach` from the origin is left out, so a disc cut from the field has no
42
+ * half stars at its edge.
43
+ */
44
+ export function starsAndStripes(w, h, canton, reach = Infinity, red = FLAG.usRed, white = FLAG.white, blue = FLAG.navy) {
45
+ const x0 = -w / 2, y0 = -h / 2, sh = h / 13;
46
+ let out = `<rect x="${num(x0, 3)}" y="${num(y0, 3)}" width="${num(w, 3)}" height="${num(h, 3)}" fill="${white}"/>`;
47
+ for (let i = 0; i < 13; i += 2)
48
+ out += `<rect x="${num(x0, 3)}" y="${num(y0 + i * sh, 3)}" width="${num(w, 3)}" height="${num(sh, 3)}" fill="${red}"/>`;
49
+ const ch = sh * 7;
50
+ out += `<rect x="${num(x0, 3)}" y="${num(y0, 3)}" width="${num(canton, 3)}" height="${num(ch, 3)}" fill="${blue}"/>`;
51
+ const r = Math.min(canton / 9.5, ch / 15);
52
+ for (let row = 0; row < 7; row++) {
53
+ const n = row % 2 === 0 ? 4 : 3;
54
+ for (let c = 0; c < n; c++) {
55
+ const x = x0 + canton * ((row % 2 === 0 ? c + 0.5 : c + 1) / 4);
56
+ const y = y0 + ch * ((row + 0.5) / 7);
57
+ if (Math.hypot(x, y) <= reach)
58
+ out += star(x, y, r, white);
59
+ }
60
+ }
61
+ return out;
62
+ }
63
+ /** A flag of the United States' pattern, 2 wide about the origin scaled to the width `w` (it is 1.9 to 1): stripes and a canton of stars. */
64
+ export function usaFlag(cx, cy, w) {
65
+ const k = w / 2;
66
+ return `<g transform="translate(${num(cx, 3)} ${num(cy, 3)}) scale(${num(k, 4)})">${starsAndStripes(2, 2 / 1.9, 0.8)}</g>`;
67
+ }
@@ -1,3 +1,4 @@
1
+ import type { FlagRenderer, PuckFace } from "./ball.face.ts";
1
2
  /** `metal` (also written `colour`): full colour, with shading. `line`: one outline in `currentColor`. */
2
3
  export type PieceStyle = "metal" | "colour" | "color" | "line";
3
4
  /** `inherit` (the default): the page's text colour. `auto`: dark ink on a light device and pale ink on a dark one. `light` and `dark` force one. */
@@ -18,6 +19,10 @@ export interface PieceOptions {
18
19
  locale?: "en" | "ja";
19
20
  /** A string is the `<title>` (escaped); `false` drops it and marks the drawing `aria-hidden="true"`. */
20
21
  title?: string | false;
22
+ /** A puck: the print on its top face (see `PuckFace`). The printed pucks (`puck:*`) take it, and so does `ball:puck` through `renderEmblem`, `emblemFrame` and `ballFrame`. Anything else ignores it. */
23
+ face?: PuckFace;
24
+ /** A puck: a renderer for the flag codes this package does not draw (`face.flag`), `(code, { shape, size, theme }) => svg`, the one taisen-brackets takes. Synchronous. */
25
+ flags?: FlagRenderer;
21
26
  }
22
27
  /** 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. */
23
28
  export interface BallFrameOptions extends PieceOptions {
@@ -85,6 +90,10 @@ export interface ResolvedOptions {
85
90
  tint: string | undefined;
86
91
  accent: string | undefined;
87
92
  grain: boolean | undefined;
93
+ face: PuckFace | undefined;
94
+ flags: FlagRenderer | undefined;
95
+ /** A string that tells two faces apart, for the ids of a drawing. */
96
+ faceKey: string;
88
97
  }
89
98
  export declare function resolveOptions(o: PieceOptions | undefined): ResolvedOptions;
90
99
  /** The ink of a drawing when a theme sets it, and the page it is made to read on. */
@@ -42,12 +42,23 @@ export function hashOf(text) {
42
42
  h = Math.imul(h ^ text.charCodeAt(i), 16777619);
43
43
  return (h >>> 0).toString(36);
44
44
  }
45
+ /** What tells one face from another: its plain data (a renderer is a function, and only its name goes in). */
46
+ function faceKeyOf(face) {
47
+ if (typeof face !== "object" || face === null)
48
+ return "";
49
+ try {
50
+ return JSON.stringify(face, (k, v) => (typeof v === "function" ? `fn:${v.name}` : v)) ?? "";
51
+ }
52
+ catch {
53
+ return "?";
54
+ }
55
+ }
45
56
  export function resolveOptions(o) {
46
57
  const size = typeof o?.size === "number" && Number.isFinite(o.size) && o.size > 0 ? Math.min(Math.round(o.size * 100) / 100, 4096) : 64;
47
58
  const theme = o?.theme === "auto" || o?.theme === "light" || o?.theme === "dark" ? o.theme : "inherit";
48
59
  const tint = readColour(o?.tint);
49
60
  const accent = readColour(o?.accent);
50
- return { style: o?.style === "line" ? "line" : "colour", size, theme, tint: tint === undefined ? undefined : hexOf(tint), accent: accent === undefined ? undefined : hexOf(accent), grain: typeof o?.grain === "boolean" ? o.grain : undefined };
61
+ return { style: o?.style === "line" ? "line" : "colour", size, theme, tint: tint === undefined ? undefined : hexOf(tint), accent: accent === undefined ? undefined : hexOf(accent), grain: typeof o?.grain === "boolean" ? o.grain : undefined, face: typeof o?.face === "object" && o.face !== null ? o.face : undefined, flags: typeof o?.flags === "function" ? o.flags : undefined, faceKey: faceKeyOf(o?.face) + (typeof o?.flags === "function" ? `|fn:${o.flags.name}` : "") };
51
62
  }
52
63
  /** The ink of a drawing when a theme sets it, and the page it is made to read on. */
53
64
  export const PIECE_INK = { light: "#20262d", dark: "#eef1f3" };
@@ -0,0 +1,59 @@
1
+ /** The designs a face starts from. */
2
+ export type PuckDesign = "canada" | "canada-red" | "canada-white" | "usa" | "usa-navy" | "player" | "souvenir";
3
+ /** How a flag is framed: `round` in a circle, `square`, or `rect` in its own proportions (the puck asks for `rect`). */
4
+ export type FlagShape = "round" | "square" | "rect";
5
+ /** What a drawing asks a `flags` renderer for: the shape, a width and height in pixels, and the look (`inherit` for a page whose ink is its own). */
6
+ export interface FlagRequest {
7
+ shape: FlagShape;
8
+ size: number;
9
+ theme: "inherit" | "light" | "dark";
10
+ }
11
+ /**
12
+ * A function from a country code and a request to an SVG string, or `null` or `undefined` for a code it has no flag for: the same renderer taisen-brackets and kyogijo take, so one adapter over hata serves them
13
+ * all. It must be synchronous (a drawing is a pure function of its options): hata's `flag()` is asynchronous, so load the flags first and pass `(code) => loaded[code]`. Pass it as the `flags` option of the call.
14
+ */
15
+ export type FlagRenderer = (code: string, request: FlagRequest) => string | null | undefined;
16
+ /** The colours of a face, by name. The array form is the same list in this order: field, ring, ink, accent, signature. */
17
+ export interface FaceColors {
18
+ /** The disc's own colour. */
19
+ field?: string;
20
+ /** The ring round the edge, where the face has one. */
21
+ ring?: string;
22
+ /** The lettering on the ring, and the main lettering on a dark face. */
23
+ ink?: string;
24
+ /** The second colour: the leaf, a number's fill, the stars. */
25
+ accent?: string;
26
+ /** The marker of a signature. */
27
+ signature?: string;
28
+ }
29
+ /**
30
+ * The print on a puck's top face. Everything is optional, and what is not given comes from the design (the puck's own id says which, or `design`). Text is cleaned (no control characters, 24 letters a line) and
31
+ * drawn in our own vector lettering, so any letter, digit or `- . , ' / : &` is available and any other character is left out.
32
+ */
33
+ export interface PuckFace {
34
+ /** The design to start from. A puck with an id of its own already has one; `puck:souvenir` and `ball:puck` take it from here. */
35
+ design?: PuckDesign;
36
+ /** Any SVG: a logo, a crest, a drawing. Made safe first (see `safeImage`): shapes, gradients and clip paths only. It is fitted into the middle of the face and replaces the design's own picture. */
37
+ image?: string;
38
+ /** A country code (`"ca"` and `"us"` are drawn by this package; another needs the `flags` option of the call). It replaces the design's own picture. */
39
+ flag?: string;
40
+ /** The id of a dougu emblem (`"trophy:cup"`, `"sport:nhl"`), drawn small in the middle. Resolved by `renderEmblem`, `emblemFrame` and `ballFrame`. */
41
+ emblem?: string;
42
+ /** The words under the picture (`"CANADA"`), one line. */
43
+ text?: string;
44
+ /** The words round the edge. A string is set on the top; a pair is the top and the bottom; `false` leaves the ring out. */
45
+ ringText?: string | readonly [string, string] | false;
46
+ /** A big number, like a jersey's. Up to three characters. */
47
+ number?: string | number;
48
+ /** A surname, set on the ring like a jersey's name. */
49
+ name?: string;
50
+ /** A seed for a made-up signature, or `false` for none. A number or any text; the same seed always draws the same squiggle, and no seed is a real person's hand. */
51
+ signature?: number | string | false;
52
+ /** The colours: by name, or as an array in the order field, ring, ink, accent, signature. A colour is `#rgb`, `#rrggbb` or `rgb()`; anything else is ignored. */
53
+ colors?: FaceColors | readonly (string | undefined)[];
54
+ }
55
+ /** The face of a design, in the face's units (a disc of radius 1, up is up): its definitions and its drawing. `prefix` makes every id unique to this puck. */
56
+ export declare function faceMarkup(face: PuckFace | undefined, design: PuckDesign, own: PuckFace | undefined, prefix: string, flags?: FlagRenderer, theme?: FlagRequest["theme"]): {
57
+ defs: string;
58
+ body: string;
59
+ };
@@ -0,0 +1,288 @@
1
+ // The print on a puck's top face: the designs (a Canadian flag with its country below it, a red leaf, a white face with red bands, stars and stripes, a navy face with rings, a player's name and number, a made-up
2
+ // signature, a generic souvenir) and the options that change them. The face is drawn in its own units, a disc of radius 1 whose up is up, and ball.print.ts lays that disc on the top of the puck in 3D. All of it
3
+ // is our own drawing: our own leaf, our own flags, our own lettering (ball.letters.ts) and no mark, name or wordmark of a team, a league or a maker. Imports only this folder's helpers.
4
+ import { hexOf, num, readColour } from "./ball.core.js";
5
+ import { FLAG, LEAF_PATH, canadaFlag, star, starsAndStripes, usaFlag } from "./ball.art.js";
6
+ import { safeImage } from "./ball.image.js";
7
+ import { arcPath, fitSize, linePath } from "./ball.letters.js";
8
+ import { signaturePath } from "./ball.signature.js";
9
+ const LOOKS = {
10
+ canada: { field: "#fbf8f2", ring: "#c4202d", ink: "#ffffff", accent: FLAG.red, signature: "#1b1b1b" },
11
+ "canada-red": { field: FLAG.red, ring: "#1d222b", ink: "#ffffff", accent: "#ffffff", signature: "#e8b923" },
12
+ "canada-white": { field: "#fbf8f2", ring: "#c4202d", ink: "#c4202d", accent: FLAG.red, signature: "#1b1b1b" },
13
+ usa: { field: "#ffffff", ring: "#0f2a5c", ink: "#ffffff", accent: FLAG.usRed, signature: "#1b1b1b" },
14
+ "usa-navy": { field: "#0f2a5c", ring: "#b31942", ink: "#ffffff", accent: "#ffffff", signature: "#e8b923" },
15
+ player: { field: "#1f4e8c", ring: "#0d2347", ink: "#ffffff", accent: "#f2b705", signature: "#e8b923" },
16
+ souvenir: { field: "#f4f1e8", ring: "#13325e", ink: "#ffffff", accent: "#c4202d", signature: "#1b1b1b" },
17
+ };
18
+ /** The words and the signature a design starts with; the caller's own options replace them. */
19
+ const WORDS = {
20
+ canada: { flag: "ca", text: "CANADA", ringText: ["CANADA · ICE HOCKEY", "SOUVENIR PUCK"] },
21
+ "canada-red": { text: "CANADA", ringText: ["CANADA · ICE HOCKEY", "SOUVENIR PUCK"] },
22
+ "canada-white": { text: "CANADA", ringText: false },
23
+ usa: { text: "USA", ringText: ["UNITED STATES · ICE HOCKEY", "SOUVENIR PUCK"] },
24
+ "usa-navy": { text: "USA", ringText: ["UNITED STATES · ICE HOCKEY", "SOUVENIR PUCK"] },
25
+ player: { name: "MORRIS", number: 23, ringText: ["", "ICE HOCKEY"] },
26
+ souvenir: { text: "SOUVENIR", ringText: ["ICE HOCKEY · COLLECTOR EDITION", ""] },
27
+ };
28
+ /** A text from outside made safe: control characters out, 24 characters a line, one line, or `undefined` when nothing is left. */
29
+ const cleanText = (value, max = 24) => {
30
+ if (typeof value !== "string" && typeof value !== "number")
31
+ return undefined;
32
+ const text = [...String(value).replace(/\p{Cc}/gu, " ").replace(/\s+/g, " ").trim()].slice(0, max).join("");
33
+ return text === "" ? undefined : text;
34
+ };
35
+ const colourOf = (value) => {
36
+ const rgb = typeof value === "string" ? readColour(value) : undefined;
37
+ return rgb === undefined ? undefined : hexOf(rgb);
38
+ };
39
+ const DESIGNS = Object.keys(LOOKS);
40
+ /** The face, ready to draw: the design's words and colours under the caller's. `base` is the design a puck's own id starts from. */
41
+ function resolve(face, base, own, flags, theme) {
42
+ const f = typeof face === "object" && face !== null ? face : {};
43
+ const design = (typeof f.design === "string" && DESIGNS.includes(f.design) ? f.design : own?.design ?? base);
44
+ const words = { ...WORDS[design], ...own, ...definedOnly(f) };
45
+ const look = { ...LOOKS[design] };
46
+ for (const source of [own?.colors, f.colors]) {
47
+ const list = Array.isArray(source) ? { field: source[0], ring: source[1], ink: source[2], accent: source[3], signature: source[4] } : source;
48
+ for (const key of ["field", "ring", "ink", "accent", "signature"]) {
49
+ const c = colourOf(list?.[key]);
50
+ if (c !== undefined)
51
+ look[key] = c;
52
+ }
53
+ }
54
+ const ringText = words.ringText;
55
+ const ring = ringText === false ? undefined : typeof ringText === "string" ? [cleanText(ringText, 32) ?? "", ""] : Array.isArray(ringText) ? [cleanText(ringText[0], 32) ?? "", cleanText(ringText[1], 32) ?? ""] : undefined;
56
+ const number = typeof words.number === "number" || typeof words.number === "string" ? String(words.number).replace(/[^A-Za-z0-9]/g, "").slice(0, 3).toUpperCase() : "";
57
+ const sig = words.signature;
58
+ return {
59
+ design,
60
+ look,
61
+ image: typeof words.image === "string" ? words.image : undefined,
62
+ flag: typeof words.flag === "string" ? words.flag.trim().toLowerCase().slice(0, 12) : undefined,
63
+ flags,
64
+ theme,
65
+ text: cleanText(words.text),
66
+ ring,
67
+ number: number === "" ? undefined : number,
68
+ name: cleanText(words.name, 14),
69
+ signature: sig === false || sig === undefined || sig === null ? undefined : typeof sig === "number" ? (Number.isFinite(sig) ? sig : undefined) : cleanText(sig, 40),
70
+ };
71
+ }
72
+ /** A face's own fields that are set (a field of `undefined` does not replace the design's). */
73
+ const definedOnly = (face) => Object.fromEntries(Object.entries(face).filter(([, v]) => v !== undefined && !(typeof v === "number" && !Number.isFinite(v))));
74
+ /** The ink of one line of lettering, with the outline under it when there is one. */
75
+ function word(text, o, x, y, fill, outline, outlineWidth = 0) {
76
+ const d = linePath(text, o, x, y);
77
+ const join = ` fill="none" stroke-linejoin="miter" stroke-miterlimit="2"`;
78
+ return (outline === undefined ? "" : `<path d="${d}"${join} stroke="${outline}" stroke-width="${num(o.weight * o.size + outlineWidth, 3)}"/>`) + `<path d="${d}"${join} stroke="${fill}" stroke-width="${num(o.weight * o.size, 3)}"/>`;
79
+ }
80
+ /** The letters of the ring, on the top and on the bottom, set on the circle of radius `r` with letters `size` tall, in the ink. */
81
+ function ringLetters(ring, r, size, ink) {
82
+ const spec = { weight: 0.17, wide: 1.12, track: 0.08 };
83
+ let out = "";
84
+ const set = (text, centre, inside) => {
85
+ if (text === "")
86
+ return;
87
+ const s = Math.min(size, fitSize(text, spec, r * 2.3, size));
88
+ const o = { ...spec, size: s };
89
+ out += `<path d="${arcPath(text, o, r, centre, inside)}" fill="none" stroke="${ink}" stroke-width="${num(o.weight * s, 3)}" stroke-linejoin="miter" stroke-miterlimit="2"/>`;
90
+ };
91
+ set(ring[0], 0, false);
92
+ set(ring[1], 180, true);
93
+ return out;
94
+ }
95
+ /** The disc's flat ground, and the ring band when there is one (inner radius `ri`). */
96
+ const ground = (look, ringed, ri) => `<circle r=".97" fill="${look.field}"/>` + (ringed ? `<path d="M0-.97A.97.97 0 1 1 0 .97A.97.97 0 1 1 0-.97ZM0-${ri}A${ri} ${ri} 0 1 0 0 ${ri}A${ri} ${ri} 0 1 0 0-${ri}Z" fill="${look.ring}" fill-rule="evenodd"/>` : "");
97
+ /** What the picture of a face is: a safe image fitted into a box, or `undefined`. The id prefix keeps two faces in a page apart. */
98
+ function picture(f, prefix, cx, cy, w, h) {
99
+ const fit = (img, tag) => {
100
+ const [x, y, bw, bh] = img.box;
101
+ const k = Math.min(w / bw, h / bh);
102
+ const cid = `${prefix}${tag}c`;
103
+ const t = `translate(${num(cx - (x + bw / 2) * k, 3)} ${num(cy - (y + bh / 2) * k, 3)}) scale(${num(k, 5)})`;
104
+ return { defs: `<clipPath id="${cid}"><rect x="${num(x, 3)}" y="${num(y, 3)}" width="${num(bw, 3)}" height="${num(bh, 3)}"/></clipPath>`, body: `<g transform="${t}" clip-path="url(#${cid})" color="${f.look.accent}">${img.markup}</g>`, width: bw * k, height: bh * k };
105
+ };
106
+ if (f.image !== undefined) {
107
+ const img = safeImage(f.image, `${prefix}i`);
108
+ if (img !== undefined)
109
+ return fit(img, "i");
110
+ }
111
+ if (f.flag !== undefined) {
112
+ const code = f.flag;
113
+ if (f.flags !== undefined) {
114
+ let svg;
115
+ try {
116
+ svg = f.flags(code, { shape: "rect", size: 120, theme: f.theme });
117
+ }
118
+ catch {
119
+ svg = undefined;
120
+ }
121
+ const img = safeImage(svg, `${prefix}f`);
122
+ if (img !== undefined)
123
+ return fit(img, "f");
124
+ }
125
+ if (code === "ca")
126
+ return { defs: "", body: canadaFlag(cx, cy, Math.min(w, h * 2)) + `<rect x="${num(cx - Math.min(w, h * 2) / 2, 3)}" y="${num(cy - Math.min(w, h * 2) / 4, 3)}" width="${num(Math.min(w, h * 2), 3)}" height="${num(Math.min(w, h * 2) / 2, 3)}" fill="none" stroke="#9a9a9a" stroke-width=".008"/>`, width: Math.min(w, h * 2), height: Math.min(w, h * 2) / 2 };
127
+ if (code === "us")
128
+ return { defs: "", body: usaFlag(cx, cy, Math.min(w, h * 1.9)), width: Math.min(w, h * 1.9), height: Math.min(w, h * 1.9) / 1.9 };
129
+ }
130
+ return undefined;
131
+ }
132
+ const LETTERS = { weight: 0.22, wide: 1.32, slant: 0.1, track: 0.02 };
133
+ /** The middle of the face in the style of any design: a picture (if there is one) with the words under it, or the words alone, or a number, or a star. `ri` is the radius of the open disc. */
134
+ function middle(f, prefix, ri, ink, outline) {
135
+ const pic = picture(f, prefix, 0, 0, ri * 1.3, ri * 0.82);
136
+ const text = f.text;
137
+ const number = f.number;
138
+ let defs = "";
139
+ let body = "";
140
+ if (number !== undefined) {
141
+ const s = fitSize(number, LETTERS, ri * 1.3, ri * 1.0);
142
+ body += word(number, { ...LETTERS, size: s }, 0, -0.02, f.look.accent, outline ?? ink, s * 0.1);
143
+ if (text !== undefined)
144
+ body += word(text, { ...LETTERS, size: fitSize(text, LETTERS, ri * 1.2, ri * 0.18) }, 0, ri * 0.74, ink);
145
+ return { defs, body };
146
+ }
147
+ if (pic !== undefined) {
148
+ const withText = text !== undefined;
149
+ const dy = withText ? -ri * 0.2 : 0;
150
+ const placed = picture(f, prefix, 0, dy, ri * (withText ? 1.2 : 1.5), ri * (withText ? 0.8 : 1.4));
151
+ defs += placed.defs;
152
+ body += placed.body;
153
+ if (withText) {
154
+ const y = dy + placed.height / 2 + ri * 0.26;
155
+ const s = fitSize(text, LETTERS, Math.sqrt(Math.max(0.01, ri * ri - y * y)) * 1.85, ri * 0.2);
156
+ body += word(text, { ...LETTERS, size: s }, 0, y, ink);
157
+ }
158
+ return { defs, body };
159
+ }
160
+ if (text !== undefined && f.design !== "souvenir") {
161
+ const s = fitSize(text, LETTERS, ri * 1.6, ri * 0.5);
162
+ body += word(text, { ...LETTERS, size: s }, 0, 0, ink, outline, s * 0.12);
163
+ return { defs, body };
164
+ }
165
+ if (text !== undefined) {
166
+ body += star(0, -ri * 0.2, ri * 0.4, f.look.accent);
167
+ body += word(text, { ...LETTERS, size: fitSize(text, LETTERS, ri * 1.3, ri * 0.2) }, 0, ri * 0.5, ink);
168
+ return { defs, body };
169
+ }
170
+ body += star(0, 0, ri * 0.5, f.look.accent);
171
+ return { defs, body };
172
+ }
173
+ /** A signature across the lower half of the face. */
174
+ function signed(f) {
175
+ if (f.signature === undefined)
176
+ return "";
177
+ const k = 1.12;
178
+ const sw = 0.034 / k;
179
+ return `<path d="${signaturePath(f.signature)}" transform="translate(.02 .2) rotate(-9) scale(${k})" fill="none" stroke="${f.look.signature}" stroke-width="${num(sw, 4)}" stroke-linecap="round" stroke-linejoin="round" opacity=".94"/>`;
180
+ }
181
+ /** The leaf of the white face, big, with the bands. */
182
+ function leaf(cx, cy, w, fill) {
183
+ return `<path d="${LEAF_PATH}" transform="translate(${num(cx, 3)} ${num(cy, 3)}) scale(${num(w / 2, 4)})" fill="${fill}"/>`;
184
+ }
185
+ /** The face of a design, in the face's units (a disc of radius 1, up is up): its definitions and its drawing. `prefix` makes every id unique to this puck. */
186
+ export function faceMarkup(face, design, own, prefix, flags, theme = "inherit") {
187
+ const f = resolve(face, design, own, typeof flags === "function" ? flags : undefined, theme);
188
+ const look = f.look;
189
+ const clip = `${prefix}clip`;
190
+ let defs = `<clipPath id="${clip}"><circle r=".965"/></clipPath>`;
191
+ let body = "";
192
+ const ringed = f.ring !== undefined && f.design !== "canada-white";
193
+ switch (f.design) {
194
+ case "canada-white": {
195
+ // A white face, the flag's red bands left and right, a big red leaf and the country under it.
196
+ body += ground(look, false, 0);
197
+ body += `<rect x="-1" y="-1" width=".42" height="2" fill="${look.accent}"/><rect x=".58" y="-1" width=".42" height="2" fill="${look.accent}"/>`;
198
+ const pic = picture(f, prefix, 0, -0.1, 1.0, 1.0);
199
+ if (pic !== undefined) {
200
+ defs += pic.defs;
201
+ body += pic.body;
202
+ }
203
+ else
204
+ body += leaf(0, -0.22, 1.0, look.accent);
205
+ if (f.text !== undefined)
206
+ body += word(f.text, { ...LETTERS, size: fitSize(f.text, LETTERS, 0.98, 0.17) }, 0, 0.6, look.accent);
207
+ if (f.ring !== undefined)
208
+ body += ringLetters(f.ring, 0.88, 0.09, look.accent);
209
+ break;
210
+ }
211
+ case "usa": {
212
+ const ri = 0.73;
213
+ body += ground(look, true, ri);
214
+ if (f.image !== undefined || f.flag !== undefined) {
215
+ // The caller's own picture takes the place of the stars and stripes.
216
+ const mid = middle(f, prefix, ri, look.ring, undefined);
217
+ body += mid.body;
218
+ defs += mid.defs;
219
+ }
220
+ else {
221
+ body += `<g clip-path="url(#${prefix}u)"><g transform="scale(${num(ri, 3)})">${starsAndStripes(2, 2, 1.04, 0.92, look.accent, "#ffffff", look.ring)}</g></g>`;
222
+ defs += `<clipPath id="${prefix}u"><circle r="${ri}"/></clipPath>`;
223
+ if (f.text !== undefined) {
224
+ const s = fitSize(f.text, LETTERS, 1.05, 0.3);
225
+ body += word(f.text, { ...LETTERS, size: s }, 0, 0.34, "#ffffff", look.ring, s * 0.16);
226
+ }
227
+ }
228
+ if (f.ring !== undefined)
229
+ body += ringLetters(f.ring, 0.855, 0.115, look.ink);
230
+ break;
231
+ }
232
+ case "usa-navy": {
233
+ // Navy, a red ring with white letters between two white hairlines, five stars over the country, and a rule.
234
+ body += ground(look, false, 0);
235
+ const ro = 0.9, ri = 0.69;
236
+ body += `<path d="M0-${ro}A${ro} ${ro} 0 1 1 0 ${ro}A${ro} ${ro} 0 1 1 0-${ro}ZM0-${ri}A${ri} ${ri} 0 1 0 0 ${ri}A${ri} ${ri} 0 1 0 0-${ri}Z" fill="${look.ring}" fill-rule="evenodd"/>`;
237
+ body += `<circle r=".935" fill="none" stroke="#ffffff" stroke-width=".022"/><circle r=".665" fill="none" stroke="#ffffff" stroke-width=".022"/>`;
238
+ if (f.ring !== undefined)
239
+ body += ringLetters(f.ring, 0.795, 0.105, look.ink);
240
+ for (let i = 0; i < 5; i++) {
241
+ const a = ((-40 + i * 20) * Math.PI) / 180;
242
+ body += star(0.5 * Math.sin(a) * 1.0, -0.5 * Math.cos(a) * 1.0 + 0.0, 0.052, look.accent, (-40 + i * 20));
243
+ }
244
+ if (f.text !== undefined || f.image !== undefined || f.flag !== undefined) {
245
+ const mid = middle({ ...f, number: undefined }, prefix, 0.62, look.ink, look.ring);
246
+ body += `<g transform="translate(0 .1)">${mid.body}</g>`;
247
+ defs += mid.defs;
248
+ body += `<path d="M-.4 .42H.4" stroke="${look.ring}" stroke-width=".022" stroke-linecap="round"/>`;
249
+ }
250
+ break;
251
+ }
252
+ case "player": {
253
+ // A jersey's face: the surname on the ring, the number big, a thin gold ring inside the band.
254
+ body += ground(look, true, 0.74);
255
+ body += `<circle r=".735" fill="none" stroke="${look.accent}" stroke-width=".02"/>`;
256
+ const ring = [f.name ?? f.ring?.[0] ?? "", f.ring?.[1] ?? ""];
257
+ body += ringLetters(ring, 0.855, 0.12, look.ink);
258
+ const mid = middle({ ...f, text: undefined, flag: undefined }, prefix, 0.72, look.ink, look.ink);
259
+ body += mid.body;
260
+ defs += mid.defs;
261
+ break;
262
+ }
263
+ default: {
264
+ // The flag with the country under it, the red face with the big leaf, and the generic souvenir share one layout: a ring when there is ring text, and the middle.
265
+ const ri = 0.74;
266
+ body += ground(look, ringed, ri);
267
+ if (f.design === "canada")
268
+ body += `<circle r="${ri}" fill="none" stroke="${look.ring}" stroke-width=".018"/>`;
269
+ if (ringed && f.ring !== undefined)
270
+ body += ringLetters(f.ring, 0.855, 0.115, look.ink);
271
+ const redLeaf = f.design === "canada-red" && f.image === undefined && f.flag === undefined && f.number === undefined;
272
+ if (redLeaf) {
273
+ body += leaf(0, -0.24, 0.98, look.accent);
274
+ const text = f.text;
275
+ if (text !== undefined)
276
+ body += word(text, { ...LETTERS, size: fitSize(text, LETTERS, 0.92, 0.14) }, 0, 0.43, look.accent);
277
+ }
278
+ else {
279
+ const dark = f.design === "souvenir" ? look.ring : f.design === "canada" ? look.accent : look.ink;
280
+ const mid = middle(f, prefix, ringed ? ri : 0.92, dark, undefined);
281
+ body += mid.body;
282
+ defs += mid.defs;
283
+ }
284
+ }
285
+ }
286
+ body += signed(f);
287
+ return { defs, body: `<g clip-path="url(#${clip})">${body}</g>` };
288
+ }
@@ -0,0 +1,8 @@
1
+ /** A drawing made safe: the box of its coordinates and the markup of what it holds. */
2
+ export interface SafeImage {
3
+ /** x, y, width and height of the drawing's own coordinates (its viewBox). */
4
+ box: readonly [number, number, number, number];
5
+ markup: string;
6
+ }
7
+ /** The markup of an SVG made safe, or `undefined` when there is none to be had: not text, too long, no `<svg>` root, nothing drawable left. `prefix` is put in front of every id and every reference to one. */
8
+ export declare function safeImage(source: unknown, prefix: string): SafeImage | undefined;