@heroiclands/package-build 25.0.0 → 25.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +26 -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 +144 -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 +193 -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 +458 -575
  19. package/docs/reference/format-details.md +292 -404
  20. package/docs/reference/note-types.md +83 -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 +701 -0
  37. package/engine/encoding-notes.mjs +186 -0
  38. package/engine/encoding-standard.mjs +42 -0
  39. package/engine/encoding-template.mjs +370 -0
  40. package/engine/encoding-terms.mjs +178 -0
  41. package/engine/encoding.mjs +952 -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 +44 -0
  82. package/types/engine/encoding-terms.d.mts +143 -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
@@ -0,0 +1,193 @@
1
+ # Encoding text
2
+
3
+ An encoding is a `doc` note for one language in one font. `enc=` resolves its
4
+ Address with `doc` as the default type: `enc=aelwsemaronenc` selects
5
+ `doc-aelwsemaronenc`. The document's `data.language` is a configured language
6
+ alias and `data.font` is an installed font slug. Those values supply a run's
7
+ language and face; contradictory explicit values are errors. `enc=default`
8
+ leaves Roman text as written and requires no encoding document.
9
+
10
+ Fonts publish glyphs and their codes in `fonts.json`, under each family's
11
+ `codepoints`: `{codepoint: "U+F0230", code: "ANKRON_MIN", ...}`. Codes are
12
+ case-sensitive letters, digits and underscores, unique within that font.
13
+ Encoding documents refer to those codes in `data.mappings`; they do not copy
14
+ the font's code table. Each mapping value is one code or a list of codes.
15
+
16
+ ```yaml
17
+ type: doc
18
+ subType: reference
19
+ shortcode: aelwsemaronenc
20
+ name: { full: Aelwythi in Semaron }
21
+ data:
22
+ language: aelw
23
+ font: semaron-kuriale
24
+ mappings:
25
+ a: ANKRON_MIN
26
+ A: ANKRON_MAJ
27
+ ch: CHEROLA_MIN
28
+ Ch: CHEROLA_MAJ
29
+ rules:
30
+ normalization: NFC
31
+ matching: longest-first
32
+ ```
33
+
34
+ This is a small illustrative mapping, not the complete Aelwythi alphabet.
35
+ `data.accepts`, when supplied, is a list of Roman characters or ranges such as
36
+ `a-z`; otherwise the mapping sources determine the accepted letters.
37
+ `data.vowels` is a string of vowels used by contextual rules. A document's body
38
+ explains the spelling to its reader. Ordinary document metadata remains
39
+ available alongside the executable fields.
40
+
41
+ ## Executable rules
42
+
43
+ `data.rules` has a closed vocabulary. Unknown keys and unsupported values are
44
+ errors at their source keys; explanatory rules do not silently authorize a
45
+ second behaviour.
46
+
47
+ | Rule | Values and effect |
48
+ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
49
+ | `normalization` | Required `NFC`; input is normalized before matching, with original source offsets retained. |
50
+ | `matching` | Required `longest-first`. |
51
+ | `roundTrip` | Optional `exact` (default) or `canonical`; canonical permits authored aliases and empty code sequences. |
52
+ | `word-separators` | Optional list of source word dividers that emit shared word spaces after contextual alternatives. |
53
+ | `case` | `{single-letter: preserve, consonant-group: lowercase-or-initial-uppercase, other-group-cases: forbidden}`; single letters retain authored case, and groups use lowercase or an initial capital. |
54
+ | `stress` | `{modifier, position: after-stressed-vowel, printing: false, effect: stress-only, changes-sound: false, changes-length: false, vowel-group-position: first-vowel}`; `modifier` names a font code and acute mappings append it to the unchanged vowel. |
55
+ | `vowel-groups` | `{reading: established, allow-splitting: false, unrecognized-adjacent-vowels: separate, groups: [...]}`; the explicit group list determines where an acute belongs on its first vowel. |
56
+ | `diaeresis` | `{allowed: boolean}` permits or forbids explicitly mapped diaeresis characters. |
57
+ | `grave-accent` | `{allowed: boolean}` permits or forbids explicitly mapped grave-accent characters. |
58
+ | `contextual-mappings` | A list of `{match, emit, before?, at?}`; following-letter or source-position conditions precede unconditional mappings. |
59
+ | `y` | `{kind: consonant, sound: "y in yes", vowel-reading: false}`; y cannot also appear in `data.vowels`. |
60
+
61
+ The accent blocks accept an explicit Boolean `allowed`. False forbids that
62
+ accent; true permits it only where the character is accepted and mapped.
63
+ Omitting the block also permits such explicit mappings. Neither choice invents
64
+ an accent pronunciation or adds a character to the inventory.
65
+
66
+ `rules.roundTrip` is optional and defaults to `exact`. Exact mappings must be
67
+ uniquely decodable across adjacent mappings and spaces; ambiguous outputs and
68
+ empty code sequences are refused. This remains the default for Aelwythi.
69
+
70
+ An encoding whose orthography omits distinctions may explicitly state
71
+ `roundTrip: canonical`. Its mappings and conditional emissions may use `[]`
72
+ for an inherent vowel or deleted boundary marker, and different Roman sources
73
+ may name the same output. Decoding returns a conventional spelling. For an output it prefers the first
74
+ authored alias whose following-letter condition matches the decoded suffix,
75
+ with conditional rows before general mappings and longest complete native
76
+ matches first. When none applies, the first authored reading is retained:
77
+ an inherent vowel or deleted marker may have hidden the original condition. It does not invent characters for
78
+ empty outputs or store invisible metadata to restore the original spelling.
79
+ A language's authoritative vocabulary can recover fuller word spellings.
80
+ Shared word spaces remain spaces in both modes.
81
+
82
+ Contextual entries use `{match, emit, before?, at?}` and require at least one
83
+ condition. `before` is `vowel` or `consonant`; `at` is `start`, `end` or
84
+ `line-start`. `start` and `end` refer to word boundaries. A consonant is a
85
+ letter outside the declared `data.vowels`. Line start follows a newline or
86
+ verse slash and permits preceding indentation carets. Multiple conditions
87
+ combine with AND. Entries for the same source may use different conditions;
88
+ repeating the same source and condition tuple is an error. Equal-length
89
+ contextual matches retain authored order.
90
+
91
+ ```yaml
92
+ rules:
93
+ normalization: NFC
94
+ matching: longest-first
95
+ roundTrip: canonical
96
+ contextual-mappings:
97
+ - { match: e, before: consonant, emit: [] }
98
+ - { match: e, at: end, emit: [] }
99
+ ```
100
+
101
+ Conditions govern forward matching. Inverse following-letter choices use the
102
+ visible decoded suffix; canonical mode keeps its authored fallback when that
103
+ suffix has lost an inherent vowel or marker. Position conditions govern only
104
+ forward matching. No mode invents deleted markers. Exact decoding retains
105
+ strict following-letter checks.
106
+
107
+ `rules.word-separators` lists source word dividers, such as `['-']`, that emit
108
+ the shared ASCII word space. Contextual rules take priority, so a canonical
109
+ note can emit `[]` for an edge hyphen and a word space for an internal hyphen.
110
+ These sources cannot also be ordinary mappings, replace shared punctuation,
111
+ or contain whitespace, literal markers or structural controls. Separator
112
+ sources participate in inferred `accepts`; no font SPACE code is invented.
113
+ Since a divider and an ordinary space share output, such a note selects
114
+ canonical round trips. Decoding keeps the shared space as a space.
115
+
116
+ A contextual match cannot contain whitespace, literal markers or forbidden
117
+ accents. Both modes enforce the same accepted characters, normalization,
118
+ case, stress, accent and literal-control rules. The compiler does not infer
119
+ linguistic vowel groups or silently add missing letter mappings.
120
+
121
+ Lint checks every authored word, variant and registered name for a language
122
+ with an encoding document, including unused entries. It also checks actual
123
+ name-list entries and name-element spellings and examples; patterns and
124
+ abstract roots are excluded. Bound-element edge hyphens mark joining positions.
125
+ These are bare Roman checks, so a native writing cannot bypass spelling rules.
126
+ Different case and multiword names retain their authored spelling.
127
+
128
+ Every encoding inherits the shared standard's fifteen Roman punctuation
129
+ mappings, verse and name controls, and literal boundaries. `‼` marks an
130
+ imperative and `!` an exclamation. An explicit mapping can give a standard
131
+ source its own font glyph. Other punctuation needs its own mapping.
132
+
133
+ ## Conversion API
134
+
135
+ The pure engine works in browsers and Node and opens no files. The caller
136
+ supplies the selected font's resolved code-to-Unicode table separately:
137
+
138
+ ```js
139
+ import {
140
+ compileEncoding,
141
+ encodePieces,
142
+ encodeText,
143
+ decodeText,
144
+ } from "@heroiclands/package-build/engine/encoding";
145
+
146
+ const encoding = compileEncoding(note.data, {
147
+ codes: fontCodes,
148
+ name: "thalorna-note-doc-aelwsemaronenc",
149
+ });
150
+ const native = encodeText(encoding, roman);
151
+ const reading = decodeText(encoding, native);
152
+ const pieces = encodePieces(encoding, roman);
153
+ ```
154
+
155
+ `validateSource(encoding, text, options)` validates accepted Roman characters
156
+ and source rules without requiring an ordinary mapping for every letter;
157
+ authoritative lexical writings can supply that coverage. It throws the same
158
+ positioned errors as encoding and shares its source normalization.
159
+
160
+ Only the executable fields belong in the compiler's `data` argument: language,
161
+ font, mappings, rules, and optional accepts and vowels. A loader separates
162
+ ordinary document metadata. The closed public compiler accepts encoding-note
163
+ data; `compileRows` is an internal row-execution entry used by conformance
164
+ fixtures, not an authored input format.
165
+
166
+ `encodeText` and `decodeText` return strings. Unknown input throws
167
+ `EncodingError` and returns no partial text. `offset` is a zero-based UTF-16
168
+ offset into the input, suitable for composing with a Markdown offset map.
169
+ `line` and `column` are one-based Unicode code-point positions; schema errors
170
+ expose their `key`. Exact decoding resolves the complete unambiguous output sequence. Canonical
171
+ decoding selects the authored conventional reading.
172
+
173
+ `encodePieces` uses the same rules and returns `{from, to, start, end, row}`
174
+ records. `start` and exclusive `end` delimit original UTF-16 input, including
175
+ all whitespace consumed by a piece. A matched row identifies its source and
176
+ schema key. Literal syntax has a null row. Optional `start` and `end` bounds
177
+ retain surrounding source context while preventing matches beyond the bound.
178
+
179
+ Outside literal markers, Unicode Zs and U+0009 through U+000D normalize to one
180
+ space. Other whitespace needs a mapping. `⟦…⟧` reads the font's codes or strict
181
+ `U+XXXX` scalars, with four to six uppercase hex digits and no surrogates.
182
+ Whitespace between literal codes writes nothing. Literal payload occurrences
183
+ of the opening, closing or escape control are quoted with U+E0115; decoding
184
+ unquotes them before reading their codes. Unknown codes, malformed escapes,
185
+ nested markers and unmatched markers are errors.
186
+
187
+ `{verse: true}` handles authored stanza and canto structure. The Markdown
188
+ renderer additionally writes a boundary after every content line, including
189
+ the final line before a stanza or canto. Roman line layout is retained.
190
+
191
+ The internal row conformance fixtures cover their captured source programs;
192
+ they do not establish that every language has an authored encoding document.
193
+ The Aelwythi Semaron template is the public-note integration fixture.
@@ -505,14 +505,14 @@ the font registry.
505
505
 
506
506
  ### Fonts for marked text
507
507
 
508
- Native text marked with `lang=` and `.nativefont` in one of the faces of `@heroiclands/fonts` is set in
508
+ Native text marked with `enc=` in one of the faces of `@heroiclands/fonts` is set in
509
509
  that face once the package installs the fonts package in `devDependencies`.
510
510
  The build then adds the fonts package's `fonts/` directory, which holds the
511
511
  TrueType files, to the compiler's font search path after the shipped faces and
512
512
  `fonts.path`. `font=serif`, `sans` and `mono` set the book's own serif, sans and
513
513
  mono. A package without the fonts, or whose notes set no text in a face,
514
514
  searches the same directories as before. See
515
- [marking language, font and mapping](../authoring/links-and-markup.md#marking-language-font-and-mapping).
515
+ [marking language, font and encoding](../authoring/links-and-markup.md#marking-language-font-and-encoding).
516
516
 
517
517
  ## Build and inspect
518
518
 
@@ -691,7 +691,7 @@ package-build: Cannot stage assets — these paths do not exist:
691
691
 
692
692
  ### Fonts for marked text
693
693
 
694
- A module shows native text marked with `lang=` and `.nativefont` in one of the faces of
694
+ A module shows native text selected with an `enc=` document Address in one of the faces of
695
695
  `@heroiclands/fonts` in that face once the package installs the fonts package
696
696
  in `devDependencies`. Nothing is configured: the build reads which faces the
697
697
  notes use from explicit `font=` overrides or opted-in language defaults.
@@ -705,6 +705,28 @@ no text in a face, stages nothing and its manifest names nothing. The stage
705
705
  refuses a fonts package that lacks its stylesheet, its licence or a file its
706
706
  manifest lists, with a finding at the fonts package's `fonts.json`.
707
707
 
708
+ ## Browser conversion data
709
+
710
+ A module offering a language converter declares one output in its existing
711
+ manifest build:
712
+
713
+ ```yaml
714
+ packageBuild:
715
+ converterData: data/converter.json
716
+ ```
717
+
718
+ `package-build manifest` writes the JSON beneath the build stage, so the
719
+ release includes the data alongside the module's other files. It embeds each
720
+ language's published encoding and the lexical inputs used by the build.
721
+
722
+ Bundle `@heroiclands/package-build/convert` into the module's browser code,
723
+ fetch `modules/<module-id>/data/converter.json` once, and pass the parsed data
724
+ to `encode` or `decode`. A picker can list `Object.entries(data.languages)`
725
+ using each entry's `name`; each entry already states its encoding and face.
726
+ Show the returned findings alongside the text when a word cannot be recovered.
727
+ See the [API](../api.md#browser-conversion) and
728
+ [data format](../reference/format-details.md#converter-data-format).
729
+
708
730
  ## Step 9 — generate the manifest
709
731
 
710
732
  ```console
@@ -270,10 +270,10 @@ part of the site.
270
270
 
271
271
  ## Show marked text in its font
272
272
 
273
- A site shows native text marked with `lang=` and `.nativefont` in one of the faces of
273
+ A site shows native text marked with `enc=` in one of the faces of
274
274
  `@heroiclands/fonts` in that face once the package installs the fonts package
275
275
  in `devDependencies`, as the
276
- [authoring guide](../authoring/links-and-markup.md#marking-language-font-and-mapping)
276
+ [authoring guide](../authoring/links-and-markup.md#marking-language-font-and-encoding)
277
277
  describes. Nothing else is configured: the build reads which faces the notes use
278
278
  from explicit `font=` overrides or opted-in language defaults.
279
279