@heroiclands/package-build 20.3.0 → 20.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 20.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 43188f2: A registry entry can now name an icon family, so a package that draws from two
8
+ of them can say so.
9
+
10
+ The SoHL icon legend states in its own prose that it uses Font Awesome plus
11
+ Game-Icons.net "for the arms, gear, and condition glyphs that Font Awesome does
12
+ not cover" — eighteen `ginf-*` classes the registry had no way to express. They
13
+ differ in more than a prefix: Game-Icons has no weights, so a `style` on such an
14
+ entry names something that does not exist, and it resolves to a different font,
15
+ which a PDF has to embed separately.
16
+
17
+ `family` defaults to `fontawesome`, so every existing entry is unchanged. A
18
+ style on an unstyled family is reported rather than silently ignored, because it
19
+ means the author expected a filled and hollow pair the family cannot spell.
20
+
21
+ Also adds `gem` and `gem-outline`, and repoints `diamond` at them. `fa-diamond`
22
+ is Font Awesome's playing-card suit and ships in solid only, so it could not
23
+ spell the hollow half of the Success Value scale it was being used for.
24
+
3
25
  ## 20.3.0
4
26
 
5
27
  ### Minor Changes
@@ -79,6 +79,57 @@ import path from "node:path";
79
79
  */
80
80
  export const ICON_STYLES = Object.freeze(["solid", "regular", "brands"]);
81
81
 
82
+ /**
83
+ * The icon families a registry entry may draw from.
84
+ *
85
+ * **Two, because the interface uses two.** The SoHL icon legend says so in its
86
+ * own prose: Font Awesome for most things, and Game-Icons.net *"for the arms,
87
+ * gear, and condition glyphs that Font Awesome does not cover"* — eighteen of
88
+ * them, `ginf-broadsword` and its kin (#391).
89
+ *
90
+ * They differ in more than a class prefix, which is why this is a family rather
91
+ * than a naming convention:
92
+ *
93
+ * - Font Awesome has **weights**, so an entry names a `style` and a filled and
94
+ * hollow pair is one icon twice. Game-Icons has none, so a `style` on such an
95
+ * entry names something that does not exist.
96
+ * - They resolve to **different fonts**. A PDF has to embed both, and find each
97
+ * codepoint in its own table — Font Awesome's from the file it ships,
98
+ * Game-Icons' from the `game-icons-codepoints.json` the consumer's own
99
+ * `build-icon-font.mjs` writes.
100
+ *
101
+ * `class` is the prefix the web surfaces use. `styled` says whether a `style`
102
+ * belongs on the entry at all, which is what lets a mistake be reported rather
103
+ * than rendered as a class nobody defined.
104
+ *
105
+ * @type {Readonly<Record<string, {class: string, styled: boolean, describe: string}>>}
106
+ */
107
+ export const ICON_FAMILIES = Object.freeze({
108
+ fontawesome: {
109
+ class: "fa",
110
+ styled: true,
111
+ describe: "Font Awesome Free",
112
+ },
113
+ "game-icons": {
114
+ class: "ginf",
115
+ styled: false,
116
+ describe: "the Game-Icons.net webfont a package builds for itself",
117
+ },
118
+ });
119
+
120
+ /** The family an entry that does not name one belongs to. */
121
+ export const DEFAULT_ICON_FAMILY = "fontawesome";
122
+
123
+ /**
124
+ * The family an entry draws from, named or defaulted.
125
+ *
126
+ * @param {{family?: string}} entry - A registry entry.
127
+ * @returns {string} The family name.
128
+ */
129
+ export function familyOf(entry) {
130
+ return entry?.family ?? DEFAULT_ICON_FAMILY;
131
+ }
132
+
82
133
  /**
83
134
  * The sizes a note may ask for, and what each means on a page.
84
135
  *
@@ -207,7 +258,14 @@ export const DEFAULT_ICONS = Object.freeze({
207
258
  affiliation: { style: "solid", icon: "certificate", label: "affiliation" },
208
259
  star: { style: "solid", icon: "star", label: "star" },
209
260
  "star-outline": { style: "regular", icon: "star", label: "hollow star" },
210
- diamond: { style: "solid", icon: "diamond", label: "diamond" },
261
+ // The Success Value scale. `fa-diamond` is Font Awesome's playing-card
262
+ // suit and ships in solid only, so it can spell no hollow half of a
263
+ // filled/hollow pair; `fa-gem` is a gemstone, has both weights, and is what
264
+ // a quality scale actually means. `diamond` stays as an alias of it so a
265
+ // note that already says `:icon-diamond:` keeps working.
266
+ gem: { style: "solid", icon: "gem", label: "value gem" },
267
+ "gem-outline": { style: "regular", icon: "gem", label: "unearned value gem" },
268
+ diamond: { style: "solid", icon: "gem", label: "value gem" },
211
269
  edit: { style: "solid", icon: "pen-to-square", label: "edit" },
212
270
  delete: { style: "solid", icon: "trash", label: "delete" },
213
271
  add: { style: "solid", icon: "plus", label: "add" },
@@ -281,9 +339,22 @@ const attr = (value) =>
281
339
  * @returns {string} An `<i>` element.
282
340
  */
283
341
  export function iconHtml(entry, attrs = {}) {
284
- const classes = [`fa-${attr(entry.style)}`, `fa-${attr(entry.icon)}`];
342
+ const family = ICON_FAMILIES[familyOf(entry)] ?? ICON_FAMILIES[DEFAULT_ICON_FAMILY];
343
+ const prefix = family.class;
344
+
345
+ // A styled family spells the weight and the name as two classes; an
346
+ // unstyled one has a single class and no weight to spell.
347
+ const classes =
348
+ family.styled ?
349
+ [`${prefix}-${attr(entry.style)}`, `${prefix}-${attr(entry.icon)}`]
350
+ : [`${prefix}-${attr(entry.icon)}`];
351
+
352
+ // The size classes are Font Awesome's, and the Game-Icons stylesheet this
353
+ // toolchain's consumers generate mirrors its box metrics deliberately, so
354
+ // they apply to both families.
285
355
  const sized = attrs.size ? ICON_SIZES[attrs.size] : undefined;
286
356
  if (sized) classes.push(sized.class);
357
+
287
358
  return `<i class="${classes.join(" ")}" role="img" aria-label="${attr(entry.label)}"></i>`;
288
359
  }
289
360
 
@@ -410,7 +481,19 @@ export function checkIconRegistry(registry, where = "icons") {
410
481
  });
411
482
  continue;
412
483
  }
413
- if (!ICON_STYLES.includes(entry.style)) {
484
+ const familyName = familyOf(entry);
485
+ const family = ICON_FAMILIES[familyName];
486
+ if (!family) {
487
+ findings.push({
488
+ severity: /** @type {const} */ ("warning"),
489
+ message:
490
+ `${at} names family \`${familyName}\`, and the families there ` +
491
+ `are: ${Object.keys(ICON_FAMILIES).join(", ")}`,
492
+ });
493
+ continue;
494
+ }
495
+
496
+ if (family.styled && !ICON_STYLES.includes(entry.style)) {
414
497
  findings.push({
415
498
  severity: /** @type {const} */ ("warning"),
416
499
  message:
@@ -419,6 +502,19 @@ export function checkIconRegistry(registry, where = "icons") {
419
502
  `absent from the font a book would embed`,
420
503
  });
421
504
  }
505
+
506
+ // A style on an unstyled family is not a harmless extra key: it says
507
+ // the author expected a weight, and the family has none, so what they
508
+ // get is not what they asked for.
509
+ if (!family.styled && entry.style !== undefined) {
510
+ findings.push({
511
+ severity: /** @type {const} */ ("warning"),
512
+ message:
513
+ `${at} names style \`${entry.style}\`, and ${family.describe} has ` +
514
+ `no weights — the style is ignored, so a filled and hollow pair ` +
515
+ `cannot be spelled this way`,
516
+ });
517
+ }
422
518
  if (typeof entry.icon !== "string" || !entry.icon) {
423
519
  findings.push({
424
520
  severity: /** @type {const} */ ("warning"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "20.3.0",
3
+ "version": "20.3.1",
4
4
  "description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "type": "module",
@@ -1,3 +1,12 @@
1
+ /**
2
+ * The family an entry draws from, named or defaulted.
3
+ *
4
+ * @param {{family?: string}} entry - A registry entry.
5
+ * @returns {string} The family name.
6
+ */
7
+ export function familyOf(entry: {
8
+ family?: string;
9
+ }): string;
1
10
  /**
2
11
  * Read the brace of an icon token.
3
12
  *
@@ -132,6 +141,38 @@ export function iconPlugin(registry?: Record<string, object>): (md: object) => v
132
141
  * @type {readonly string[]}
133
142
  */
134
143
  export const ICON_STYLES: readonly string[];
144
+ /**
145
+ * The icon families a registry entry may draw from.
146
+ *
147
+ * **Two, because the interface uses two.** The SoHL icon legend says so in its
148
+ * own prose: Font Awesome for most things, and Game-Icons.net *"for the arms,
149
+ * gear, and condition glyphs that Font Awesome does not cover"* — eighteen of
150
+ * them, `ginf-broadsword` and its kin (#391).
151
+ *
152
+ * They differ in more than a class prefix, which is why this is a family rather
153
+ * than a naming convention:
154
+ *
155
+ * - Font Awesome has **weights**, so an entry names a `style` and a filled and
156
+ * hollow pair is one icon twice. Game-Icons has none, so a `style` on such an
157
+ * entry names something that does not exist.
158
+ * - They resolve to **different fonts**. A PDF has to embed both, and find each
159
+ * codepoint in its own table — Font Awesome's from the file it ships,
160
+ * Game-Icons' from the `game-icons-codepoints.json` the consumer's own
161
+ * `build-icon-font.mjs` writes.
162
+ *
163
+ * `class` is the prefix the web surfaces use. `styled` says whether a `style`
164
+ * belongs on the entry at all, which is what lets a mistake be reported rather
165
+ * than rendered as a class nobody defined.
166
+ *
167
+ * @type {Readonly<Record<string, {class: string, styled: boolean, describe: string}>>}
168
+ */
169
+ export const ICON_FAMILIES: Readonly<Record<string, {
170
+ class: string;
171
+ styled: boolean;
172
+ describe: string;
173
+ }>>;
174
+ /** The family an entry that does not name one belongs to. */
175
+ export const DEFAULT_ICON_FAMILY: "fontawesome";
135
176
  /**
136
177
  * The sizes a note may ask for, and what each means on a page.
137
178
  *