@heroiclands/package-build 24.0.1 → 24.0.3

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 (65) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/assets/preview/gitea.ini +1 -1
  3. package/assets/styles/content-base.css +33 -0
  4. package/bin/content-commands.mjs +17 -17
  5. package/content-config.mjs +1 -52
  6. package/docs/api.md +20 -13
  7. package/docs/authoring/links-and-markup.md +165 -17
  8. package/docs/commands.md +20 -20
  9. package/docs/configuration.md +29 -69
  10. package/docs/content-format.md +4 -2
  11. package/docs/guides/site.md +3 -1
  12. package/docs/guides/writing-in-invented-scripts.md +68 -6
  13. package/docs/reference/format-details.md +160 -118
  14. package/docs/reference/note-types.md +49 -29
  15. package/docs/source-preview.md +2 -2
  16. package/engine/content-blocks.mjs +6 -2
  17. package/engine/content-charset.mjs +1 -0
  18. package/engine/content-format.yaml +2 -0
  19. package/engine/content-index.mjs +5 -1
  20. package/engine/content-render.mjs +2 -1
  21. package/engine/content-scripts.mjs +268 -21
  22. package/engine/content-scroll.mjs +389 -0
  23. package/engine/dictionary-layout.mjs +16 -3
  24. package/engine/font-outlines.mjs +394 -0
  25. package/engine/foreign-catalog.mjs +11 -122
  26. package/engine/language-data.mjs +154 -63
  27. package/engine/language-rows.mjs +35 -9
  28. package/engine/language-terms.mjs +39 -9
  29. package/engine/map-lexicon.mjs +31 -6
  30. package/engine/note-vocabulary.mjs +58 -6
  31. package/engine/pdf-build.mjs +9 -178
  32. package/engine/pdf-fonts.mjs +1 -1
  33. package/engine/pdf-render.mjs +52 -11
  34. package/engine/run-rows.mjs +45 -3
  35. package/engine/script-direction.mjs +67 -0
  36. package/engine/script-emphasis-terms.mjs +11 -3
  37. package/engine/script-font-delivery.mjs +42 -9
  38. package/engine/script-fonts.mjs +7 -0
  39. package/engine/site-reach.mjs +23 -0
  40. package/engine/site-render.mjs +18 -3
  41. package/engine/verse-metre.mjs +1 -0
  42. package/engine/written-in.mjs +208 -0
  43. package/package.json +2 -1
  44. package/site-theme/static/css/style.css +7 -0
  45. package/types/content-config.d.mts +1 -19
  46. package/types/engine/content-scripts.d.mts +31 -6
  47. package/types/engine/content-scroll.d.mts +179 -0
  48. package/types/engine/dictionary-layout.d.mts +5 -0
  49. package/types/engine/font-outlines.d.mts +67 -0
  50. package/types/engine/foreign-catalog.d.mts +0 -38
  51. package/types/engine/language-data.d.mts +13 -0
  52. package/types/engine/language-rows.d.mts +5 -4
  53. package/types/engine/language-terms.d.mts +7 -0
  54. package/types/engine/map-lexicon.d.mts +4 -3
  55. package/types/engine/pdf-build.d.mts +5 -27
  56. package/types/engine/pdf-fonts.d.mts +10 -0
  57. package/types/engine/run-rows.d.mts +28 -6
  58. package/types/engine/script-direction.d.mts +10 -0
  59. package/types/engine/script-emphasis-terms.d.mts +10 -3
  60. package/types/engine/script-font-delivery.d.mts +5 -3
  61. package/types/engine/script-fonts.d.mts +9 -0
  62. package/types/engine/site-reach.d.mts +11 -0
  63. package/types/engine/written-in.d.mts +76 -0
  64. package/engine/asset-replacement.mjs +0 -93
  65. package/types/engine/asset-replacement.d.mts +0 -22
package/docs/commands.md CHANGED
@@ -1718,13 +1718,11 @@ package-build deps fetch [--from <zip|dir>] [--id <id>]
1718
1718
  The only action is `fetch`, which fills three caches under `build/cache/`:
1719
1719
  the **content index** of every declared dependency and of every
1720
1720
  `recommends` or `conflicts` relationship that declares `itemCatalog: true`,
1721
- the **item catalogue or
1722
- asset archive** of those that additionally declare `itemCatalog: true` or
1723
- `assetArchive: true`, and the **site navigation** —
1721
+ the **item catalogue** of those that additionally declare `itemCatalog: true`,
1722
+ and the **site navigation** —
1724
1723
  `https://www.heroiclands.org/nav.json`, the header menu every package site
1725
1724
  renders, fetched for every package because every package publishes a site.
1726
- A relationship declaring both `itemCatalog: true` and `assetArchive: true`
1727
- is fetched once. Its own command rather than a step of `package compile` or
1725
+ Its own command rather than a step of `package compile` or
1728
1726
  `site`, so neither reaches the network — a build that downloads silently is
1729
1727
  not reproducible and hides a dependency's version change behind a passing
1730
1728
  run. Each cache is stamped complete only once its fetch finishes, so a
@@ -1766,7 +1764,7 @@ including when the repository declares no dependencies at all.
1766
1764
  ```
1767
1765
  $ package-build deps fetch
1768
1766
  […] Fetched the site navigation to build/cache/navigation/nav.json.
1769
- […] No relationship declares `itemCatalog: true` or `assetArchive: true`; nothing to fetch.
1767
+ […] No relationship declares `itemCatalog: true`; nothing to fetch.
1770
1768
  […] This package declares no dependencies.
1771
1769
 
1772
1770
  $ package-build deps fetch --from build/dist/module.zip
@@ -3101,11 +3099,11 @@ from standard input, less one trailing newline. Writes nothing.
3101
3099
  | `map` | string | — | The `data.map.name` of a mapping note. |
3102
3100
  | `text` | string, variadic | stdin | The passage; `-` reads it from stdin. |
3103
3101
 
3104
- | Option | Type | Default | Description |
3105
- | -------- | ------- | -------------------------- | ------------------------------------------------------------------------------- |
3106
- | `--lang` | string | — | A language declared in `languages`, whose lexicon is read first. |
3107
- | `--font` | string | the map's one face, if one | The face, whose own rows come first; required when the map lists more than one. |
3108
- | `--hex` | boolean | `false` | Print the code points, as `U+XXXX` separated by spaces. |
3102
+ | Option | Type | Default | Description |
3103
+ | -------- | ------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
3104
+ | `--lang` | string | — | A language declared in `languages`, whose lexicon is read first. |
3105
+ | `--font` | string | the language's face where the map is drawn in it, else the map's one face, if one | The face, whose own rows come first; required when neither gives one and the map lists more than one. |
3106
+ | `--hex` | boolean | `false` | Print the code points, as `U+XXXX` separated by spaces. |
3109
3107
 
3110
3108
  **EXIT STATUS**
3111
3109
 
@@ -3158,14 +3156,15 @@ List a language's words with their native forms under a map.
3158
3156
  **SYNOPSIS**
3159
3157
 
3160
3158
  ```
3161
- package-build lexicon <language> --map <map> [--font <slug>] [--derived] [--json]
3159
+ package-build lexicon <language> [--map <map>] [--font <slug>] [--derived] [--json]
3162
3160
  ```
3163
3161
 
3164
3162
  **DESCRIPTION**
3165
3163
 
3166
3164
  Reads the content index and lists every word of the language note
3167
3165
  [`languages.<language>.note`](configuration.md#languages) names, in `data.words`
3168
- order, each with its native form under `<map>`: the stated exception where the
3166
+ order, each with its native form under `<map>`, or under the script the
3167
+ language note names in `data.writtenIn` when `--map` is left out: the stated exception where the
3169
3168
  word has one, else the rows' spelling, each exactly as
3170
3169
  `package-build transliterate` and the build give it. Each line holds, separated
3171
3170
  by tabs, the word's `form`, its native form, `exception` or `rows`, its `class`
@@ -3196,17 +3195,18 @@ the lexicon's own words. A derived line carries its word's `class` and `gloss`.
3196
3195
  | ---------- | ------ | ------- | --------------------------------------------------- |
3197
3196
  | `language` | string | — | A declared language whose `note` names its lexicon. |
3198
3197
 
3199
- | Option | Type | Default | Description |
3200
- | ----------- | ------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
3201
- | `--map` | string | — | Required: the `data.map.name` of a mapping note. |
3202
- | `--font` | string | the map's one face, if one | The face; required when the map lists more than one. |
3203
- | `--derived` | boolean | `false` | Also list the forms the language's rules derive, each with the word it comes from. |
3204
- | `--json` | boolean | `false` | Print a JSON array of `{ form, native, exception, class, gloss }` instead; a derived form adds `from`. Under a logographic map `source` (`own`, `parts` or `none`) takes the place of `exception`, and an ambiguous word adds `ambiguous: true`. |
3198
+ | Option | Type | Default | Description |
3199
+ | ----------- | ------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
3200
+ | `--map` | string | the language's `data.writtenIn` map | The `data.map.name` of a mapping note; required when the language note states no `data.writtenIn`. |
3201
+ | `--font` | string | the language's face where the map is drawn in it, else the map's one face, if one | The face; required when neither gives one and the map lists more than one. |
3202
+ | `--derived` | boolean | `false` | Also list the forms the language's rules derive, each with the word it comes from. |
3203
+ | `--json` | boolean | `false` | Print a JSON array of `{ form, native, exception, class, gloss }` instead; a derived form adds `from`. Under a logographic map `source` (`own`, `parts` or `none`) takes the place of `exception`, and an ambiguous word adds `ambiguous: true`. |
3205
3204
 
3206
3205
  **EXIT STATUS**
3207
3206
 
3208
3207
  1 if the content index could not be built for any note, or the language, the
3209
- map or the face names nothing, the language states no `note`, or a capitalised
3208
+ map or the face names nothing, no map is named and the language note states no
3209
+ `data.writtenIn`, the language states no `note`, or a capitalised
3210
3210
  word has a native form the rows cannot capitalise, each reported as a finding.
3211
3211
  Otherwise 0.
3212
3212
 
@@ -606,7 +606,7 @@ HeroicLands layout, resolved against `rootDir`:
606
606
  | `paths.packJson` | `build/packs-json` | Build-only per-entry JSON intermediate. |
607
607
  | `paths.stage` | `build/stage/packs` | Compiled LevelDB packs. |
608
608
  | `paths.unpack` | `build/tmp/packs` | Where `unpack` extracts JSON back to. |
609
- | `paths.foreignCache` | `build/cache/foreign` | Where a dependency declaring `itemCatalog: true` or `assetArchive: true` is unpacked. |
609
+ | `paths.foreignCache` | `build/cache/foreign` | Where a dependency declaring `itemCatalog: true` is unpacked. |
610
610
  | `paths.metadataCache` | `build/cache/metadata` | Where a published content index is fetched to, for every declared dependency and every relationship declaring `itemCatalog: true`. |
611
611
  | `paths.navigationCache` | `build/cache/navigation` | Where the site navigation heroiclands.org publishes is fetched to, for the site's menu. |
612
612
 
@@ -767,11 +767,27 @@ spells every other word by the map's rows; see
767
767
  Every word of such a run is a word of the lexicon, of a name the language note's
768
768
  own register lists, or a regular form of a lexicon word, and any other word is an error; see
769
769
  [a mapped run's words](authoring/links-and-markup.md#a-mapped-runs-words).
770
+ The language note may also name the script the language is written in, as
771
+ `data.writtenIn: { map, font }`, and a run marked with the language alone is
772
+ then mapped and set in it; see
773
+ [a language's script](authoring/links-and-markup.md#a-languages-script). The
774
+ script belongs to the language note, and the configuration never states it.
770
775
  A language with no `note` tags its text and has no lexicon. Whether the note
771
776
  resolves is checked by `package-build lint`, at the key:
772
777
 
773
778
  > ``package-build.config.yaml:5:5: error: `languages.vyl.note` is `skill-vylarilg`, which names no language note in thalorna; it names a skill note whose subType is language``
774
779
 
780
+ **The direction text is written in** belongs to the script, not to the
781
+ language, and the configuration never states it. Where the script note listing
782
+ a face states `direction: vertical-rl` in its `data.script`, text in that face
783
+ is written in columns, top to bottom, with the columns read from the right. A
784
+ passage of it goes in a
785
+ [`scroll` fence](authoring/links-and-markup.md#scrolls). On the site, in the
786
+ preview and in Foundry, the fence is set with `writing-mode: vertical-rl` in a
787
+ panel that scrolls sideways from the right. In the book, its panels are images
788
+ drawn from the face's TrueType file, which the installed fonts package lists
789
+ under `files.ttf`. A span in the same face stays horizontal.
790
+
775
791
  The shape is checked when the configuration loads, and each fault is a
776
792
  configuration error at its key:
777
793
 
@@ -1212,7 +1228,7 @@ writes its own `sitemap.xml` whether or not it states this key.
1212
1228
  ```yaml
1213
1229
  site:
1214
1230
  base: /
1215
- sitemaps: [sohl, hm3, thalorna, kethira, thalornaaltart, harnensemble, harnadventures]
1231
+ sitemaps: [sohl, hm3, thalorna, kethira, harnensemble, harnadventures]
1216
1232
  ```
1217
1233
 
1218
1234
  `site.mergeIndexes` names the package sites, by `contentPackage`, whose search
@@ -1259,7 +1275,7 @@ site:
1259
1275
  projects: doc-projects
1260
1276
  feed: blog
1261
1277
  tags: true
1262
- sitemaps: [sohl, hm3, thalorna, kethira, thalornaaltart, harnensemble, harnadventures]
1278
+ sitemaps: [sohl, hm3, thalorna, kethira, harnensemble, harnadventures]
1263
1279
  mergeIndexes: [sohl, thalorna, kethira, harnensemble]
1264
1280
  ```
1265
1281
 
@@ -1629,17 +1645,15 @@ system relationship it declares — see [`stats.systemVersion`](#statssystemvers
1629
1645
 
1630
1646
  Each entry, in any of the four lists:
1631
1647
 
1632
- | Key (under `relationships.<kind>[]`) | Type | Required | Default |
1633
- | ------------------------------------------ | ------------------------------- | -------- | ------- |
1634
- | `relationships.systems[].id` | string | yes | — |
1635
- | `relationships.systems[].contentPackage` | string | no | the id |
1636
- | `relationships.systems[].type` | string | no | none |
1637
- | `relationships.systems[].manifest` | string | no | none |
1638
- | `relationships.systems[].compatibility` | object, `{minimum?, verified?}` | no | none |
1639
- | `relationships.systems[].itemCatalog` | boolean | no | `false` |
1640
- | `relationships.systems[].assetArchive` | boolean | no | `false` |
1641
- | `relationships.systems[].contentIndex` | boolean | no | `true` |
1642
- | `relationships.systems[].assetReplacement` | boolean | no | `false` |
1648
+ | Key (under `relationships.<kind>[]`) | Type | Required | Default |
1649
+ | ---------------------------------------- | ------------------------------- | -------- | ------- |
1650
+ | `relationships.systems[].id` | string | yes | — |
1651
+ | `relationships.systems[].contentPackage` | string | no | the id |
1652
+ | `relationships.systems[].type` | string | no | none |
1653
+ | `relationships.systems[].manifest` | string | no | none |
1654
+ | `relationships.systems[].compatibility` | object, `{minimum?, verified?}` | no | none |
1655
+ | `relationships.systems[].itemCatalog` | boolean | no | `false` |
1656
+ | `relationships.systems[].contentIndex` | boolean | no | `true` |
1643
1657
 
1644
1658
  (the same keys apply under `requires[]`, `recommends[]` and
1645
1659
  `conflicts[]`.)
@@ -1648,7 +1662,7 @@ Each entry, in any of the four lists:
1648
1662
 
1649
1663
  > ``package-build config: `relationships.<kind>[<index>].id` must be a non-empty string.``
1650
1664
 
1651
- > ``package-build config: `relationships.<kind>[<index>].<key>` is not a recognized option (expected one of: id, contentPackage, type, manifest, compatibility, itemCatalog, assetArchive, contentIndex, assetReplacement).``
1665
+ > ``package-build config: `relationships.<kind>[<index>].<key>` is not a recognized option (expected one of: id, contentPackage, type, manifest, compatibility, itemCatalog, contentIndex).``
1652
1666
 
1653
1667
  `contentPackage` names what the other package's _content_ is called, where
1654
1668
  that differs from its Foundry id. A note addresses a file by the content
@@ -1669,31 +1683,6 @@ embedded-item addresses the compile does. It requires a `manifest`:
1669
1683
 
1670
1684
  > ``package-build config: `relationships.<kind>[<index>].itemCatalog` needs a `manifest` naming the package to fetch.``
1671
1685
 
1672
- `assetArchive` opts into unpacking the named package's release archive for
1673
- its asset bytes alone — the sibling of `itemCatalog`, for a dependency whose
1674
- bytes are wanted and whose items are not. It builds no item catalogue from
1675
- the archive, which is what distinguishes it from `itemCatalog: true`. It
1676
- requires a `manifest`:
1677
-
1678
- > ``package-build config: `relationships.<kind>[<index>].assetArchive` must be true or false.``
1679
-
1680
- > ``package-build config: `relationships.<kind>[<index>].assetArchive` needs a `manifest` naming the package to fetch.``
1681
-
1682
- A relationship with no items of its own to extract, such as an asset
1683
- replacement module, declares only `assetArchive: true`:
1684
-
1685
- ```yaml
1686
- relationships:
1687
- requires:
1688
- - id: thalornaaltart
1689
- manifest: https://…/releases/latest/download/module.json
1690
- compatibility: { verified: "1.0.0" }
1691
- assetArchive: true
1692
- ```
1693
-
1694
- A relationship declaring both `itemCatalog: true` and `assetArchive: true` is
1695
- fetched once: `deps fetch` does not download the archive twice for one entry.
1696
-
1697
1686
  `contentIndex` and `itemCatalog` are the two edges a relationship may declare,
1698
1687
  and a package may have either without the other. `itemCatalog` says a
1699
1688
  dependency supplies _items_; `contentIndex`, `true` by default, says
@@ -1713,35 +1702,6 @@ demand.
1713
1702
 
1714
1703
  > ``package-build config: `relationships.<kind>[<index>].contentIndex` cannot be false together with `itemCatalog: true` — a catalogue is fetched from the same index.``
1715
1704
 
1716
- `assetReplacement` opts a relationship into serving this package's own asset
1717
- addresses from the named dependency's tree instead of its local one —
1718
- `engine/asset-replacement.mjs`'s `resolveAssetReplacement` is what reads the
1719
- declaration. Off by default: declaring a dependency is not the same as
1720
- wanting its pictures in place of the ones this package ships itself. It
1721
- requires a fetched index to check the replacement against, so it cannot pair
1722
- with `contentIndex: false`:
1723
-
1724
- ```yaml
1725
- relationships:
1726
- requires:
1727
- - id: thalornaaltart
1728
- manifest: https://…/releases/latest/download/module.json
1729
- assetReplacement: true
1730
- contentIndex: true
1731
- ```
1732
-
1733
- With that declared, an address `thalorna` would otherwise answer itself —
1734
- `thalorna-none-image-thorn` — resolves against
1735
- `thalornaaltart-none-image-thorn` first, falling back to `thalorna`'s own
1736
- record when `thalornaaltart` carries no file at that address. A package
1737
- declaring more than one `assetReplacement: true` relationship is tried in
1738
- the order the relationships are declared, and the first one carrying the
1739
- address wins.
1740
-
1741
- > ``package-build config: `relationships.<kind>[<index>].assetReplacement` must be true or false.``
1742
-
1743
- > ``package-build config: `relationships.<kind>[<index>].assetReplacement` cannot be true together with `contentIndex: false` — a replacement with no fetched index to check against resolves nothing.``
1744
-
1745
1705
  ### `systems`
1746
1706
 
1747
1707
  **Type:** object (`{id: spec}`) · **Optional** · default `{}`. Refused in a
@@ -31,7 +31,9 @@ Leading `:` captions describe the next supported block; `:@` captions also numbe
31
31
 
32
32
  A code fence with language `verse` keeps verse lines, stanza breaks and relative indentation. Its numbered caption uses the **Poem** label. Generic `:::` divs and `[inline spans]{.class}` accept attributes. See [verse](authoring/links-and-markup.md#verse), [fenced divs](authoring/links-and-markup.md#fenced-divs) and [inline spans](authoring/links-and-markup.md#inline-spans). `numstep=N` numbers every Nth line. `col1=` and `col2=` set the poem in at most two columns, each a `:`-joined stack of `language[/mapping[/font]]` entries, the first in the normal tone and the rest in grey; with columns, each blank-line group of `[text]{lang=…}` spans is one verse line, every entry sets the span in its language, and a `***` line breaks the stanza. See [verse in columns](authoring/links-and-markup.md#verse-in-columns).
33
33
 
34
- A span, a generic div or a verse fence marks its text's language with `lang=`, the font it is set in with `font=` and a mapping with `map=`, each optional, and the runs inside a div or a fence inherit its keys. `lang=` is a name the package declares in `languages` or a BCP 47 tag, and gives the language tag alone. `font=` is a face of the installed `@heroiclands/fonts`, named by its class without `hl-`, or `mono`, `serif` or `sans`. `map=` names a mapping note: a `doc` note whose `data.map` lists the fonts it is drawn in and rows that rewrite the text before it is set, a font's own rows first. With `lang=` naming a language whose configured `note` names its language note, a word whose entry states a `native` form for the map is written by it, and the rows spell every other word. A map stating `logographic: true` spells no word by its rows: each word is written by its own `native` entry or its parts' writings, and a word with neither, or whose form stands for entries written differently, is a located error. Every word of such a run is a lexicon form, a word of a name the language note's own register lists under a tongue other than `translation`, or a form a plural row, the verb pattern or an affix stating `replaces` derives from a lexicon form, and any other word is an error at the word. A character a face does not draw, after the map, is an error at the character it came from. A verse fence that states `scan=` (the `name` of a `data.metres` entry of a `doc` note) with a `lang=` whose language note states `data.sounds.stress` is counted line by line by the beat convention, and a line that cannot have the metre's beats is a warning at the line; see [metres](reference/format-details.md#metres). Text with a font, a map or a declared language is plain, except that text set in a face takes `*…*` and `**…**`: they render by the convention its language note's `data.emphasis` states, else the one the script note listing its face states in `data.script` (`rubric` or `overline`), else plainly, and a language whose `data.emphasis` is `words` makes them an error. Every marked construct is a row of the `runs` relation, every mapping row of the `maps` relation, and every native form of the `natives` relation. See [marking language, font and mapping](authoring/links-and-markup.md#marking-language-font-and-mapping).
34
+ A code fence with language `scroll` sets text in a script whose script note states `direction: vertical-rl` as columns, top to bottom, read from the right. The lines of a paragraph are joined, and each paragraph starts a fresh column. `rows=N` is the characters in a column and `cols=M` the columns in a panel, each a whole number above zero; `lang=`, `font=` and `map=` mark the text as on a verse fence, and a fence naming no `font=` takes the face of a map drawn in one. The site, the preview and Foundry set it with `writing-mode: vertical-rl` in a panel that scrolls sideways from the right; the book draws its panels as images from the face's outlines, each with its roman text as alt text. A scroll fence in a script written in lines is an error. See [scrolls](authoring/links-and-markup.md#scrolls).
35
+
36
+ A span, a generic div, a verse fence or a scroll fence marks its text's language with `lang=`, the font it is set in with `font=` and a mapping with `map=`, each optional, and the runs inside a div or a fence inherit its keys. `lang=` is a name the package declares in `languages` or a BCP 47 tag, and gives the language tag; where the language note states `data.writtenIn`, the script the language is written in as a `map` and a `font` the map lists, a run marked with the language alone takes that map and face, and a run's own `map=` or `font=` overrides its half of the default. `font=` is a face of the installed `@heroiclands/fonts`, named by its class without `hl-`, or `mono`, `serif` or `sans`. `map=` names a mapping note: a `doc` note whose `data.map` lists the fonts it is drawn in and rows that rewrite the text before it is set, a font's own rows first. With `lang=` naming a language whose configured `note` names its language note, a word whose entry states a `native` form for the map is written by it, and the rows spell every other word. A map stating `logographic: true` spells no word by its rows: each word is written by its own `native` entry or its parts' writings, and a word with neither, or whose form stands for entries written differently, is a located error. Every word of such a run is a lexicon form, a word of a name the language note's own register lists under a tongue other than `translation`, or a form a plural row, the verb pattern or an affix stating `replaces` derives from a lexicon form, and any other word is an error at the word. A character a face does not draw, after the map, is an error at the character it came from. A verse fence that states `scan=` (the `name` of a `data.metres` entry of a `doc` note) with a `lang=` whose language note states `data.sounds.stress` is counted line by line by the beat convention, and a line that cannot have the metre's beats is a warning at the line; see [metres](reference/format-details.md#metres). Text with a font, a map or a declared language is plain, except that text set in a face takes `*…*` and `**…**`: they render by the convention its language note's `data.emphasis` states, else the one the script note listing its face states in `data.script` (`rubric` or `overline`), else plainly, and a language whose `data.emphasis` is `words` makes them an error. Every marked construct is a row of the `runs` relation, every mapping row of the `maps` relation, and every native form of the `natives` relation. See [marking language, font and mapping](authoring/links-and-markup.md#marking-language-font-and-mapping).
35
37
 
36
38
  Mark a name without a note as `[Name]{.name data-type="being"}`; the optional
37
39
  classification uses the declared note vocabulary. The [name-marking
@@ -175,6 +177,6 @@ Places, affiliations, lore and beings may name their culture in `data.culture`,
175
177
 
176
178
  An affiliation may name the trade, temple or order tongue it keeps in its own `data.language`. An affiliation that reaches no culture is a warning, in a tree holding a culture note. A language needs no culture and no affiliation. See [type: affiliation](reference/format-details.md#type-affiliation).
177
179
 
178
- A language note (`type: skill`, `subType: language`) states its words in frontmatter: `data.words` states each word as a map of its form, class, field and gloss — one sense or a list of them, which a dictionary numbers — with its source, its variant forms, the pieces it is built from and a note, beside `data.classModifiers`, `data.fields` and `data.affixes`, each affix with the `kind` it makes, for a given name's ending who the name is `given` to, and the stem closes it is barred after; a zero affix states `zero: true` and no form. The notes a word is cited from are derived, not written: a note cites a word by marking it in italics in its prose, matched by form or a variant with diacritics and case set aside. The language of a word is the note it sits on, and none of it reaches the compiled Item. A daughter language names its parent in `data.family` and reads the parent's words as its own; the `language_lineage` relation joins a language to its ancestors. Classes come from the [master word classes](reference/format-details.md#word-classes). A culture or an affiliation states its own glossary entries in `data.glossary`: common words in its core vocabulary, dictionary words with a context meaning, or phrases, compounds and terms of art outside the dictionary with their own gloss. An entry naming a word of its resolved language takes the word's senses and class and links to its language entry; a `definition` supplies the entry's meaning instead. Common marks belong to the glossary's owner. A language note with nonempty `data.words` receives its full Dictionary section automatically: one alphabetical list with field tags and one key. A culture or affiliation with nonempty `data.glossary` receives only its explicitly declared entries in its Glossary section; ranks and offices stand in the affiliation's own sections. A note without the relevant frontmatter receives no such section. All surfaces show these sections after the authored text, or under an existing `{#dictionary}`, `{#lexicon}` or `{#glossary}` heading after its introduction. There are no setting-wide dictionary or glossary pages. Every entry resolves to a language — its own `language`, else the affiliation's, its parents' or its culture's, else the culture's — or is an error at the entry. The `words`, `affixes`, `fields`, `glossary` and `language_lineage` relations hold them for `sql` fences, the `dictionary` relation lists each word under every language that has it, inherited words included, and the `terms` relation each glossary entry, rank and office with its culture associations; a fence sets custom views with `layout=dictionary` or `layout=glossary`. A language note names the tag a dictionary prints for it in `data.abbreviation`. A `doc` note states a setting's core concepts in `data.concepts`, a language note says it must cover them with `data.core: true`, and a word names the concepts it serves in `concept`; `package-build core` lists what each covering language has no word for (see [the core vocabulary](reference/format-details.md#the-core-vocabulary)). Every fact a check reads about a language's names, sounds and history is frontmatter too: `data.tongues` and `data.register` (every name the setting gives in the language's reach, the notes carrying each derived from the index, and a name tagged `other:<tongue>` or `exonym:<tongue>` one that the tongue's own language note registers under that tongue), `data.retired`, `data.scope`, `data.sounds` (letters with their place, word edges from a closed list, the cluster limit and the groups of clusters inside a word, the stress rule (`two`, `longer` and `mark`), syllable finals, syllable rules and vowel harmony with each vowel's front partner, the joining of parts, written marks, a root's skeleton vowel and frames, the spellings read past, and named mutations such as wearing and voicing), `data.soundChanges` (named sets of ordered steps), `data.elements` (each standing `opening`, `closing`, `either` or `link`, for one kind of name, with the word it is the stem of and its joining form), `data.roots`, `data.cognates` (with a reconstructed `proto` form), `data.callingNames`, `data.older` (the forms older than the rules), `data.negation` (the negative particle and the mutation it brings) and `data.naming` (how long each class of name runs, the edges names keep that words do not, the element kinds each kind of name draws on, what a place name takes from what it is held from, and the near name), each a relation — `sounds` as `letters`, `edges`, `clusters`, `cluster_groups`, `stress`, `finals`, `syllables`, `joining`, `joins`, `marks`, `skeleton`, `frames`, `read_past` and `mutations`, `soundChanges` as `sound_changes`, `callingNames` as `calling_names` — that a fence sets back on the page. A word's citations also come from a note whose name, alias or rank or office title spells it whole. See [a language's words](reference/format-details.md#a-languages-words), [a language's register, sounds and history](reference/format-details.md#a-languages-register-sounds-and-history), [glossary terms](reference/format-details.md#glossary-terms) and [dictionaries and glossaries](reference/format-details.md#dictionaries-and-glossaries).
180
+ A language note (`type: skill`, `subType: language`) states its words in frontmatter: `data.words` states each word as a map of its form, class, field and gloss — one sense or a list of them, which a dictionary numbers — with its source, its variant forms, the pieces it is built from and a note, beside `data.classModifiers`, `data.fields` and `data.affixes`, each affix with the `kind` it makes, for a given name's ending who the name is `given` to, and the stem closes it is barred after; a zero affix states `zero: true` and no form. The notes a word is cited from are derived, not written: a note cites a word by marking it in italics in its prose, matched by form or a variant with diacritics and case set aside. The language of a word is the note it sits on, and none of it reaches the compiled Item. A daughter language names its parent in `data.family` and reads the parent's words as its own; the `language_lineage` relation joins a language to its ancestors. Classes come from the [master word classes](reference/format-details.md#word-classes). A culture or an affiliation states its own glossary entries in `data.glossary`: common words in its core vocabulary, dictionary words with a context meaning, or phrases, compounds and terms of art outside the dictionary with their own gloss. An entry naming a word of its resolved language takes the word's senses and class and links to its language entry; a `definition` supplies the entry's meaning instead. Common marks belong to the glossary's owner. A language note with nonempty `data.words` receives its full Dictionary section automatically: one alphabetical list with field tags and one key. A culture or affiliation with nonempty `data.glossary` receives only its explicitly declared entries in its Glossary section; ranks and offices stand in the affiliation's own sections. A note without the relevant frontmatter receives no such section. All surfaces show these sections after the authored text, or under an existing `{#dictionary}`, `{#lexicon}` or `{#glossary}` heading after its introduction. There are no setting-wide dictionary or glossary pages. Every entry resolves to a language — its own `language`, else the affiliation's, its parents' or its culture's, else the culture's — or is an error at the entry. The `words`, `affixes`, `fields`, `glossary` and `language_lineage` relations hold them for `sql` fences, the `dictionary` relation lists each word under every language that has it, inherited words included, and the `terms` relation each glossary entry, rank and office with its culture associations; a fence sets custom views with `layout=dictionary` or `layout=glossary`. A language note names the tag a dictionary prints for it in `data.abbreviation`. A `doc` note states a setting's core concepts in `data.concepts`, a language note says it must cover them with `data.core: true`, and a word names the concepts it serves in `concept`; `package-build core` lists what each covering language has no word for (see [the core vocabulary](reference/format-details.md#the-core-vocabulary)). Every fact a check reads about a language's names, sounds and history is frontmatter too: `data.tongues` and `data.register` (every name the setting gives in the language's reach, the notes carrying each derived from the index, and a name tagged `other:<tongue>` or `exonym:<tongue>` one that the tongue's own language note registers under that tongue), `data.retired`, `data.scope`, `data.sounds` (letters with their place, their IPA and the tongues that write them, each a declared tag or a language note's Address, word edges from a closed list, the cluster limit and the groups of clusters inside a word, the stress rule (`two`, `longer`, which may be `first`, and `mark`), syllable finals with their IPA, syllable rules and vowel harmony with each vowel's front partner, the joining of parts, written marks, a root's skeleton vowel and frames, the spellings read past, and named mutations such as wearing and voicing), `data.soundChanges` (named sets of ordered steps), `data.elements` (each standing `opening`, `closing`, `either` or `link`, for one kind of name, with the word it is the stem of and its joining form), `data.roots`, `data.cognates` (with a reconstructed `proto` form), `data.callingNames`, `data.older` (the forms older than the rules), `data.negation` (the negative particle and the mutation it brings) and `data.naming` (how long each class of name runs, the edges names keep that words do not, the element kinds each kind of name draws on, what a place name takes from what it is held from, and the near name), each a relation — `sounds` as `letters`, `edges`, `clusters`, `cluster_groups`, `stress`, `finals`, `syllables`, `joining`, `joins`, `marks`, `skeleton`, `frames`, `read_past` and `mutations`, `soundChanges` as `sound_changes`, `callingNames` as `calling_names` — that a fence sets back on the page. A `doc` note may state a spelling guide's `data.sounds.letters` and `data.sounds.marks` alone, its letter rows naming tongues by language note Addresses, and they join the `letters` and `marks` relations under the guide's Address. A word's citations also come from a note whose name, alias or rank or office title spells it whole. See [a language's words](reference/format-details.md#a-languages-words), [a language's register, sounds and history](reference/format-details.md#a-languages-register-sounds-and-history), [glossary terms](reference/format-details.md#glossary-terms) and [dictionaries and glossaries](reference/format-details.md#dictionaries-and-glossaries).
179
181
 
180
182
  Blockquote alerts use `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, or `[!]`, with optional attributes after the marker. They replace the former info and warn div fences; secret blocks remain supported. See [alerts](authoring/links-and-markup.md#alerts).
@@ -247,7 +247,9 @@ generated after a note's text (Within, Governed by, Governed places,
247
247
  Chronology, Events, Accounts, What followed, In song and story). Each is drawn
248
248
  without a link to a page outside the reachable set, and a section left with
249
249
  nothing under it is not drawn. An infobox value that names an unreachable page
250
- is set as plain text. A link written in prose or produced by a query is kept
250
+ is set as plain text. So is a link in a dictionary or glossary entry, whether
251
+ it names a word's entry, cites a note or sits in a gloss; it does not make its
252
+ target reachable. A link written in prose or produced by a query is kept
251
253
  wherever it points, and it is what makes its target reachable.
252
254
 
253
255
  A page that is not reachable is the way to preview a page in place. Write the
@@ -142,6 +142,29 @@ address of the language's `skill` note of `subType: language`, whose
142
142
  Without `note`, `lang=` only tags: there is no dictionary and no lexicon check.
143
143
  `package-build lint` checks at the key that `note` names a language note.
144
144
 
145
+ **A language names its script.** Most tongues are written in one script, so the
146
+ language note says which, as one map and one face:
147
+
148
+ ```yaml
149
+ data:
150
+ writtenIn: { map: semaron, font: semaron-kuriale }
151
+ ```
152
+
153
+ A run marked with the language alone then takes both: `[Zaron lonak]{lang=vyl}`
154
+ is mapped through Semaron and checked against Vylari exactly as
155
+ `[Zaron lonak]{lang=vyl font=semaron-kuriale map=semaron}` is, and so is a div,
156
+ a verse fence or a `scroll` fence marked `lang=vyl`. A run that states one of
157
+ the two keys keeps it, and takes the other from the language only where the
158
+ two agree: `font=` alone takes the language's map where that map is drawn in
159
+ the face, and `map=` alone takes the language's face where the map is drawn in
160
+ it. So a tongue written in several scripts states its usual one, and a run in
161
+ another names that script's keys. A language whose note states no `writtenIn`
162
+ only tags, as above. The checks are at the key:
163
+
164
+ > `` `data.writtenIn.map` is `semoran`, which no mapping note's `data.map.name` declares; did you mean `semaron`? ``
165
+
166
+ > `` `data.writtenIn.font` is `teng-chou`, which the map `semaron` is not drawn in; it is drawn in `semaron-kuriale` ``
167
+
145
168
  **A run inherits from its passage and overrides it.** A span inside a marked
146
169
  div or fence takes the passage's keys, and its own keys win. A Khelathi phrase
147
170
  inside a Vylari poem is checked against Khelathi's lexicon and tagged Khelathi
@@ -805,8 +828,8 @@ Lokamen lonak zaron.
805
828
 
806
829
  **The stress rule** is `data.sounds.stress`, `{ two, longer, mark }`. `two`
807
830
  (`first` or `last`) places the stress in a word of two syllables; `longer`
808
- (`antepenultimate`, `penultimate` or `final`) places it in a word of three or
809
- more, counted from its end; `mark` (`acute`, `grave` or `circumflex`) is a
831
+ (`first`, `antepenultimate`, `penultimate` or `final`) places it in a word of
832
+ three or more, on its first syllable or counted from its end; `mark` (`acute`, `grave` or `circumflex`) is a
810
833
  diacritic that moves the stress to the syllable it sits on. Syllables are vowel
811
834
  groups: a vowel is a letter whose base is `a`, `e`, `i`, `o` or `u`, and a pair
812
835
  that the language lists in `data.sounds.syllables.vowelPairs` is one syllable.
@@ -850,6 +873,43 @@ A repeated metre name is an error too:
850
873
 
851
874
  > `` `data.metres[1].name` is `trimeter`, which this note declares twice; a metre's name is unique ``
852
875
 
876
+ ## Scripts written in columns
877
+
878
+ A script written in vertical columns, read from the right, states it on its
879
+ script note:
880
+
881
+ ```yaml
882
+ data:
883
+ script:
884
+ fonts: [teng-chou]
885
+ emphasis: rubric
886
+ direction: vertical-rl
887
+ ```
888
+
889
+ A passage in that script goes in a `scroll` fence. Its lines are joined into
890
+ paragraphs, and each paragraph starts at the top of a fresh column. `rows=`
891
+ sets the characters in a column and `cols=` the columns in a panel:
892
+
893
+ ````markdown
894
+ ```scroll {rows=25 cols=12 lang=tsu map=tengchou}
895
+ chen teng chou hing
896
+ tan tsou dou rou
897
+
898
+ gen chei chou tsuen
899
+ ```
900
+ ````
901
+
902
+ Where the Tantsou note states `writtenIn: { map: tengchou, font: teng-chou }`,
903
+ ` ```scroll {rows=25 cols=12 lang=tsu} ` is enough.
904
+
905
+ The site, the preview and Foundry set it in columns, in a panel that scrolls
906
+ sideways from the right. The book draws it as panel images from the face's
907
+ outlines, each with its roman text as alt text. A span in the same script stays
908
+ horizontal, inline in its line. A `scroll` fence in a script that states no
909
+ direction is an error. See
910
+ [scrolls](../authoring/links-and-markup.md#scrolls) for every attribute, the
911
+ defaults on each target and the errors.
912
+
853
913
  ## The core vocabulary
854
914
 
855
915
  A setting can name the concepts every one of its languages has a word for, and
@@ -907,8 +967,9 @@ whose notes set no text in a face, ships none of it.
907
967
  `package-build transliterate <map> [--lang <language>] [--font <slug>] [--hex] [text..]`
908
968
  maps a passage exactly as the build maps a run marked `map=<map> lang=<language>
909
969
  font=<slug>`. The text is the words given; `-` or none reads standard input.
910
- `--font` is required when the mapping lists more than one face, and `--hex`
911
- prints `U+XXXX` code points separated by spaces.
970
+ `--font` defaults to the language's `writtenIn` face where the mapping is drawn
971
+ in it, and is otherwise required when the mapping lists more than one face.
972
+ `--hex` prints `U+XXXX` code points separated by spaces.
912
973
 
913
974
  ```console
914
975
  $ package-build transliterate semaron --lang vyl --hex zaron lonak
@@ -920,8 +981,9 @@ U+F0246 U+F0230 U+F023F U+F023D U+F023C
920
981
  The first run writes `zaron` by its exception, and the second, with no
921
982
  `--lang`, spells it by the rows.
922
983
 
923
- `package-build lexicon <language> --map <map> [--font <slug>] [--derived] [--json]`
924
- lists every word of the language with its native form, tab-separated: the form,
984
+ `package-build lexicon <language> [--map <map>] [--font <slug>] [--derived] [--json]`
985
+ lists every word of the language with its native form under the map, or under
986
+ the language's `writtenIn` script when `--map` is left out, tab-separated: the form,
925
987
  the native form, `exception` or `rows`, the class and the gloss. `--derived`
926
988
  adds the forms the language's rules derive, each with the word it comes from,
927
989
  `--json` prints an array instead, and under a logographic map the third column