@heroiclands/package-build 25.0.0 → 25.1.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 (105) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -0
  3. package/bin/content-commands.mjs +30 -96
  4. package/bin/encoding-commands.mjs +146 -0
  5. package/bin/package-build.mjs +35 -24
  6. package/config.mjs +25 -0
  7. package/convert.mjs +182 -0
  8. package/docs/api.md +137 -6
  9. package/docs/authoring/links-and-markup.md +260 -481
  10. package/docs/commands.md +110 -119
  11. package/docs/configuration.md +31 -13
  12. package/docs/content-format.md +11 -4
  13. package/docs/diagnostics.md +43 -16
  14. package/docs/encoding-core.md +135 -0
  15. package/docs/guides/book.md +2 -2
  16. package/docs/guides/module-project.md +23 -1
  17. package/docs/guides/site.md +2 -2
  18. package/docs/guides/writing-in-invented-scripts.md +450 -574
  19. package/docs/reference/format-details.md +289 -404
  20. package/docs/reference/note-types.md +80 -63
  21. package/docs/source-preview.md +8 -0
  22. package/e2e.mjs +1 -1
  23. package/engine/base-compiler.mjs +27 -9
  24. package/engine/content-charset.mjs +13 -1
  25. package/engine/content-format.yaml +6 -2
  26. package/engine/content-index.mjs +6 -2
  27. package/engine/content-links.mjs +1 -0
  28. package/engine/content-scripts.mjs +614 -459
  29. package/engine/content-scroll.mjs +4 -4
  30. package/engine/content-spans.mjs +40 -9
  31. package/engine/content-verse.mjs +1 -1
  32. package/engine/converter-data.mjs +70 -0
  33. package/engine/dictionary-rows.mjs +2 -34
  34. package/engine/encoding-integrity.mjs +94 -0
  35. package/engine/encoding-language.mjs +506 -0
  36. package/engine/encoding-lexicon.mjs +699 -0
  37. package/engine/encoding-notes.mjs +186 -0
  38. package/engine/encoding-standard.mjs +42 -0
  39. package/engine/encoding-template.mjs +313 -0
  40. package/engine/encoding-terms.mjs +141 -0
  41. package/engine/encoding.mjs +946 -0
  42. package/engine/extension-attributes.mjs +2 -0
  43. package/engine/helpers.mjs +1 -1
  44. package/engine/index.mjs +7 -0
  45. package/engine/language-terms.mjs +17 -16
  46. package/engine/{logographic.mjs → language-writings.mjs} +29 -38
  47. package/engine/markup-validation.mjs +1 -1
  48. package/engine/note-vocabulary.mjs +4 -158
  49. package/engine/pdf-render.mjs +31 -3
  50. package/engine/roman-characters.mjs +135 -16
  51. package/engine/roman-run.mjs +149 -0
  52. package/engine/roman-verse.mjs +95 -0
  53. package/engine/run-rows.mjs +29 -31
  54. package/engine/run-words.mjs +2 -2
  55. package/engine/script-emphasis-terms.mjs +3 -0
  56. package/engine/script-emphasis.mjs +3 -10
  57. package/engine/script-font-delivery.mjs +9 -12
  58. package/engine/script-fonts.mjs +2 -1
  59. package/engine/site-build.mjs +14 -5
  60. package/engine/site-preview.mjs +12 -2
  61. package/engine/sql-tables.mjs +36 -31
  62. package/engine/verse-metre.mjs +1 -1
  63. package/engine/word-forms.mjs +1 -1
  64. package/engine/word-inflections.mjs +48 -0
  65. package/engine/word-rows.mjs +1 -1
  66. package/engine/word-terms.mjs +16 -17
  67. package/package.json +11 -5
  68. package/types/config.d.mts +5 -0
  69. package/types/convert.d.mts +44 -0
  70. package/types/e2e.d.mts +1 -1
  71. package/types/engine/base-compiler.d.mts +4 -2
  72. package/types/engine/content-index.d.mts +4 -1
  73. package/types/engine/content-scripts.d.mts +103 -42
  74. package/types/engine/converter-data.d.mts +11 -0
  75. package/types/engine/dictionary-rows.d.mts +1 -25
  76. package/types/engine/encoding-integrity.d.mts +14 -0
  77. package/types/engine/encoding-language.d.mts +36 -0
  78. package/types/engine/encoding-lexicon.d.mts +259 -0
  79. package/types/engine/encoding-notes.d.mts +39 -0
  80. package/types/engine/encoding-standard.d.mts +11 -0
  81. package/types/engine/encoding-template.d.mts +43 -0
  82. package/types/engine/encoding-terms.d.mts +105 -0
  83. package/types/engine/encoding.d.mts +155 -0
  84. package/types/engine/helpers.d.mts +1 -1
  85. package/types/engine/index.d.mts +4 -0
  86. package/types/engine/language-terms.d.mts +1 -1
  87. package/types/engine/{logographic.d.mts → language-writings.d.mts} +6 -6
  88. package/types/engine/markup-validation.d.mts +1 -1
  89. package/types/engine/pdf-render.d.mts +1 -1
  90. package/types/engine/roman-characters.d.mts +2 -2
  91. package/types/engine/roman-run.d.mts +101 -0
  92. package/types/engine/roman-verse.d.mts +23 -0
  93. package/types/engine/run-rows.d.mts +13 -20
  94. package/types/engine/script-emphasis-terms.d.mts +2 -0
  95. package/types/engine/script-emphasis.d.mts +1 -1
  96. package/types/engine/script-font-delivery.d.mts +2 -3
  97. package/types/engine/script-fonts.d.mts +5 -0
  98. package/types/engine/site-preview.d.mts +1 -1
  99. package/types/engine/word-inflections.d.mts +25 -0
  100. package/engine/map-lexicon.mjs +0 -1045
  101. package/engine/text-maps.mjs +0 -654
  102. package/engine/written-in.mjs +0 -204
  103. package/types/engine/map-lexicon.d.mts +0 -330
  104. package/types/engine/text-maps.d.mts +0 -189
  105. package/types/engine/written-in.d.mts +0 -76
@@ -468,8 +468,8 @@ These fields are accepted by every note type. A field's value can still be irrel
468
468
  | `data.fields` | list of `{ name, holds }` | The fields the language's words are grouped under. A language skill note only. |
469
469
  | `data.words` | list of `{ form, class, field, gloss, … }` | The words this language states, one map each, in authored order; its automatic dictionary sorts them alphabetically. A language skill note only. See [a language's words](format-details.md#a-languages-words). |
470
470
  | `data.affixes` | list of `{ form, gloss, attaches?, examples? }` | The affixes a word's `built` pieces may name, each with its gloss. A language skill note only. |
471
- | `data.roman` | a literal NFC string of permitted characters, including case, accents, digits and punctuation | The exact Roman source characters this language permits, each once. No ranges, case expansion or inherited inventory; spaces and line breaks are structural. An empty string declares no written characters. Required on each configured language note. |
472
- | `data.grammar` | a map — `{ plural?, verbs?, agreement?, participles?, native? }` | Optional noun plurals, one verb pattern, agreement, participles and the endings' logographic writings, for dictionary presentation, the mapped-run word check and logographic maps. Only the shape of supplied keys is checked; no word must follow or state a rule. A language skill note only. |
471
+ | `data.roman` | a literal NFC string of additional source characters, including exact letter case and digits | The additional Roman source characters the language permits, each once. Standard punctuation and structural whitespace remain available. No ranges, case expansion or inherited inventory. An empty string declares no additions. Required on each configured language note. |
472
+ | `data.grammar` | a map — `{ plural?, verbs?, agreement?, participles?, native? }` | Optional noun plurals, one verb pattern, agreement, participles and the endings' native writings, for dictionary presentation, language word checks and encodings. Only the shape of supplied keys is checked; no word must follow or state a rule. A language skill note only. |
473
473
  | `data.tongues` | list of `{ tag, meaning, language? }` | The tags a register row's `tongue` may name, each with what it means. A language skill note only. |
474
474
  | `data.register` | list of `{ name, tongue?, kind?, built?, gloss?, why? }` | Every name the setting gives in the language's reach, with its tongue and pieces; the notes carrying each name are derived. A language skill note only. |
475
475
  | `data.retired` | list of `{ form, instead?, why }` | The forms the setting does not write, and why. A language skill note only. |
@@ -484,8 +484,7 @@ These fields are accepted by every note type. A field's value can still be irrel
484
484
  | `data.negation` | a map — `{ particle, mutation? }` | How a verb is negated. A language skill note only. |
485
485
  | `data.naming` | a map — `{ shapes?, edges?, draws?, heldFrom?, nearName? }` | The shapes, edges and sources the language's names keep beyond its words' rules. A language skill note only. |
486
486
  | `data.emphasis` | `words`, `rubric` or `overline` | How the language marks a stressed word, over its script's `emphasis`: `rubric` or `overline` sets `*…*` and `**…**` so in a run of the language, and `words` marks it with a word, so either is a finding. Only a skill of `subType: language` states it. |
487
- | `data.writtenIn` | a map — `{ map, font }` | The script the language is written in, as the default map and face selected by a run with explicit `lang=` and `.nativefont`; a run's own `map=` or `font=` overrides its half. Only a skill of `subType: language` states it. |
488
- | `data.script` | a map — `{ fonts, emphasis, strongEmphasis?, direction? }` | A script note's faces, how it marks emphasis and the direction it is written in. Only a skill of `subType: script` states it. |
487
+ | `data.script` | a map — `{ fonts, emphasis, direction? }` | A script note's faces, how it marks emphasis and the direction it is written in. Only a skill of `subType: script` states it. |
489
488
 
490
489
  **Inner keys** — a key a field's value does not declare is an error at its own line and column.
491
490
 
@@ -513,10 +512,10 @@ These fields are accepted by every note type. A field's value can still be irrel
513
512
  | `data.words[].tones` | a string | no | A tone pattern, such as `LHL`. |
514
513
  | `data.words[].root` | a string | no | A consonantal skeleton, such as `d-l-k`. |
515
514
  | `data.words[].frame` | a frame of the language's `sounds.frames` | no | The frame a root is set in, named in the language's `sounds.frames`. |
516
- | `data.words[].native` | a list of `{ map, font?, text }` | no | The word's native form under a map. Under a map that spells by its rows it is stated only where the rows spell the word differently, and the word's derived forms are spelled by the rows; under a logographic map it is the word's writing, which its derived forms and the words built on it are written from. |
517
- | `data.words[].native[].map` | a mapping note's `data.map.name` | yes | The map the native form is written for. |
518
- | `data.words[].native[].font` | a face the map lists | no | The one face the native form is for; unstated, it is for every face the map lists, and a form stated for a face wins over it there. |
519
- | `data.words[].native[].text` | the word in the script, as the map's characters | yes | The word as the script writes it, where the map's rows would write it otherwise, or as a logographic map writes it. |
515
+ | `data.words[].native` | a list of `{ enc, font?, text }` | no | The word's own writing for an encoding and optional face. It precedes complete parts and the encoding's rows; an entry for a face precedes an unqualified entry. Derived forms can use the stem and explicitly written grammatical endings. |
516
+ | `data.words[].native[].enc` | an encoding document Address | yes | The encoding the native form is written for. |
517
+ | `data.words[].native[].font` | a face the encoding lists | no | The one face the native form is for; unstated, it is for every face the encoding lists, and a form stated for a face wins over it there. |
518
+ | `data.words[].native[].text` | the word in the script, as the encoding's characters | yes | The word as the script writes it, where the encoding's rows would write it otherwise, or by an explicit native spelling. |
520
519
  | `data.affixes[].form` | the affix as written, such as `-rith` | no | The affix as a word's `built` names it; absent on a zero affix. |
521
520
  | `data.affixes[].zero` | `true` | no | The affix is written as nothing, such as a case with no ending; it states no `form`, prints as `∅`, and no `built` piece names it. |
522
521
  | `data.affixes[].kind` | one of case, derivation, tense, aspect, mood, voice, name, lineage | no | What the affix makes. |
@@ -531,10 +530,10 @@ These fields are accepted by every note type. A field's value can still be irrel
531
530
  | `data.affixes[].examples` | a list of strings | no | The names the affix is read from. |
532
531
  | `data.affixes[].barredAfter` | a list of sounds | no | The sounds a stem may not close on to take the affix. |
533
532
  | `data.affixes[].tones` | a tone pattern, such as `L` | no | The tone pattern the affix carries, written as a word's `tones` is. |
534
- | `data.affixes[].native` | a list of `{ map, font?, text }` | no | The affix's writing under a logographic map, which a word whose `built` names the affix is written with. |
535
- | `data.affixes[].native[].map` | a mapping note's `data.map.name` | yes | The map the native form is written for. |
536
- | `data.affixes[].native[].font` | a face the map lists | no | The one face the native form is for; unstated, it is for every face the map lists, and a form stated for a face wins over it there. |
537
- | `data.affixes[].native[].text` | the word in the script, as the map's characters | yes | The word as the script writes it, where the map's rows would write it otherwise, or as a logographic map writes it. |
533
+ | `data.affixes[].native` | a list of `{ enc, font?, text }` | no | The affix's writing under an encoding, which a word whose `built` names the affix is written with. |
534
+ | `data.affixes[].native[].enc` | an encoding document Address | yes | The encoding the native form is written for. |
535
+ | `data.affixes[].native[].font` | a face the encoding lists | no | The one face the native form is for; unstated, it is for every face the encoding lists, and a form stated for a face wins over it there. |
536
+ | `data.affixes[].native[].text` | the word in the script, as the encoding's characters | yes | The word as the script writes it, where the encoding's rows would write it otherwise, or by an explicit native spelling. |
538
537
  | `data.grammar.plural` | list of `{ ending, becomes }` | no | Default noun plurals, replacing the first matching literal ending in authored order. A word's explicit plural wins; no word is checked against this rule. |
539
538
  | `data.grammar.plural[].ending` | a literal ending string, which may be empty | yes | The bare suffix replaced by this rule; empty matches every noun. |
540
539
  | `data.grammar.plural[].becomes` | a literal ending string, which may be empty | yes | The bare replacement; empty removes the matching suffix. |
@@ -554,10 +553,10 @@ These fields are accepted by every note type. A field's value can still be irrel
554
553
  | `data.grammar.participles` | a map — `{ root, affixes }` | no | Participles on the verb root: a verb closing on a `root` ending drops it, and each named suffix attaches to what is left; a participle closing on an `agreement` ending agrees. Read by the mapped-run word check. |
555
554
  | `data.grammar.participles.root` | a list of closing letters, such as `[athen, ethen]` | yes | The infinitive endings a verb's citation form drops to give its root, the longest that matches first. A verb closing on none has no participle. |
556
555
  | `data.grammar.participles.affixes` | a list of suffixes of `data.affixes`, such as `[-andar, -thos]` | yes | The participle suffixes, each attaching to the root as its own `replaces` says: `vowel` or letters take the place of the root's close, and none adds it whole. |
557
- | `data.grammar.native` | a list of `{ ending, map, font?, text }` | no | Under a logographic map, the signs a noun's plural or a verb's present form is written with after its stem's writing, one entry per ending, map and face. |
556
+ | `data.grammar.native` | a list of `{ ending, enc, font?, text }` | no | Under an encoding, the signs a noun's plural or a verb's present form is written with after its stem's writing, one entry per ending, encoding and face. |
558
557
  | `data.grammar.native[].ending` | one of plural, I, you, heShe, we, youAll, they | yes | The ending written: `plural` for a noun's plural, or a person of `verbs.persons` for a verb's present form. |
559
- | `data.grammar.native[].map` | a logographic mapping note's `data.map.name` | yes | The logographic map the ending is written for. |
560
- | `data.grammar.native[].font` | a face the map lists | no | The one face the writing is for; unstated, it is for every face the map lists, and a writing stated for a face wins over it there. |
558
+ | `data.grammar.native[].enc` | an encoding document Address | yes | The encoding the ending is written for. |
559
+ | `data.grammar.native[].font` | a face the encoding lists | no | The one face the writing is for; unstated, it is for every face the encoding lists, and a writing stated for a face wins over it there. |
561
560
  | `data.grammar.native[].text` | the ending in the script, as the map's characters; empty writes the stem alone | yes | The signs written after the stem's writing. |
562
561
  | `data.tongues[].tag` | a tag such as `dunhari`, or `exonym:<tongue>` | yes | The tag a register row's `tongue` names: lowercase letters and digits, or a tag closing on `:<tongue>`, which declares one tag per tongue named after the colon. |
563
562
  | `data.tongues[].meaning` | a string | yes | What a name of this tongue is. |
@@ -712,11 +711,8 @@ These fields are accepted by every note type. A field's value can still be irrel
712
711
  | `data.naming.nearName.examples` | list of `{ near, given }` | no | Worked near names, each beside its given name. |
713
712
  | `data.naming.nearName.examples[].near` | a near name, such as `Anlâ` | yes | The near name. |
714
713
  | `data.naming.nearName.examples[].given` | a given name | yes | The given name it is the near name of. |
715
- | `data.writtenIn.map` | a mapping note's `data.map.name` | yes | The map the language's text is written by. |
716
- | `data.writtenIn.font` | an installed face's slug | yes | The face the text is set in, one the map is drawn in. |
717
714
  | `data.script.fonts` | list of installed faces' slugs | yes | The faces the script is written in, by the slug `font=` names them by. A face belongs to one script. |
718
- | `data.script.emphasis` | `rubric` or `overline` | yes | How the script marks a stressed word: `rubric` sets it in red, `overline` draws a line above it. It sets `*…*` and `**…**` inside a run in one of its faces, unless the run's language states its own `emphasis`. |
719
- | `data.script.strongEmphasis` | `rubric` or `overline` | no | The convention `**…**` takes instead of `emphasis`'s. |
715
+ | `data.script.emphasis` | `rubric` or `overline` | yes | How the script marks a stressed word: `rubric` sets it in red, `overline` draws a line above it. It sets `.emph` and accepted English emphasis inside a run in one of its faces, unless the run's language states its own `emphasis`. |
720
716
  | `data.script.direction` | `vertical-rl` | no | How the script is written: `vertical-rl` is columns, top to bottom, read from the right, which a `scroll` fence sets. A script stating none is written in lines. |
721
717
 
722
718
  **System mappings**
@@ -1001,53 +997,74 @@ Lore records in-world knowledge. A `culture` describes a people; a `custom` desc
1001
997
 
1002
998
  **Subtypes:** `rules`, `userguide`, `reference`, `howto`, `concept`, `settingguide`, `post`.
1003
999
 
1004
- | Field | Shape | Meaning |
1005
- | --------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1006
- | `data.concepts` | list of `{ id, gloss, group }` | The concepts of a core vocabulary, in the order the report lists them. See [the core vocabulary](format-details.md#the-core-vocabulary). |
1007
- | `data.date` | string | The day a post was published, as `YYYY-MM-DD`. Required on a `post`; no other subType states it. Posts are listed and fed newest first by it. |
1008
- | `data.author` | string | Who wrote a post, as the byline prints them. A post that names none carries the site's byline, the first entry of `authors`; no other subType states it. |
1009
- | `data.culture` | an Address | The culture a setting guide introduces, as a culture lore note. Required on `settingguide`; no other subType states it. |
1010
- | `data.sounds` | a map — `{ letters?, marks? }` | A spelling guide's letters and the marks written on them, for every tongue of the setting: each letter row's `tongue` names the language notes that write it. |
1011
- | `data.map` | a map — `{ name, logographic?, vowels?, fonts, rows }` | A mapping, which native text marked with `lang=`, `.nativefont` and its `map=` name is rewritten by before it is set. See [mapping notes](format-details.md#mapping-notes). |
1012
- | `data.metres` | list of `{ name, beats, kind }` | The metres a verse fence's `scan=` names. A fence that states one is warned at each line that cannot have its beats. See [metres](../authoring/links-and-markup.md#metre). |
1000
+ | Field | Shape | Meaning |
1001
+ | --------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1002
+ | `data.language` | a configured language alias | The configured language alias an encoding document writes. |
1003
+ | `data.font` | an installed font slug | The encoding's installed font, which supplies all glyph codes. |
1004
+ | `data.accepts` | a list of characters or ranges | Accepted Roman letters and additions to standard punctuation. |
1005
+ | `data.vowels` | a string | The letters a contextual vowel match reads as vowels. |
1006
+ | `data.mappings` | Roman strings to glyph codes | Roman source strings mapped to codes published by the selected font. |
1007
+ | `data.rules` | the closed encoding rules vocabulary | Executable normalization, matching, case, stress, vowel groups, accents and contextual mappings. |
1008
+ | `data.concepts` | list of `{ id, gloss, group }` | The concepts of a core vocabulary, in the order the report lists them. See [the core vocabulary](format-details.md#the-core-vocabulary). |
1009
+ | `data.date` | string | The day a post was published, as `YYYY-MM-DD`. Required on a `post`; no other subType states it. Posts are listed and fed newest first by it. |
1010
+ | `data.author` | string | Who wrote a post, as the byline prints them. A post that names none carries the site's byline, the first entry of `authors`; no other subType states it. |
1011
+ | `data.culture` | an Address | The culture a setting guide introduces, as a culture lore note. Required on `settingguide`; no other subType states it. |
1012
+ | `data.sounds` | a map — `{ letters?, marks? }` | A spelling guide's letters and the marks written on them, for every tongue of the setting: each letter row's `tongue` names the language notes that write it. |
1013
+ | `data.metres` | list of `{ name, beats, kind }` | The metres a verse fence's `scan=` names. A fence that states one is warned at each line that cannot have its beats. See [metres](../authoring/links-and-markup.md#metre). |
1013
1014
 
1014
1015
  **Inner keys** — a key a field's value does not declare is an error at its own line and column.
1015
1016
 
1016
- | Key | Shape | Required | Meaning |
1017
- | -------------------------------- | ------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1018
- | `data.concepts[].id` | lowercase letters, digits and `-` | yes | The concept's id, which a word's `concept` names. Unique in the package. |
1019
- | `data.concepts[].gloss` | a short gloss, such as `a smell; a scent` | yes | What the concept means, in brief. |
1020
- | `data.concepts[].group` | a group name, such as `senses` | yes | The group the report lists the concept under. |
1021
- | `data.sounds.letters` | list of `{ letters, kind, like?, place?, ipa?, tongue? }` | no | The letters the language writes, and the sounds they stand for. |
1022
- | `data.sounds.letters[].letters` | a list of letters | yes | The letters or digraphs the row describes, as written. |
1023
- | `data.sounds.letters[].kind` | a string, such as `consonant` or `long vowel` | yes | What kind of sound they write. |
1024
- | `data.sounds.letters[].like` | a string | no | What they sound like; absent where the row states only their kind and place. |
1025
- | `data.sounds.letters[].place` | a string, such as `lips` or `tongue-tip` | no | Where in the mouth they are made. |
1026
- | `data.sounds.letters[].ipa` | a string in the International Phonetic Alphabet, such as `ʃ` | no | The sounds they write, in the International Phonetic Alphabet. |
1027
- | `data.sounds.letters[].tongue` | a tag or a language note's Address, or a list of them | no | The tongues that write them, each a tag `data.tongues` declares or the Address of a language note; every one where absent. A `doc` note's rows name language notes only. |
1028
- | `data.sounds.marks` | list of `{ mark, letters, kind, limit }` | no | The marks the language writes on its letters. |
1029
- | `data.sounds.marks[].mark` | a string, such as `acute` | yes | The mark's name. |
1030
- | `data.sounds.marks[].letters` | a list of letters | yes | The letters that carry it. |
1031
- | `data.sounds.marks[].kind` | one of stress, length, apart | yes | What the mark says of its letter. |
1032
- | `data.sounds.marks[].limit` | one of one, any | yes | How many a word may carry: `one` at most, or `any`. |
1033
- | `data.map.name` | string | yes | What `map=` names the map by: lowercase letters, digits and `-`, unique in the package. |
1034
- | `data.map.logographic` | `true` | no | The script writes meanings: a mapped run's words are written by the language's lexicon alone, and the rows write only the text between them. |
1035
- | `data.map.vowels` | string | no | The characters a row's `before: vowel` reads as vowels; any other letter is a consonant. |
1036
- | `data.map.rows` | list of `[from, to]` or `{ from, to, before?, at? }` | yes | The rewrites, each `[from, to]` or `{ from, to, before?, at? }`: `before` is `vowel` or `consonant`, `at` is `start` or `end`. The longest `from` wins, and a character no row matches passes through. |
1037
- | `data.map.rows[].from` | a string of one or more characters | yes | The one or more characters the row matches. |
1038
- | `data.map.rows[].to` | a string, which may be empty | yes | What the row writes in their place; empty to drop them. |
1039
- | `data.map.rows[].before` | string | no | `vowel` or `consonant`: what must follow the match for the row to apply. |
1040
- | `data.map.rows[].at` | string | no | `start` or `end`: where the match must sit for the row to apply. |
1041
- | `data.map.fonts` | list of `{ font, rows? }` | yes | The faces the map is drawn in, at least one, each with any rows of its own. A run in another face is refused, and each face must draw every character its rows and the lexicon's native forms write. |
1042
- | `data.map.fonts[].font` | an installed face's slug | yes | A face the map is drawn in, by the slug `font=` names it by. |
1043
- | `data.map.fonts[].rows` | list of `[from, to]` or `{ from, to, before?, at? }` | no | Rows for this face alone, tried before the shared `rows`; the same shapes and conditions. |
1044
- | `data.map.fonts[].rows[].from` | a string of one or more characters | yes | The one or more characters the row matches. |
1045
- | `data.map.fonts[].rows[].to` | a string, which may be empty | yes | What the row writes in their place; empty to drop them. |
1046
- | `data.map.fonts[].rows[].before` | string | no | `vowel` or `consonant`: what must follow the match for the row to apply. |
1047
- | `data.map.fonts[].rows[].at` | string | no | `start` or `end`: where the match must sit for the row to apply. |
1048
- | `data.metres[].name` | string | yes | What a verse fence's `scan=` names the metre by: lowercase letters, digits and `-`, unique in the package. |
1049
- | `data.metres[].beats` | a whole number above zero | yes | The beats of one line. |
1050
- | `data.metres[].kind` | one of accentual | yes | How the beats are counted: `accentual`, one beat for each stressed syllable the language's stress rule and the beat convention give. |
1017
+ | Key | Shape | Required | Meaning |
1018
+ | ------------------------------------------------------ | ------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1019
+ | `data.rules.normalization` | "NFC" | yes | Required value: "NFC". |
1020
+ | `data.rules.matching` | "longest-first" | yes | Required value: "longest-first". |
1021
+ | `data.rules.case` | a map | no | Case handling for letters and consonant groups. |
1022
+ | `data.rules.case.single-letter` | "preserve" | yes | Required value: "preserve". |
1023
+ | `data.rules.case.consonant-group` | "lowercase-or-initial-uppercase" | yes | Required value: "lowercase-or-initial-uppercase". |
1024
+ | `data.rules.case.other-group-cases` | "forbidden" | yes | Required value: "forbidden". |
1025
+ | `data.rules.stress` | a map | no | The font's nonprinting stress modifier after an acute vowel. |
1026
+ | `data.rules.stress.modifier` | a string | yes | The font code for U+E0114. |
1027
+ | `data.rules.stress.position` | "after-stressed-vowel" | yes | Required value: "after-stressed-vowel". |
1028
+ | `data.rules.stress.printing` | false | yes | Required value: false. |
1029
+ | `data.rules.stress.effect` | "stress-only" | yes | Required value: "stress-only". |
1030
+ | `data.rules.stress.changes-sound` | false | yes | Required value: false. |
1031
+ | `data.rules.stress.changes-length` | false | yes | Required value: false. |
1032
+ | `data.rules.stress.vowel-group-position` | "first-vowel" | yes | Required value: "first-vowel". |
1033
+ | `data.rules.vowel-groups` | a map | no | Explicit recognized vowel groups and their acute placement. |
1034
+ | `data.rules.vowel-groups.reading` | "established" | yes | Required value: "established". |
1035
+ | `data.rules.vowel-groups.allow-splitting` | false | yes | Required value: false. |
1036
+ | `data.rules.vowel-groups.unrecognized-adjacent-vowels` | "separate" | yes | Required value: "separate". |
1037
+ | `data.rules.vowel-groups.groups` | a list of vowel groups | yes | The complete authored group inventory. |
1038
+ | `data.rules.diaeresis` | a map | no | Whether diaeresis mappings are allowed. |
1039
+ | `data.rules.diaeresis.allowed` | false | yes | Required value: false. |
1040
+ | `data.rules.grave-accent` | a map | no | Whether grave-accent mappings are allowed. |
1041
+ | `data.rules.grave-accent.allowed` | false | yes | Required value: false. |
1042
+ | `data.rules.contextual-mappings` | a list of conditional mappings | no | Before-vowel alternatives applied before unconditional mappings. |
1043
+ | `data.rules.contextual-mappings[].match` | a string | yes | The source sequence. |
1044
+ | `data.rules.contextual-mappings[].before` | "vowel" | yes | Required value: "vowel". |
1045
+ | `data.rules.contextual-mappings[].emit` | a glyph code or list of codes | yes | The font codes written by this alternative. |
1046
+ | `data.rules.y` | a map | no | The consonant reading of y. |
1047
+ | `data.rules.y.kind` | "consonant" | yes | Required value: "consonant". |
1048
+ | `data.rules.y.sound` | "y in yes" | yes | Required value: "y in yes". |
1049
+ | `data.rules.y.vowel-reading` | false | yes | Required value: false. |
1050
+ | `data.concepts[].id` | lowercase letters, digits and `-` | yes | The concept's id, which a word's `concept` names. Unique in the package. |
1051
+ | `data.concepts[].gloss` | a short gloss, such as `a smell; a scent` | yes | What the concept means, in brief. |
1052
+ | `data.concepts[].group` | a group name, such as `senses` | yes | The group the report lists the concept under. |
1053
+ | `data.sounds.letters` | list of `{ letters, kind, like?, place?, ipa?, tongue? }` | no | The letters the language writes, and the sounds they stand for. |
1054
+ | `data.sounds.letters[].letters` | a list of letters | yes | The letters or digraphs the row describes, as written. |
1055
+ | `data.sounds.letters[].kind` | a string, such as `consonant` or `long vowel` | yes | What kind of sound they write. |
1056
+ | `data.sounds.letters[].like` | a string | no | What they sound like; absent where the row states only their kind and place. |
1057
+ | `data.sounds.letters[].place` | a string, such as `lips` or `tongue-tip` | no | Where in the mouth they are made. |
1058
+ | `data.sounds.letters[].ipa` | a string in the International Phonetic Alphabet, such as `ʃ` | no | The sounds they write, in the International Phonetic Alphabet. |
1059
+ | `data.sounds.letters[].tongue` | a tag or a language note's Address, or a list of them | no | The tongues that write them, each a tag `data.tongues` declares or the Address of a language note; every one where absent. A `doc` note's rows name language notes only. |
1060
+ | `data.sounds.marks` | list of `{ mark, letters, kind, limit }` | no | The marks the language writes on its letters. |
1061
+ | `data.sounds.marks[].mark` | a string, such as `acute` | yes | The mark's name. |
1062
+ | `data.sounds.marks[].letters` | a list of letters | yes | The letters that carry it. |
1063
+ | `data.sounds.marks[].kind` | one of stress, length, apart | yes | What the mark says of its letter. |
1064
+ | `data.sounds.marks[].limit` | one of one, any | yes | How many a word may carry: `one` at most, or `any`. |
1065
+ | `data.metres[].name` | string | yes | What a verse fence's `scan=` names the metre by: lowercase letters, digits and `-`, unique in the package. |
1066
+ | `data.metres[].beats` | a whole number above zero | yes | The beats of one line. |
1067
+ | `data.metres[].kind` | one of accentual | yes | How the beats are counted: `accentual`, one beat for each stressed syllable the language's stress rule and the beat convention give. |
1051
1068
 
1052
1069
  ### macro
1053
1070
 
@@ -39,6 +39,14 @@ picture, marked text is mapped and set in its face, an icon is drawn, and a
39
39
  caption is numbered with its asset's role, so a map is a **Map**. A query, which
40
40
  a buffer cannot run, stays highlighted source in both views.
41
41
 
42
+ With an index, a marked-text run that cannot be set produces an error finding
43
+ and sets the result's `ok` to `false`. The preview still returns recoverable
44
+ HTML, including the Roman text of a failed native-script run. Findings identify
45
+ the supplied `file`, severity, message, line and column in the authored buffer,
46
+ including its frontmatter lines; expansion does not shift their positions.
47
+ Hosts can display that HTML beside the findings. Without an index, marked text
48
+ stays highlighted source and produces no run findings.
49
+
42
50
  Wikilinks, image and portrait links retain their complete source: blue brackets,
43
51
  purple addresses, bold foreground labels and grey attributes. Entire spans are bold, including their visible brackets and attributes.
44
52
  Definition-list terms are bold and definitions use normal text. SQL and pagelist fences remain grey source;
package/e2e.mjs CHANGED
@@ -799,7 +799,7 @@ export function suiteExecutables(command) {
799
799
  }
800
800
 
801
801
  /** The Chrome for Testing build shared by local and CI Cypress runs. */
802
- export const CHROME_BUILD_ID = "158.0.8110.0";
802
+ export const CHROME_BUILD_ID = "158.0.8122.0";
803
803
 
804
804
  /**
805
805
  * The browser cache survives reseeding and container teardown.
@@ -633,9 +633,24 @@ export class BasePackCompiler {
633
633
  * @throws {Error} When `findings` is non-empty, after reporting all of them.
634
634
  */
635
635
  reportConvertBodyFindings(findings) {
636
+ this.externalBodyFindings ??= new Set();
636
637
  for (const finding of findings) {
638
+ if (finding.file && finding.file !== this.currentNote?.absPath) {
639
+ const key = JSON.stringify([
640
+ finding.file,
641
+ finding.line,
642
+ finding.column,
643
+ finding.message,
644
+ ]);
645
+ if (this.externalBodyFindings.has(key)) continue;
646
+ this.externalBodyFindings.add(key);
647
+ }
637
648
  this.errorCount++;
638
- this.noteError(finding.message, { line: finding.line, column: finding.column });
649
+ this.noteError(finding.message, {
650
+ file: finding.file,
651
+ line: finding.line,
652
+ column: finding.column,
653
+ });
639
654
  }
640
655
  if (findings.length) {
641
656
  const error = new Error(
@@ -670,10 +685,13 @@ export class BasePackCompiler {
670
685
  const bodyColumn = this.currentNote?.bodyColumn ?? 1;
671
686
  // A finding's body position, in the file. The parser trims the body,
672
687
  // so only its first line is offset by the column the body starts at.
673
- const inFile = ({ line, column }) => ({
674
- line: bodyLine + line - 1,
675
- column: line === 1 && column !== undefined ? bodyColumn + column - 1 : column,
676
- });
688
+ const inFile = ({ file, line, column }) =>
689
+ file ?
690
+ { file, line, column }
691
+ : {
692
+ line: bodyLine + line - 1,
693
+ column: line === 1 && column !== undefined ? bodyColumn + column - 1 : column,
694
+ };
677
695
  const syntax = this.corpus?.syntax;
678
696
  const scanned = syntax?.(body);
679
697
  for (const secretError of [
@@ -893,12 +911,12 @@ export class BasePackCompiler {
893
911
  * naming the file alone beats naming a line that is not the problem.
894
912
  *
895
913
  * @param {string} message - What is wrong, in one sentence.
896
- * @param {{line?: number, column?: number}} [position] - Where, if known.
914
+ * @param {{file?: string, line?: number, column?: number}} [position] - Where, if known.
897
915
  * @returns {void}
898
916
  */
899
917
  noteWarn(message, position) {
900
918
  emitDiagnostic({
901
- file: this.currentNote?.absPath,
919
+ file: position?.file ?? this.currentNote?.absPath,
902
920
  line: position?.line,
903
921
  column: position?.column,
904
922
  severity: "warning",
@@ -910,12 +928,12 @@ export class BasePackCompiler {
910
928
  * Reports an error about the note being compiled.
911
929
  *
912
930
  * @param {string} message - What is wrong, in one sentence.
913
- * @param {{line?: number, column?: number}} [position] - Where, if known.
931
+ * @param {{file?: string, line?: number, column?: number}} [position] - Where, if known.
914
932
  * @returns {void}
915
933
  */
916
934
  noteError(message, position) {
917
935
  emitDiagnostic({
918
- file: this.currentNote?.absPath,
936
+ file: position?.file ?? this.currentNote?.absPath,
919
937
  line: position?.line,
920
938
  column: position?.column,
921
939
  severity: "error",
@@ -125,6 +125,13 @@ export function isLetterTier(cp) {
125
125
  */
126
126
  export const TYPOGRAPHY = Object.freeze(
127
127
  new Set([
128
+ 0x00ab, // « outer quotation open
129
+ 0x00bb, // » outer quotation close
130
+ 0x2039, // ‹ inner quotation open
131
+ 0x203a, // › inner quotation close
132
+ 0x203c, // ‼ imperative terminal
133
+ 0x27e6, // ⟦ codage open
134
+ 0x27e7, // ⟧ codage close
128
135
  0x2013, // – en dash
129
136
  0x2014, // — em dash
130
137
  0x2018, // ' left single quote
@@ -170,6 +177,7 @@ export const NOTATION = Object.freeze(
170
177
  */
171
178
  const SCRIPT_BLOCKS = Object.freeze([
172
179
  [0xf0000, 0xf2fff], // plane 15: Pirzath, Thuravark, Semaron, the reserved alphabets, Teng Chou
180
+ [0xf3000, 0xf3fff], // Ch'alish P'ilam Common
173
181
  [0x1e960, 0x1ec6f], // Zethu overflow, Qalezu, Zethu
174
182
  [0x1ecc0, 0x1ecff], // Qalezu catch-before forms
175
183
  [0x1ed50, 0x1ed7f], // Veldikko
@@ -195,7 +203,11 @@ export function isSettingScriptCodePoint(cp) {
195
203
  */
196
204
  export function isAllowedCodePoint(cp) {
197
205
  return (
198
- isLetterTier(cp) || TYPOGRAPHY.has(cp) || NOTATION.has(cp) || isSettingScriptCodePoint(cp)
206
+ isLetterTier(cp) ||
207
+ TYPOGRAPHY.has(cp) ||
208
+ NOTATION.has(cp) ||
209
+ isSettingScriptCodePoint(cp) ||
210
+ (cp >= 0xe0100 && cp <= 0xe01ef)
199
211
  );
200
212
  }
201
213
 
@@ -227,7 +227,6 @@ types:
227
227
  - negation
228
228
  - naming
229
229
  - emphasis
230
- - writtenIn
231
230
  - script
232
231
  subTypes:
233
232
  - social
@@ -367,12 +366,17 @@ types:
367
366
  - encounter
368
367
  doc:
369
368
  data:
369
+ - language
370
+ - font
371
+ - accepts
372
+ - vowels
373
+ - mappings
374
+ - rules
370
375
  - culture
371
376
  - concepts
372
377
  - date
373
378
  - author
374
379
  - sounds
375
- - map
376
380
  - metres
377
381
  subTypes:
378
382
  - rules
@@ -106,7 +106,7 @@ import { AddressLink } from "./address-values.mjs";
106
106
  import { scanSpans } from "./content-spans.mjs";
107
107
  import { rebaseNameSpans } from "./name-markup.mjs";
108
108
  import { analyzeNameSpans } from "./names.mjs";
109
- import { fillWrittenIn, recordRuns } from "./run-rows.mjs";
109
+ import { fillEncodingRuns, recordRuns } from "./run-rows.mjs";
110
110
  import fs from "node:fs";
111
111
  import path from "node:path";
112
112
 
@@ -1074,6 +1074,8 @@ export function resolveCultures(
1074
1074
  * calendar note's shortcode; see {@link module:engine/note-calendars}.
1075
1075
  * @param {string} [options.configFile] - The configuration file, where an
1076
1076
  * unknown `defaultCalendar` is reported.
1077
+ * @param {string} [options.rootDir] - Consumer package root for loading installed
1078
+ * encodings when filling language native-writing defaults.
1077
1079
  * @param {Readonly<Record<string, {tag: string}>>} [options.languages] - The
1078
1080
  * configured languages, which give each run's `lang=` its tag.
1079
1081
  * @param {(key: string) => ({culture?: unknown}|undefined)} [options.foreignRecord] -
@@ -1095,6 +1097,7 @@ export function collectContentIndex(
1095
1097
  currentDate,
1096
1098
  defaultCalendar,
1097
1099
  configFile,
1100
+ rootDir,
1098
1101
  languages,
1099
1102
  skipDirectories,
1100
1103
  assetsBase,
@@ -1252,7 +1255,7 @@ export function collectContentIndex(
1252
1255
 
1253
1256
  // A run marked with a language alone takes its language's script, which
1254
1257
  // is read once every language note is recorded.
1255
- fillWrittenIn(records, { languages, contentPackage });
1258
+ fillEncodingRuns(records, { languages, contentPackage, rootDir });
1256
1259
 
1257
1260
  // Every note record exists and every Address in it is a tuple, so a parent
1258
1261
  // is found by its canonical address. Before the sort, which reads nothing
@@ -1489,6 +1492,7 @@ export function indexRecordsFor({
1489
1492
  return foreign.get(key);
1490
1493
  };
1491
1494
  return collectContentIndex(tree, {
1495
+ rootDir: resolved.rootDir,
1492
1496
  contentPackage: resolved.contentPackage,
1493
1497
  currentDate: resolved.currentDate,
1494
1498
  defaultCalendar: resolved.defaultCalendar ?? undefined,
@@ -648,6 +648,7 @@ export function buildLinkIndex(
648
648
  }
649
649
 
650
650
  return {
651
+ config,
651
652
  notes,
652
653
  frontmatterLinks,
653
654
  anchors,