@heroiclands/package-build 22.14.3 → 22.15.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 (62) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CONTENT.md +25 -1
  3. package/bin/content-build.mjs +3 -7
  4. package/bin/package-build.mjs +25 -0
  5. package/docs/api.md +64 -36
  6. package/docs/commands.md +65 -9
  7. package/docs/configuration.md +6 -3
  8. package/docs/content-format.md +187 -140
  9. package/docs/diagnostics.md +1 -1
  10. package/docs/getting-started.md +27 -3
  11. package/engine/base-compiler.mjs +3 -1
  12. package/engine/being-age.mjs +16 -24
  13. package/engine/calendar-artifacts.mjs +153 -0
  14. package/engine/calendar-notes.mjs +64 -16
  15. package/engine/calendars.mjs +129 -0
  16. package/engine/content-images.mjs +20 -10
  17. package/engine/content-index.mjs +3 -1
  18. package/engine/exported-scene.mjs +230 -0
  19. package/engine/generate.mjs +1 -0
  20. package/engine/ids.mjs +1 -1
  21. package/engine/index.mjs +6 -0
  22. package/engine/infobox.mjs +4 -0
  23. package/engine/itinerary-scenes.mjs +170 -0
  24. package/engine/map-build.mjs +10 -45
  25. package/engine/map-dot.mjs +6 -3
  26. package/engine/map-graphviz.mjs +50 -131
  27. package/engine/map-notes.mjs +26 -3
  28. package/engine/map-places.mjs +12 -6
  29. package/engine/map-raster.mjs +49 -0
  30. package/engine/note-dates.mjs +125 -85
  31. package/engine/note-vocabulary.mjs +22 -6
  32. package/engine/pdf-build.mjs +57 -1
  33. package/engine/pdf-render.mjs +48 -7
  34. package/engine/reckoning-markers.mjs +207 -0
  35. package/engine/scenes.mjs +113 -26
  36. package/engine/site-build.mjs +11 -13
  37. package/engine/site-maps.mjs +5 -35
  38. package/engine/site-preview.mjs +6 -1
  39. package/engine/social-tie-terms.mjs +3 -0
  40. package/engine/social-ties.mjs +30 -19
  41. package/package.json +3 -1
  42. package/types/engine/being-age.d.mts +2 -2
  43. package/types/engine/calendar-artifacts.d.mts +16 -0
  44. package/types/engine/calendar-notes.d.mts +1 -1
  45. package/types/engine/calendars.d.mts +65 -0
  46. package/types/engine/content-images.d.mts +7 -4
  47. package/types/engine/exported-scene.d.mts +12 -0
  48. package/types/engine/index.d.mts +2 -0
  49. package/types/engine/itinerary-scenes.d.mts +29 -0
  50. package/types/engine/map-build.d.mts +4 -13
  51. package/types/engine/map-graphviz.d.mts +17 -59
  52. package/types/engine/map-places.d.mts +5 -1
  53. package/types/engine/map-raster.d.mts +8 -0
  54. package/types/engine/note-dates.d.mts +6 -0
  55. package/types/engine/note-vocabulary.d.mts +3 -3
  56. package/types/engine/pdf-render.d.mts +16 -1
  57. package/types/engine/reckoning-markers.d.mts +27 -0
  58. package/types/engine/scenes.d.mts +9 -2
  59. package/types/engine/site-build.d.mts +1 -5
  60. package/types/engine/site-maps.d.mts +2 -7
  61. package/types/engine/social-tie-terms.d.mts +2 -0
  62. package/types/engine/social-ties.d.mts +2 -1
@@ -78,6 +78,7 @@ import { indexRecordsFor } from "./content-index.mjs";
78
78
  // A being's computed `age` — see `engine/being-age.mjs`. `pdf-build.mjs`
79
79
  // reuses `collectContentPages`'s pages, so wiring it here reaches the book too.
80
80
  import { applyComputedBeingAge, presentAmongRecords } from "./being-age.mjs";
81
+ import { reckoningContext } from "./reckoning-markers.mjs";
81
82
  import { isNoteRecord, noteFile } from "./index-records.mjs";
82
83
  // The one statement of what an empty body means, shared with the index.
83
84
  import { isStubNote } from "./note-state.mjs";
@@ -196,19 +197,20 @@ export function collectContentPages(contentBase, ctx) {
196
197
  // The package's declared present, read once from the same corpus the site
197
198
  // and the PDF share — see `engine/being-age.mjs`. `pdf-build.mjs` reaches
198
199
  // this through the pages this function returns, so one call covers both.
199
- const present = presentAmongRecords(
200
+ const records =
200
201
  ctx.records ??
201
- indexRecordsFor({
202
- contentBase,
203
- config: ctx.config,
204
- skipDirectories: ctx.skipDirectories,
205
- }),
206
- );
202
+ indexRecordsFor({
203
+ contentBase,
204
+ config: ctx.config,
205
+ skipDirectories: ctx.skipDirectories,
206
+ });
207
+ const present = presentAmongRecords(records);
208
+ const dates = reckoningContext({ notes: records });
207
209
 
208
210
  for (const file of siteCorpusFiles(contentBase, ctx)) {
209
211
  const note = readNote(file, ctx);
210
212
  if (!note) continue;
211
- applyComputedBeingAge(note.fm, present);
213
+ applyComputedBeingAge(note.fm, present, dates);
212
214
  const { fm, body, bodyLine } = note;
213
215
  // The configuration's, never a note's: `package:` is retired, so every
214
216
  // note in the tree belongs to the package this repository compiles.
@@ -987,15 +989,12 @@ export function sitePageDecorator(config, index) {
987
989
  * {@link module:engine/sql-tables.prepareSqlTables}. A page authoring an
988
990
  * `sql` directive with none prepared is a table error: nothing here runs a
989
991
  * query.
990
- * @param {(engine: string) => string|undefined} [options.locate] - How a
991
- * GraphViz engine's binary is found, for the maps; the real lookup by
992
- * default.
993
992
  * @returns {{gates: object, stats: object|null, tableErrors: object[],
994
993
  * wikiErrors: object[], imageErrors: object[], mapFindings: object[],
995
994
  * manifests: object|null}} `mapFindings` is what drawing the maps found —
996
995
  * warnings, never a reason to fail the build.
997
996
  */
998
- export function buildSite({ config, sqlTables, locate } = {}) {
997
+ export function buildSite({ config, sqlTables } = {}) {
999
998
  const resolved = config ?? loadPackConfig();
1000
999
  const site = resolved.site;
1001
1000
  const scheme = resolved.publish.address;
@@ -1179,7 +1178,6 @@ export function buildSite({ config, sqlTables, locate } = {}) {
1179
1178
  foreignIndex: gates.foreign.index,
1180
1179
  config: resolved,
1181
1180
  base,
1182
- ...(locate ? { locate } : {}),
1183
1181
  })
1184
1182
  : { maps: new Map(), findings: [] };
1185
1183
 
@@ -23,10 +23,8 @@
23
23
  * and every `href` in it composes `<base><slug>/` the way every other href
24
24
  * the build renders does.
25
25
  *
26
- * GraphViz draws, and a site never fails for want of a map: when the engine is
27
- * absent the build says so once, as a warning, and writes every page as it
28
- * would without maps. `site.maps: false` says the site carries none and asks
29
- * nothing of GraphViz.
26
+ * The npm Graphviz runtime draws each map. `site.maps: false` says the site
27
+ * carries none and skips map rendering.
30
28
  *
31
29
  * The drawings land under `build/map/site/` — beside the author's own
32
30
  * drawings under `build/map/`, and apart from them, because the site's
@@ -40,7 +38,6 @@ import fs from "node:fs";
40
38
  import path from "node:path";
41
39
 
42
40
  import { buildMaps, relatedPlaces } from "./map-build.mjs";
43
- import { GRAPHVIZ_INSTALL, findGraphviz } from "./map-graphviz.mjs";
44
41
  import { mapWorld } from "./map-places.mjs";
45
42
 
46
43
  /**
@@ -50,13 +47,6 @@ import { mapWorld } from "./map-places.mjs";
50
47
  */
51
48
  export const SITE_MAP_DIR = "build/map/site";
52
49
 
53
- /**
54
- * The engine the map from a place is drawn with.
55
- *
56
- * @type {string}
57
- */
58
- const ENGINE = "neato";
59
-
60
50
  /**
61
51
  * A drawing as the page writer receives it.
62
52
  *
@@ -106,16 +96,12 @@ export function inlineSvg(svg) {
106
96
  * @param {object} opts.config - The resolved configuration.
107
97
  * @param {string} opts.base - The site base every page is served under,
108
98
  * ending in a slash: what every name in a drawing links through.
109
- * @param {(engine: string) => string|undefined} [opts.locate] - How the
110
- * engine's binary is found; the real lookup by default.
111
99
  * @returns {{maps: Map<string, SiteMap>, findings: Array<{file: string,
112
100
  * line?: number, column?: number, severity: "error"|"warning",
113
101
  * message: string}>}} The drawings, keyed by the URL of the page each
114
- * belongs to, and what the drawing found — one warning naming what to
115
- * install when GraphViz is absent, and a warning for each relation that
116
- * names no place.
102
+ * belongs to, and warnings for relations that name no place.
117
103
  */
118
- export function drawSiteMaps({ records, foreignIndex, config, base, locate = findGraphviz }) {
104
+ export function drawSiteMaps({ records, foreignIndex, config, base }) {
119
105
  /** @type {Map<string, SiteMap>} */
120
106
  const maps = new Map();
121
107
  const outDir = path.join(config.rootDir, ...SITE_MAP_DIR.split("/"));
@@ -123,22 +109,6 @@ export function drawSiteMaps({ records, foreignIndex, config, base, locate = fin
123
109
  // the map an earlier run drew.
124
110
  fs.rmSync(outDir, { recursive: true, force: true });
125
111
 
126
- if (!locate(ENGINE)) {
127
- return {
128
- maps,
129
- findings: [
130
- {
131
- file: config.rootDir,
132
- severity: "warning",
133
- message:
134
- `GraphViz's \`${ENGINE}\` is not installed, so no place page carries ` +
135
- `the map from that place; ${GRAPHVIZ_INSTALL} to draw them, or set ` +
136
- "`site.maps: false` to say the site carries none",
137
- },
138
- ],
139
- };
140
- }
141
-
142
112
  const world = mapWorld({
143
113
  records,
144
114
  foreignIndex,
@@ -152,7 +122,7 @@ export function drawSiteMaps({ records, foreignIndex, config, base, locate = fin
152
122
  const centres = relatedPlaces(world.places).filter((s) => world.places.get(s)?.local);
153
123
  if (centres.length === 0) return { maps, findings: [] };
154
124
 
155
- const { findings } = buildMaps({ world, outDir, from: centres, locate });
125
+ const { findings } = buildMaps({ world, outDir, from: centres });
156
126
  for (const centre of centres) {
157
127
  const place = world.places.get(centre);
158
128
  if (!place?.url) continue;
@@ -14,6 +14,7 @@ import { noteAddressContext, decodeNoteAddresses, encodeAddresses } from "./note
14
14
  import { noteFile } from "./index-records.mjs";
15
15
  import { foundryIdentities } from "./foundry-entries.mjs";
16
16
  import { applyComputedBeingAge, presentAmongRecords } from "./being-age.mjs";
17
+ import { reckoningContext } from "./reckoning-markers.mjs";
17
18
  import { cachedMetadataIndexes, noContentIndexPackages } from "./metadata-index.mjs";
18
19
  import { buildSiteIndex } from "./site-index.mjs";
19
20
  import { openNotesDatabase, prepareSqlTables, findSqlBlocks } from "./sql-tables.mjs";
@@ -133,7 +134,11 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
133
134
  ...noteAddressContext(config),
134
135
  package: config.contentPackage,
135
136
  });
136
- applyComputedBeingAge(fm, presentAmongRecords(snapshot.records));
137
+ applyComputedBeingAge(
138
+ fm,
139
+ presentAmongRecords(snapshot.records),
140
+ reckoningContext({ notes: snapshot.records }),
141
+ );
137
142
  } catch (error) {
138
143
  return {
139
144
  ok: false,
@@ -9,3 +9,6 @@ export const SOCIAL_TIES = Object.freeze([
9
9
  { term: "rival", meaning: "Opposes the subject without implacable hostility." },
10
10
  { term: "nemesis", meaning: "Opposes the subject personally and implacably." },
11
11
  ]);
12
+
13
+ /** The note types a being can name as a social tie. */
14
+ export const SOCIAL_TIE_TARGET_TYPES = Object.freeze(["being", "affiliation"]);
@@ -8,14 +8,13 @@
8
8
  * SPDX-License-Identifier: GPL-3.0-or-later
9
9
  */
10
10
 
11
- import { parseAddress, renderAddress } from "./address.mjs";
11
+ import { acceptsType, parseAddress, renderAddress } from "./address.mjs";
12
12
  import { positionOfFrontmatterPath } from "./diagnostics.mjs";
13
- import { SOCIAL_TIES } from "./social-tie-terms.mjs";
13
+ import { SOCIAL_TIES, SOCIAL_TIE_TARGET_TYPES } from "./social-tie-terms.mjs";
14
14
 
15
- export { SOCIAL_TIES };
15
+ export { SOCIAL_TIES, SOCIAL_TIE_TARGET_TYPES };
16
16
 
17
17
  const TERMS = new Set(SOCIAL_TIES.map(({ term }) => term));
18
- const ACCEPTED = new Set(["being", "affiliation"]);
19
18
 
20
19
  /** Validate a being's map of defining relationships. */
21
20
  export function checkSocialTies(note, { index } = {}) {
@@ -38,6 +37,7 @@ export function checkSocialTies(note, { index } = {}) {
38
37
  },
39
38
  ];
40
39
  const findings = [];
40
+ const seen = new Map();
41
41
  for (const [key, term] of Object.entries(ties)) {
42
42
  const tuple = parseAddress(
43
43
  key,
@@ -54,25 +54,36 @@ export function checkSocialTies(note, { index } = {}) {
54
54
  ...at(key),
55
55
  message: `data.socialTies key ${JSON.stringify(key)} is not a complete Address (${tuple.reason}); write being or affiliation as its type`,
56
56
  });
57
- } else if (!ACCEPTED.has(tuple.type)) {
57
+ } else if (!acceptsType(tuple, SOCIAL_TIE_TARGET_TYPES)) {
58
58
  findings.push({
59
59
  ...at(key),
60
60
  message: `data.socialTies accepts being or affiliation, not ${tuple.type}`,
61
61
  });
62
- } else if (
63
- tuple.package === (index?.contentPackage ?? note.fm?.package) &&
64
- tuple.type === "being" &&
65
- tuple.shortcode === note.fm?.shortcode
66
- ) {
67
- findings.push({
68
- ...at(key),
69
- message: `data.socialTies cannot address this being itself`,
70
- });
71
- } else if (index?.addressHit && !index.addressHit(renderAddress(tuple))) {
72
- findings.push({
73
- ...at(key),
74
- message: `data.socialTies target ${renderAddress(tuple)} does not resolve`,
75
- });
62
+ } else {
63
+ const target = renderAddress(tuple);
64
+ if (seen.has(target)) {
65
+ findings.push({
66
+ ...at(key),
67
+ message: `data.socialTies names the same target ${target} as ${JSON.stringify(seen.get(target))}; keep one tie per target`,
68
+ });
69
+ } else {
70
+ seen.set(target, key);
71
+ }
72
+ if (
73
+ tuple.package === (index?.contentPackage ?? note.fm?.package) &&
74
+ tuple.type === "being" &&
75
+ tuple.shortcode === note.fm?.shortcode
76
+ ) {
77
+ findings.push({
78
+ ...at(key),
79
+ message: `data.socialTies cannot address this being itself`,
80
+ });
81
+ } else if (index?.addressHit && !index.addressHit(target)) {
82
+ findings.push({
83
+ ...at(key),
84
+ message: `data.socialTies target ${target} does not resolve`,
85
+ });
86
+ }
76
87
  }
77
88
  if (!TERMS.has(term))
78
89
  findings.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "22.14.3",
3
+ "version": "22.15.0",
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",
@@ -134,6 +134,8 @@
134
134
  "dependencies": {
135
135
  "@duckdb/node-api": "^1.5.5-r.4",
136
136
  "@foundryvtt/foundryvtt-cli": "^3.0.4",
137
+ "@resvg/resvg-js": "^2.6.2",
138
+ "@viz-js/viz": "^3.30.0",
137
139
  "acorn": "^8.18.0",
138
140
  "archiver": "^8.0.0",
139
141
  "classic-level": "^3.0.0",
@@ -21,7 +21,7 @@ export function parseAgeMagnitude(raw: unknown): {
21
21
  * @returns {number|null} The age, or `null` where `born` is `unknown` or
22
22
  * absent, where no present is declared, or where either date fails to parse.
23
23
  */
24
- export function computeAge(bornRaw: unknown, presentRaw: unknown): number | null;
24
+ export function computeAge(bornRaw: unknown, presentRaw: unknown, dateContext?: {}): number | null;
25
25
  /**
26
26
  * Fill in a being's computed `age`, and the numeric companion an estimate
27
27
  * carries beside it — mutating the frontmatter object handed in, never a file
@@ -39,7 +39,7 @@ export function computeAge(bornRaw: unknown, presentRaw: unknown): number | null
39
39
  * `undefined`/`null` where it states none.
40
40
  * @returns {void}
41
41
  */
42
- export function applyComputedBeingAge(fm: object, presentRaw: unknown): void;
42
+ export function applyComputedBeingAge(fm: object, presentRaw: unknown, dateContext?: {}): void;
43
43
  /**
44
44
  * A being's `data.present`, read from every `place` note a batch of records or
45
45
  * frontmatter objects carries.
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Emit calendar definitions and import envelopes from the package's index.
3
+ *
4
+ * @param {object} opts
5
+ * @param {object} opts.config - Resolved content configuration.
6
+ * @param {string} opts.calendariaVersion - Version whose import format is targeted.
7
+ * @returns {{directory: string, calendars: number, files: number}}
8
+ */
9
+ export function emitCalendarArtifacts({ config, calendariaVersion }?: {
10
+ config: object;
11
+ calendariaVersion: string;
12
+ }): {
13
+ directory: string;
14
+ calendars: number;
15
+ files: number;
16
+ };
@@ -103,7 +103,7 @@ export function quarterDays(daysPerYear: number): number[];
103
103
  * its own package.
104
104
  * @returns {object} The definition.
105
105
  */
106
- export function compileCalendar({ note, invariants, contentPackage }: {
106
+ export function compileCalendar({ note, invariants, contentPackage, dateContext }: {
107
107
  note: object;
108
108
  invariants: object;
109
109
  contentPackage?: string | undefined;
@@ -35,6 +35,60 @@ export function canonicalYear(year: number, epoch?: number): number;
35
35
  * @returns {number} The sort key.
36
36
  */
37
37
  export function dateSortKey(year: number, month: number | null, day: number | null): number;
38
+ /**
39
+ * Days from an arbitrary canonical origin to a canonical year and day.
40
+ *
41
+ * Canonical years form a continuous integer line. Day one opens each year;
42
+ * calendar month divisions play no part in this count.
43
+ *
44
+ * @param {number} year - Canonical year.
45
+ * @param {number} day - Day of that year, starting at one.
46
+ * @param {number} originYear - Canonical year whose first day is offset zero.
47
+ * @param {number} daysPerYear - The world's authored year length.
48
+ * @returns {number} Signed whole-day offset.
49
+ */
50
+ export function canonicalDayOffset(year: number, day: number, originYear: number, daysPerYear: number): number;
51
+ /**
52
+ * The canonical year and day at a signed offset from an arbitrary origin.
53
+ * Flooring the division keeps the day within the year before the origin.
54
+ *
55
+ * @param {number} offset - Whole days from the origin.
56
+ * @param {number} originYear - Canonical year whose first day is offset zero.
57
+ * @param {number} daysPerYear - The world's authored year length.
58
+ * @returns {{year: number, day: number}} Canonical year and day.
59
+ */
60
+ export function canonicalDateFromOffset(offset: number, originYear: number, daysPerYear: number): {
61
+ year: number;
62
+ day: number;
63
+ };
64
+ /**
65
+ * Print a calendar-neutral date as `<year>.<day>[:HHMMSS]`.
66
+ *
67
+ * @param {{year: number, day: number, seconds?: number}} date - Canonical date.
68
+ * @param {number} daysPerYear - The world's authored year length.
69
+ * @returns {string} The canonical spelling.
70
+ */
71
+ export function formatCanonicalDate(date: {
72
+ year: number;
73
+ day: number;
74
+ seconds?: number;
75
+ }, daysPerYear: number): string;
76
+ /**
77
+ * Read the calendar-neutral `<year>.<day>[:HHMMSS]` spelling.
78
+ *
79
+ * This is distinct from an authored month date such as `720/6`; no calendar
80
+ * month list is consulted to read a canonical day of year.
81
+ *
82
+ * @param {unknown} value - Canonical date text.
83
+ * @param {number} daysPerYear - The world's authored year length.
84
+ * @returns {{year: number, day: number, seconds?: number}|null} The date, or
85
+ * `null` when the spelling or a component is invalid.
86
+ */
87
+ export function parseCanonicalDate(value: unknown, daysPerYear: number): {
88
+ year: number;
89
+ day: number;
90
+ seconds?: number;
91
+ } | null;
38
92
  /**
39
93
  * One month of a calendar, as the note declares it.
40
94
  *
@@ -101,6 +155,17 @@ export function monthStarts(months: readonly CalendarMonth[]): number[];
101
155
  * @returns {number} The day of the year.
102
156
  */
103
157
  export function dayOfYear(months: readonly CalendarMonth[], month: number, day: number): number;
158
+ /**
159
+ * Recover a calendar's month and day from an ordinal day of its year.
160
+ *
161
+ * @param {readonly CalendarMonth[]} months - The ordered month list.
162
+ * @param {number} ordinal - Day of year, starting at one.
163
+ * @returns {{month: number, day: number}|null} The named day, or `null` outside the year.
164
+ */
165
+ export function monthDayOfYear(months: readonly CalendarMonth[], ordinal: number): {
166
+ month: number;
167
+ day: number;
168
+ } | null;
104
169
  /**
105
170
  * How far into its cycle a moon is on a given day.
106
171
  *
@@ -32,12 +32,13 @@ export function parseImageDirective(raw?: string): {
32
32
  /**
33
33
  * The classes a figure carries, from a parsed directive.
34
34
  *
35
- * @param {{classes?: string[], float?: string}} [directive] - As parsed.
35
+ * @param {{classes?: string[], size?: string, float?: string}} [directive] - As parsed.
36
36
  * @returns {string} A space-separated class list, always naming
37
37
  * {@link IMAGE_FIGURE_CLASS} first.
38
38
  */
39
- export function figureClasses({ classes, float }?: {
39
+ export function figureClasses({ classes, size, float }?: {
40
40
  classes?: string[];
41
+ size?: string;
41
42
  float?: string;
42
43
  }): string;
43
44
  /**
@@ -58,13 +59,15 @@ export function escapeHtml(text: string): string;
58
59
  * @param {string} image.src - The address, resolved for the surface.
59
60
  * @param {string} [image.alt] - The alt text, which is also the caption.
60
61
  * @param {string[]} [image.classes] - Width classes, from the directive.
62
+ * @param {string} [image.size] - Named display size, from the directive.
61
63
  * @param {string} [image.float] - The float position, from the directive.
62
64
  * @returns {string} The figure, as one HTML block.
63
65
  */
64
- export function imageFigureHtml({ src, alt, classes, float }: {
66
+ export function imageFigureHtml({ src, alt, classes, size, float }: {
65
67
  src: string;
66
68
  alt?: string | undefined;
67
69
  classes?: string[] | undefined;
70
+ size?: string | undefined;
68
71
  float?: string | undefined;
69
72
  }): string;
70
73
  /**
@@ -249,7 +252,7 @@ export const IMAGE_CLASSES: Readonly<Record<string, {
249
252
  scope: string;
250
253
  describe: string;
251
254
  }>>;
252
- /** The named sizes an image directive accepts without applying a display size. */
255
+ /** The named sizes an image directive accepts. */
253
256
  export const IMAGE_SIZES: readonly string[];
254
257
  /**
255
258
  * The `float:` positions an image may take, and where each puts it.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Compile a Foundry Scene export into a pack document.
3
+ *
4
+ * Only the note-derived Scene ID, compendium metadata, and pins marked
5
+ * `#anchor` change. Every other authored Scene field passes through.
6
+ *
7
+ * @param {object} fm - The map note's frontmatter.
8
+ * @param {string} markdown - Its converted Markdown body.
9
+ * @param {object} [opts] - Pack metadata, derived JournalEntry ID, and asset resolver.
10
+ * @returns {object} The keyed Scene document.
11
+ */
12
+ export function buildExportedScene(fm: object, markdown: string, { journalEntryId, stats, resolveAddress }?: object): object;
@@ -31,7 +31,9 @@ export * as siteMaps from "./site-maps.mjs";
31
31
  export * as systems from "./systems.mjs";
32
32
  export * as calendars from "./calendars.mjs";
33
33
  export * as noteDates from "./note-dates.mjs";
34
+ export * as reckoningMarkers from "./reckoning-markers.mjs";
34
35
  export * as calendarNotes from "./calendar-notes.mjs";
36
+ export * as calendarArtifacts from "./calendar-artifacts.mjs";
35
37
  export * as beingAge from "./being-age.mjs";
36
38
  export * as contentSlug from "./content-slug.mjs";
37
39
  export * as contentAddress from "./content-address.mjs";
@@ -0,0 +1,29 @@
1
+ /** GraphViz's plain coordinates, converted to pixel positions on its PNG. */
2
+ export function pinPositions(plain: any, width: any, height: any): Map<any, any>;
3
+ /**
4
+ * Emit backgrounds into the staged package and return Scenes and the journals
5
+ * their pins need. The caller writes them into its Scene and Adventure packs.
6
+ */
7
+ export function buildItineraryScenes({ config, records, contentBase, journalSourceDirs, stats, foreign, }: {
8
+ config: any;
9
+ records: any;
10
+ contentBase: any;
11
+ journalSourceDirs?: never[] | undefined;
12
+ stats: any;
13
+ foreign?: {
14
+ index: Map<string, object>;
15
+ packages: Set<string>;
16
+ stale: Array<{
17
+ package: string;
18
+ reason: string;
19
+ }>;
20
+ } | undefined;
21
+ }): {
22
+ scenes: object[];
23
+ journal: any[];
24
+ findings: {
25
+ file: any;
26
+ severity: string;
27
+ message: string;
28
+ }[];
29
+ };
@@ -30,19 +30,13 @@ export function relatedPlaces(places: Map<string, import("./map-places.mjs").Map
30
30
  * @param {number} [opts.ranksep] - The tree's `ranksep`, inches.
31
31
  * @param {number} [opts.scale=1] - Multiplies every node's size and font.
32
32
  * @param {boolean} [opts.polities=true] - Whether the tree draws polities.
33
- * @param {(engine: string) => string|undefined} [opts.locate] - How a GraphViz
34
- * engine's binary is found; the real lookup by default.
35
- * @param {boolean} [opts.requireGraphviz=true] - Whether a missing engine is
36
- * an error. False, the `.dot` files are written, the renderings skipped,
37
- * and one warning returned.
38
33
  * @returns {{written: string[], findings: Array<{file: string, line?: number,
39
- * column?: number, severity: "error"|"warning", message: string}>,
40
- * skipped: boolean}} What was written, what was found, and whether the
41
- * rendering was skipped for want of GraphViz.
34
+ * column?: number, severity: "error"|"warning", message: string}>}} What was
35
+ * written and what was found.
42
36
  * @throws {Error} When no mode is asked for, a named place is not one, the
43
- * horizon is not a marker of the scale, or GraphViz is required and absent.
37
+ * horizon is not a marker of the scale.
44
38
  */
45
- export function buildMaps({ world, outDir, tree, root, from, chart, horizon, travel, engine, rankdir, nodesep, ranksep, scale, polities, locate, requireGraphviz, }: {
39
+ export function buildMaps({ world, outDir, tree, root, from, chart, horizon, travel, engine, rankdir, nodesep, ranksep, scale, polities, }: {
46
40
  world: import("./map-places.mjs").MapWorld;
47
41
  outDir: string;
48
42
  tree?: boolean | undefined;
@@ -57,8 +51,6 @@ export function buildMaps({ world, outDir, tree, root, from, chart, horizon, tra
57
51
  ranksep?: number | undefined;
58
52
  scale?: number | undefined;
59
53
  polities?: boolean | undefined;
60
- locate?: ((engine: string) => string | undefined) | undefined;
61
- requireGraphviz?: boolean | undefined;
62
54
  }): {
63
55
  written: string[];
64
56
  findings: Array<{
@@ -68,7 +60,6 @@ export function buildMaps({ world, outDir, tree, root, from, chart, horizon, tra
68
60
  severity: "error" | "warning";
69
61
  message: string;
70
62
  }>;
71
- skipped: boolean;
72
63
  };
73
64
  /**
74
65
  * The directory the maps land in, below a package's root.
@@ -1,68 +1,26 @@
1
1
  /**
2
- * Find a GraphViz engine's binary.
2
+ * Render a DOT source file to SVG, plain positions, or a PNG background.
3
3
  *
4
- * `PATH` first, then the directories a package manager installs into, so a
5
- * shell whose profile does not export Homebrew's directory still finds it.
6
- * The result is a path to a file that exists; whether it runs is the render's
7
- * business.
4
+ * A PNG uses the same Graphviz SVG layout as the vector map. SVG dimensions
5
+ * are in points; resvg starts at 96 pixels per inch, so the zoom converts
6
+ * the requested DPI to the same physical size as Graphviz's PNG output.
8
7
  *
9
- * @param {string} engine - One of {@link GRAPHVIZ_ENGINES}.
10
- * @param {object} [opts]
11
- * @param {Record<string, string|undefined>} [opts.env] - The environment to
12
- * read `PATH` from, defaulting to the process's.
13
- * @param {readonly string[]} [opts.fallbacks] - The directories tried after
14
- * `PATH`, defaulting to the package managers' ones.
15
- * @returns {string|undefined} The binary, or `undefined` when none is found.
16
- * @throws {Error} When `engine` is not a GraphViz engine this command uses.
17
- */
18
- export function findGraphviz(engine: string, { env, fallbacks }?: {
19
- env?: Record<string, string | undefined> | undefined;
20
- fallbacks?: readonly string[] | undefined;
21
- }): string | undefined;
22
- /**
23
- * What to say when an engine is not installed: the name, and what installs
24
- * it.
25
- *
26
- * @param {string} engine - The engine that was wanted.
27
- * @returns {string} The message.
28
- */
29
- export function graphvizMissingMessage(engine: string): string;
30
- /**
31
- * Render a `.dot` file with a GraphViz engine.
32
- *
33
- * The engine is run over the file rather than fed the text, so the file the
34
- * command keeps is exactly what was drawn. Warnings GraphViz prints — an
35
- * unknown font, an overlapping label — are returned rather than swallowed,
36
- * and a non-zero exit is an error naming the file.
37
- *
38
- * @param {string} dotPath - The `.dot` file.
39
- * @param {string} outPath - Where to write the rendering.
8
+ * @param {string} dotPath - The saved DOT source.
9
+ * @param {string} outPath - The output file.
40
10
  * @param {object} opts
41
- * @param {string} opts.binary - The engine's binary, from {@link findGraphviz}.
42
- * @param {string} [opts.format="svg"] - The `-T` format.
43
- * @param {string[]} [opts.args] - Further arguments, before the file.
44
- * @returns {{warnings: string}} What the engine printed on stderr.
45
- * @throws {Error} When the engine exits non-zero or cannot be run.
11
+ * @param {"dot"|"twopi"|"neato"} opts.engine - Layout engine.
12
+ * @param {"svg"|"plain"|"png"} [opts.format="svg"] - Output format.
13
+ * @param {2} [opts.nop] - Preserve positioned nodes as `neato -n2` does.
14
+ * @param {number} [opts.dpi=96] - PNG resolution.
15
+ * @returns {{warnings: string}} Renderer warnings.
46
16
  */
47
- export function renderDot(dotPath: string, outPath: string, { binary, format, args }: {
48
- binary: string;
49
- format?: string | undefined;
50
- args?: string[] | undefined;
17
+ export function renderDot(dotPath: string, outPath: string, { engine, format, nop, dpi }: {
18
+ engine: "dot" | "twopi" | "neato";
19
+ format?: "svg" | "plain" | "png" | undefined;
20
+ nop?: 2 | undefined;
21
+ dpi?: number | undefined;
51
22
  }): {
52
23
  warnings: string;
53
24
  };
54
- /**
55
- * The layout engines the map command may ask for. `dot` draws a tree by
56
- * rank, `twopi` the same tree radially, `neato` a graph by its edge lengths —
57
- * and, with every node pinned, the map from a place.
58
- *
59
- * @type {readonly string[]}
60
- */
25
+ /** The layout engines accepted by the map command. */
61
26
  export const GRAPHVIZ_ENGINES: readonly string[];
62
- /**
63
- * What installs GraphViz on the two platforms a contributor is likely to be
64
- * on, as the clause every "not installed" message ends with.
65
- *
66
- * @type {string}
67
- */
68
- export const GRAPHVIZ_INSTALL: string;
@@ -214,9 +214,13 @@ export type MapWorld = {
214
214
  */
215
215
  export type Containment = {
216
216
  /**
217
- * - The one `subType: world` place.
217
+ * - The world when exactly one is present.
218
218
  */
219
219
  world: string | undefined;
220
+ /**
221
+ * - Every `subType: world` place, sorted.
222
+ */
223
+ worlds: string[];
220
224
  /**
221
225
  * - Regions parented directly on the world,
222
226
  * in name order.
@@ -0,0 +1,8 @@
1
+ /** Rasterize an authored SVG map into the staged Foundry package. */
2
+ export function rasterizeMapSvg({ foundryPath, width, height, sceneId, config }: {
3
+ foundryPath: any;
4
+ width: any;
5
+ height: any;
6
+ sceneId: any;
7
+ config: any;
8
+ }): string;