@heroiclands/package-build 25.2.1 → 25.3.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.
@@ -297,36 +297,34 @@ Like a culture conflict, each fails the index and every command that reads it
297
297
  without collecting problems. The rules are under
298
298
  [a note's language](reference/format-details.md#a-notes-language).
299
299
 
300
- ## A worked case: a verse in columns
300
+ ## A worked case: versions of a work
301
301
 
302
- A [verse in columns](authoring/links-and-markup.md#verse-in-columns) has a
303
- positive whole-number `cols` value. Invalid counts are located at their
304
- value. With multiple columns, every content line needs a numeric prefix and
305
- one or more spaces. Missing prefixes point to the line's first content
306
- character; out-of-range or decreasing numbers point to the authored number.
307
-
308
- This row has a decreasing column number on its third content line:
302
+ [Versions of a work](authoring/links-and-markup.md#versions-and-stanza-labels)
303
+ share a page-local `work=` key and the same stanza shape. For example:
309
304
 
310
305
  ````markdown
311
- ```verse {cols=3 lang=en}
312
- 1 The first reading.
313
- 3 A later column.
314
- 2 Out of order within this row.
306
+ ```verse {work=watch lang=en}
307
+ The watch arrives.
308
+
309
+ The harbor wakes.
310
+ ```
311
+
312
+ ```verse {work=watch lang=en}
313
+ Another telling has only one stanza.
315
314
  ```
316
315
  ````
317
316
 
318
- The finding points to `2`. A blank line before it would start another row,
319
- where the column order begins again. A number above three or below one is
320
- out of bounds. Skipping a column is valid and leaves an empty cell; repeating
321
- a number is valid and adds another reading to that column. The first reading
322
- in every column is foreground and its later readings are grey.
317
+ The opening lines receive findings naming canto 1 and its differing stanza
318
+ counts, two and one. The first version is included in the disagreement. A key
319
+ used by only one version is also a finding at that fence's opening line.
323
320
 
324
- Language and native-font findings retain their authored locations inside a
325
- reading, including the column prefix and indentation before it. An unknown
326
- mapping or face points to that run's attribute; an unknown word or a character
327
- the face cannot draw points to the original word or character. A language-only
328
- Roman repeat is checked in its own language, independently of a native run.
329
- The build reports invalid input rather than publishing an inferred layout.
321
+ A concrete `enc=` creates its own script and Roman presentations from one
322
+ original; the display does not add a second authored version. Source newlines
323
+ join unless `/` ends the line. Blank lines and `//` end stanzas. A leading,
324
+ trailing or consecutive `***` without a stanza between is located at the break
325
+ itself. Language and encoding findings retain their positions in the single
326
+ authored original, including text inside names, directions and speaker labels.
327
+ The content index records that original once in `runs`.
330
328
 
331
329
  ## Emphasis in non-English spans
332
330
 
@@ -603,9 +601,10 @@ finding naming its `⟦U+XXXX⟧` spelling. Use that notation, or the chosen
603
601
  encoding's codage name, inside the literal markers. The encoder checks the
604
602
  codes; the Roman validator leaves their contents immutable.
605
603
 
606
- A verse fence uses its authored line endings for boundaries. A literal `/`
607
- inside it is an error at that slash. Visual indentation does not add periods
608
- to the encoded text. Source positions survive Markdown escapes and nested
604
+ A verse fence uses `/` for logical line endings, `//` or blank lines for
605
+ stanzas, and standalone `***` for cantos. A literal slash is written `\/`.
606
+ Visual indentation at a logical line boundary does not add periods to the
607
+ encoded text; a period on a joined continuation line remains text. Source positions survive Markdown escapes and nested
609
608
  `.emph` or `.name` spans.
610
609
 
611
610
  Language-run diagnostic columns count Unicode code points, so a supplementary
@@ -106,10 +106,9 @@ escapes are removed before encoding, and diagnostics retain the source position.
106
106
 
107
107
  Every `Zs` space separator and U+0009 through U+000D count as whitespace. A run
108
108
  of them is one word separator, which the encoding writes by its own rule.
109
- Newlines in prose are whitespace. In a verse fence, line endings supply line
110
- boundaries, blank lines supply stanza boundaries, and `--- {.canto}` on its
111
- own line supplies a canto boundary. Do not type a verse slash `/` inside the
112
- fence; an inline quotation of verse may use it. Verse indentation is layout,
109
+ Newlines join as whitespace. In a verse fence, `/` ends a logical line,
110
+ blank lines or `//` end stanzas, and standalone `***` ends a canto. A literal
111
+ slash is written `\/`. Indentation at a logical line boundary is layout,
113
112
  not a punctuation character added to the encoded words.
114
113
 
115
114
  A `.name` child in a language run reaches the encoding with standard `{…}`
@@ -495,58 +494,46 @@ paragraphs: indentation, line breaks and spans survive formatting untouched.
495
494
 
496
495
  ````markdown
497
496
  ```verse {lang=vyl font=semaron-kuriale enc=vylsemaronenc}
498
- Lokazor lonak.
499
- lokamen zaron.
497
+ Lokazor lonak./
498
+ .lokamen zaron.
500
499
  ```
501
500
  ````
502
501
 
503
- ### Verse in columns
502
+ ### Versions of a work
504
503
 
505
- `form=epic` records the poem's kind without inferring its layout or metre.
506
- `cols` is a positive whole number, defaulting to `1`. A single-column poem
507
- uses its physical lines without numeric prefixes. With `cols` above one,
508
- every content line starts with a column number and one or more spaces.
509
- Blank lines separate logical verse rows, and `***` separates stanzas.
504
+ `form=epic` records the author's literary form without inferring layout or metre.
510
505
 
511
- Numbers are in `1..cols` and nondecreasing within a row. Skip a number to
512
- leave that cell empty. Repeat a number for as many readings as the column
513
- needs: its first reading is foreground, and all its later readings are grey.
514
- That choice is independent for each column and resets at each new row.
515
-
516
- Here is a sparse three-column poem. The first row omits column two. The next
517
- fills it, and the last row begins a new stanza:
506
+ A `verse` fence with a concrete `enc=` sets one original in its script on the
507
+ left and Roman on the right. The author writes the text once; the two
508
+ presentations align logical lines at full weight. `/` ends a logical line,
509
+ source newlines otherwise join, a blank line or `//` ends a stanza, and a line
510
+ of only `***` ends a canto. Leading periods at a logical line boundary indent
511
+ that line. Use `\/` for a literal slash.
518
512
 
519
513
  ````markdown
520
- ```verse {numstep=1 lang=vyl cols=3}
521
- 1 [Lokazor lonak zaron.]{lang=vyl enc=vylsemaronenc}
522
- 3 [He walks the oath road.]{lang=en}
523
- 3 [Lokazor lonak zaron.]{lang=vyl enc=default}
514
+ ```verse {work=walking lang=en}
515
+ He walks the oath road.
524
516
 
525
- 1 [Lokamen lonak zaron.]{lang=vyl enc=vylsemaronenc}
526
- 2 [Nothing to say]{lang=en}
527
- 3 [I walk the oath road.]{lang=en}
528
- 3 [Lokamen lonak zaron.]{lang=vyl enc=default}
517
+ I walk the oath road.
518
+ ```
529
519
 
530
- ***
520
+ ```verse {work=walking lang=vyl enc=vylsemaronenc}
521
+ Lokazor lonak zaron.
531
522
 
532
- 1 [alathos lokazor.]{lang=vyl enc=vylsemaronenc}
533
- 3 [That one walks.]{lang=en}
534
- 3 [alathos lokazor.]{lang=vyl enc=default}
523
+ Lokamen lonak zaron.
535
524
  ```
536
525
  ````
537
526
 
538
- Each native span opts in explicitly and uses the Vylari note's configured
539
- map and font. Each Roman repeat is authored separately and stays Roman.
540
- `lang=` identifies the language without choosing a column; number prefixes
541
- alone place the readings. Inline links, language changes and authored spaces
542
- remain inside the chosen reading. Leading periods indent that reading.
543
-
544
- `numstep=N` counts logical rows, so repeated readings and empty cells do not
545
- advance its counter. Every target aligns each row's cells at the top, and
546
- narrow web or Foundry displays stack the cells in numbered order. A
547
- right-to-left reading stays in its cell and follows its own script's direction.
548
- Invalid counts and prefixes are located at the authored value or number;
549
- missing prefixes are located at the line's first content character.
527
+ `work=` groups the versions within the page. Their stanza labels match, and the
528
+ build checks that their canto counts and stanza counts per canto agree. The
529
+ English fence is one column, and `enc=default` also stays one Roman column.
530
+ The original's encoded and Roman presentations share one label per stanza.
531
+ Names, `⟦…⟧` codage, language gaps and other inline spans retain their meaning;
532
+ codage draws its glyphs on the left and appears as codage on the right. The
533
+ content index records the original once with its fence language and encoding.
534
+ See [versions and stanza labels](../authoring/links-and-markup.md#versions-and-stanza-labels)
535
+ for poetry and prose examples, and [directions and speakers](../authoring/links-and-markup.md#directions-and-speakers)
536
+ for plain semantic span hooks.
550
537
 
551
538
  ### Metre
552
539
 
@@ -567,7 +554,7 @@ whose language note states `data.sounds.stress` is counted line by line:
567
554
 
568
555
  ````markdown
569
556
  ```verse {lang=vyl scan=trimeter}
570
- Lokazor lonak zaron.
557
+ Lokazor lonak zaron./
571
558
  Lokamen lonak zaron.
572
559
  ```
573
560
  ````
@@ -613,8 +600,6 @@ anything, each an error:
613
600
 
614
601
  > ``the language `vyl` is not a configured language with a `note`, so it has no stress rule for `scan=` to count beats by``
615
602
 
616
- > `` `scan=` counts the beats of one language's lines; a verse with cols>1 groups independent physical lines ``
617
-
618
603
  A repeated metre name is an error too:
619
604
 
620
605
  > `` `data.metres[1].name` is `trimeter`, which this note declares twice; a metre's name is unique ``
@@ -1158,9 +1143,8 @@ where the characters are the code points above: `Lokazor` begins with the
1158
1143
  capital `L` of the rows, `zaron` is the exception's long vowel, and each period
1159
1144
  writes the script's pause point.
1160
1145
 
1161
- A poem in columns and a poem with its metre checked sit on one note. Both parse
1162
- with no finding: the first sets native Vylari in column one and English over Roman
1163
- Vylari in column three, with a sparse middle cell, and the second's lines each carry three beats.
1146
+ A work's English and original versions sit on one note. Its original is written
1147
+ once and appears beside its Roman presentation; a separate fence checks metre.
1164
1148
 
1165
1149
  **`assets/content/Lore/Walking.md`**
1166
1150
 
@@ -1175,25 +1159,20 @@ The walking of the oath road is sung in two settings. The first sets the poem
1175
1159
  beside its translation, and the second keeps the metre that the court keeps, so
1176
1160
  that a line with the wrong count is reported to the author who wrote it.
1177
1161
 
1178
- ```verse {numstep=1 lang=vyl cols=3}
1179
- 1 [Lokazor lonak zaron.]{lang=vyl enc=vylsemaronenc}
1180
- 3 [He walks the oath road.]{lang=en}
1181
- 3 [Lokazor lonak zaron.]{lang=vyl enc=default}
1162
+ ```verse {work=walking lang=en}
1163
+ He walks the oath road.
1182
1164
 
1183
- 1 [Lokamen lonak zaron.]{lang=vyl enc=vylsemaronenc}
1184
- 2 [Nothing to say]{lang=en}
1185
- 3 [I walk the oath road.]{lang=en}
1186
- 3 [Lokamen lonak zaron.]{lang=vyl enc=default}
1165
+ I walk the oath road.
1166
+ ```
1187
1167
 
1188
- ***
1168
+ ```verse {work=walking lang=vyl enc=vylsemaronenc}
1169
+ Lokazor lonak zaron.
1189
1170
 
1190
- 1 [alathos lokazor.]{lang=vyl enc=vylsemaronenc}
1191
- 3 [That one walks.]{lang=en}
1192
- 3 [alathos lokazor.]{lang=vyl enc=default}
1171
+ Lokamen lonak zaron.
1193
1172
  ```
1194
1173
 
1195
1174
  ```verse {lang=vyl scan=trimeter}
1196
- Lokazor lonak zaron.
1175
+ Lokazor lonak zaron./
1197
1176
  Lokamen lonak zaron.
1198
1177
  ```
1199
1178
  ````
@@ -2297,7 +2297,6 @@ its count can be the metre's.
2297
2297
  | A `scan=` no `data.metres` entry names, at the value | error |
2298
2298
  | A `scan=` on a fence with no `lang=`, at the key | error |
2299
2299
  | A `scan=` on a fence whose language states no stress rule or names no language note, at `lang=` | error |
2300
- | A `scan=` on a verse in columns, at the key | error |
2301
2300
 
2302
2301
  > ``this line has 4 beats (5 with secondary beats); the metre `hexameter` has 6``
2303
2302
 
@@ -333,63 +333,36 @@ Caption classes are unrestricted. The built-in `border` class draws the block as
333
333
 
334
334
  ## Verse
335
335
 
336
- A verse fence reaches the page as a `.verse` block holding a paragraph per
337
- stanza and a `.verse-line` per line, its indent level as `i1` to `i8`. Its
338
- keys reach the block as written, `lang` as its language and the rest as
339
- `data-*` attributes. A numbered verse also carries `.verse-numbered`, and each
340
- numbered line starts with its `.verse-number`.
341
-
342
- A [verse in columns](../authoring/links-and-markup.md#verse-in-columns) also
343
- carries `.verse-columns`, and each logical row is one `.verse-row`. The
344
- block carries `data-cols` and `--verse-columns` with its positive column
345
- count. Every row contains that many `.verse-column` cells, including empty
346
- ones:
347
-
348
- ```html
349
- <div
350
- class="verse verse-columns verse-numbered"
351
- lang="art-x-vyl"
352
- data-numstep="5"
353
- data-cols="3"
354
- style="--verse-columns: 3">
355
- <p>
356
- <span class="verse-row"
357
- ><span class="verse-number"></span
358
- ><span class="verse-column"
359
- ><span class="verse-part"
360
- ><span class="script hl-semaron-kuriale" lang="art-x-vyl">…</span></span
361
- ></span
362
- ><span class="verse-column"></span
363
- ><span class="verse-column"
364
- ><span class="verse-part"><span lang="en">The hallowed boundary I sing,</span></span
365
- ><span class="verse-part verse-secondary"
366
- ><span lang="art-x-vyl">Thiruthriman sorimamen,</span></span
367
- ></span
368
- ></span
369
- >
370
- …
371
- </p>
372
- </div>
373
- ```
374
-
375
- A numbered verse in columns gives every logical row a `.verse-number`, empty
376
- on rows it does not number, so repeated readings do not advance the counter.
377
- The grid remains left to right while inline text follows its own script
378
- direction. Each row has its own grid and aligns all its cells at the top. Below a block width of `32rem` a row stacks
379
- its columns in order; it is a container query, so a narrow journal window
380
- stacks as well. The Foundry stylesheet draws the same.
381
-
382
- | class | drawn as |
383
- | ------------------ | ------------------------------------------------------------- |
384
- | `.verse` | the poem |
385
- | `.verse-line` | one line of a poem without columns |
386
- | `.verse-numbered` | a numbered poem, with a margin for its numbers |
387
- | `.verse-number` | a line's number, muted |
388
- | `.verse-columns` | a poem in columns, and the container its rows are measured by |
389
- | `.verse-row` | one logical verse row: its number and its cells |
390
- | `.verse-column` | one numbered cell, possibly empty, with its readings stacked |
391
- | `.verse-part` | one entry's text, in the body serif unless its face sets it |
392
- | `.verse-secondary` | every reading after a column's first in that row, grey |
336
+ A `verse` fence reaches the page as `.verse`, with one `.verse-stanza` per
337
+ stanza and `.verse-line` per logical line. Indentation uses `i1` through `i8`;
338
+ deeper levels carry their complete `--verse-indent`. `.verse-canto-break`
339
+ separates cantos. A work-labelled fence adds `.verse-labelled`, and each stanza
340
+ opens with a small `.verse-label` shared across its presentations.
341
+
342
+ A concrete fence encoding adds `.verse-paired`. Each `.verse-row` aligns one
343
+ `.verse-native` cell with one `.verse-roman` cell at full weight. The script is
344
+ on the left; its direction applies within its cell. A narrow container stacks
345
+ the native presentation before the Roman presentation. An unencoded fence or
346
+ `enc=default` is one Roman column. See [versions of a work](../authoring/links-and-markup.md#versions-and-stanza-labels).
347
+
348
+ | Class | Meaning |
349
+ | -------------------- | ---------------------------------------------- |
350
+ | `.verse` | A version of a work |
351
+ | `.verse-stanza` | One stanza or prose paragraph |
352
+ | `.verse-line` | One logical line |
353
+ | `.verse-labelled` | A version carrying automatic stanza labels |
354
+ | `.verse-label` | The stanza number or canto:stanza label |
355
+ | `.verse-paired` | Script and Roman presentations of one original |
356
+ | `.verse-row` | Matching logical lines in both presentations |
357
+ | `.verse-native` | Encoded text on the left |
358
+ | `.verse-roman` | Roman text on the right |
359
+ | `.verse-canto-break` | A canto boundary |
360
+ | `.direction` | An instruction, inside or outside verse |
361
+ | `.speaker` | A speaker label, inside or outside verse |
362
+
363
+ Direction and speaker classes have plain defaults. The book preserves the same
364
+ classes through `book-span`, with plain `book-direction` and `book-speaker`
365
+ hooks for their presentation.
393
366
 
394
367
  ## Emphasis in a script
395
368
 
@@ -256,11 +256,12 @@ function attributeText(block, target, classes) {
256
256
  * @param {object} [options]
257
257
  * @param {(markdown: string) => string} [options.renderInline] - How a title
258
258
  * is rendered; omitted, a title is plain inline Markdown.
259
+ * @param {Map<string, object>} [options.verses] - Shared encoded verse presentations.
259
260
  * @returns {{markdown: string, errors: Array<{line: number, column: number, message: string}>}}
260
261
  */
261
- export function renderBlocks(source, target, { renderInline } = {}) {
262
+ export function renderBlocks(source, target, { renderInline, verses } = {}) {
262
263
  const { errors } = scanBlocks(source);
263
- source = renderScroll(renderVerse(source).markdown).markdown;
264
+ source = renderScroll(renderVerse(source, { verses }).markdown).markdown;
264
265
  const renderedBlocks = scanBlocks(source).blocks;
265
266
  if (!renderedBlocks.length) return { markdown: String(source ?? ""), errors };
266
267
  const lines = String(source ?? "").split("\n");
@@ -79,7 +79,7 @@ import { imagePlugin } from "./content-images.mjs";
79
79
  import { renderRuns } from "./content-scripts.mjs";
80
80
  import { renderSpans, spanMarkdownPlugin } from "./content-spans.mjs";
81
81
  import { cartoucheFigures, renderSymbolSpans } from "./content-symbols.mjs";
82
- import { hasColumns, inlineVerse, scanVerse, verseGroups, verseStanzas } from "./content-verse.mjs";
82
+ import { inlineVerse, scanVerse } from "./content-verse.mjs";
83
83
  import { replaceOutsideCode } from "./code-fences.mjs";
84
84
  import { dictionaryEntryPlugin } from "./dictionary-layout.mjs";
85
85
  import { headingAttributesPlugin, headingIdsPlugin } from "./heading-attributes.mjs";
@@ -132,32 +132,30 @@ function verseInlinePlugin(md) {
132
132
  continue;
133
133
  token.meta ??= {};
134
134
  const source = `${token.markup}${token.info}\n${token.content}${token.markup}`;
135
- const poem = scanVerse(source).blocks[0];
136
- if (hasColumns(poem)) {
137
- token.meta.verseGroups = verseGroups(poem, source.split("\n")).map((group) => {
138
- if (group.stanzaBreak) return group;
139
- return {
135
+ const poem = scanVerse(source, { checkWorks: false }).blocks[0];
136
+ if (!poem) continue;
137
+ const paired = state.env.verses?.get(token.content.replace(/\n$/, ""));
138
+ const model = paired?.model ?? poem.model;
139
+ token.meta.verse = poem;
140
+ token.meta.versePaired = paired;
141
+ const parse = (text) => {
142
+ const children = [];
143
+ state.md.inline.parse(inlineVerse(text), state.md, state.env, children);
144
+ return children;
145
+ };
146
+ token.meta.verseGroups = model.groups.map((group) =>
147
+ group.cantoBreak ?
148
+ { ...group, children: group.text ? parse(group.text) : [] }
149
+ : {
140
150
  ...group,
141
- parts: group.parts.map((part) => {
142
- const children = [];
143
- state.md.inline.parse(
144
- inlineVerse(part.text),
145
- state.md,
146
- state.env,
147
- children,
148
- );
149
- return { ...part, children };
150
- }),
151
- };
152
- });
153
- continue;
154
- }
155
- token.meta.verseStanzas = verseStanzas(token.content).map((stanza) =>
156
- stanza.map(({ text, level }) => {
157
- const children = [];
158
- state.md.inline.parse(inlineVerse(text), state.md, state.env, children);
159
- return { children, level, stanzaBreak: /^\*{3}$/.test(text) };
160
- }),
151
+ lines: group.lines.map((line) => ({
152
+ ...line,
153
+ children: parse(line.text),
154
+ ...(paired ?
155
+ { romanChildren: parse(line.romanText ?? line.text) }
156
+ : {}),
157
+ })),
158
+ },
161
159
  );
162
160
  }
163
161
  });
@@ -433,8 +431,12 @@ export function createRenderer(target, options = {}) {
433
431
  const prepare = (markdown, env = {}) => {
434
432
  let text = String(markdown ?? "");
435
433
  if (preview) text = sourceMalformedAlerts(text);
436
- if (env.runs)
437
- text = renderRuns(text, env.runs, undefined, { target: RUN_TARGET[target] }).markdown;
434
+ let verses = env.verses;
435
+ if (env.runs) {
436
+ const marked = renderRuns(text, env.runs, undefined, { target: RUN_TARGET[target] });
437
+ text = marked.markdown;
438
+ verses = marked.verses;
439
+ }
438
440
  // A glyph and a cartouche are pictures on every target that draws
439
441
  // HTML, so they are written before any stage renders a fragment. The
440
442
  // buffer-only preview shows a glyph as the source it was written in
@@ -448,7 +450,7 @@ export function createRenderer(target, options = {}) {
448
450
  { resolveRole: env.resolveRole, renderInline },
449
451
  );
450
452
  text = renderAlerts(
451
- renderBlocks(figured.markdown, target, { renderInline }).markdown,
453
+ renderBlocks(figured.markdown, target, { renderInline, verses }).markdown,
452
454
  target,
453
455
  {
454
456
  renderInline,