@heroiclands/package-build 24.1.1 → 25.0.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.
- package/CHANGELOG.md +22 -0
- package/docs/api.md +23 -11
- package/docs/authoring/links-and-markup.md +8 -0
- package/docs/commands.md +8 -0
- package/docs/guides/writing-in-invented-scripts.md +23 -15
- package/docs/reference/format-details.md +25 -0
- package/docs/reference/note-types.md +1 -0
- package/engine/content-charset.mjs +12 -4
- package/engine/content-format.yaml +1 -0
- package/engine/content-scripts.mjs +94 -14
- package/engine/index.mjs +3 -0
- package/engine/language-data.mjs +4 -1
- package/engine/language-terms.mjs +10 -0
- package/engine/map-lexicon.mjs +12 -1
- package/engine/roman-characters.mjs +113 -0
- package/package.json +1 -1
- package/types/engine/content-charset.d.mts +8 -0
- package/types/engine/content-scripts.d.mts +5 -0
- package/types/engine/index.d.mts +1 -0
- package/types/engine/roman-characters.d.mts +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 25.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
**Languages and scripts**
|
|
8
|
+
|
|
9
|
+
- Add `data.roman` to every configured language note: an NFC string listing each
|
|
10
|
+
permitted Roman character once, with capitals, accents, digits and punctuation
|
|
11
|
+
explicit.
|
|
12
|
+
- Use `""` for no written characters; omit spaces and line breaks, and declare
|
|
13
|
+
inventories without ranges, automatic case expansion or inheritance. Maps and
|
|
14
|
+
fonts grant no extra Roman characters.
|
|
15
|
+
- Language-tagged source and visible link labels are checked before mapping;
|
|
16
|
+
configured names and their BCP 47 tags select the same inventory.
|
|
17
|
+
- Invalid characters and non-NFC spellings are reported at their original
|
|
18
|
+
positions; link destinations, attributes and verse structure are excluded.
|
|
19
|
+
- Only period maps to Semaron's pause; recognized native signs require explicit
|
|
20
|
+
native presentation and a face that draws them, while mappings never broaden
|
|
21
|
+
Roman inventories.
|
|
22
|
+
- Unassociated external tags such as `en` and `zxx` retain ordinary behavior;
|
|
23
|
+
standalone `package-build markdown` does not load language inventories.
|
|
24
|
+
|
|
3
25
|
## 24.1.1
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/docs/api.md
CHANGED
|
@@ -1384,17 +1384,29 @@ Linting a content tree's **addresses** — the rules every package's notes are a
|
|
|
1384
1384
|
|
|
1385
1385
|
The charset authored content is held to, so a book can choose its face. The packs and the website render in whatever font the reader's browser or Foundry supplies, and a glyph nobody has is somebody else's problem. **A book is not that.** A PDF embeds the faces it sets, so every character in the corpus is a claim on the book's typeface — and the claim is silent, which is what makes it expensive.
|
|
1386
1386
|
|
|
1387
|
-
| Export
|
|
1388
|
-
|
|
|
1389
|
-
| `isLetterTier`
|
|
1390
|
-
| `TYPOGRAPHY`
|
|
1391
|
-
| `NOTATION`
|
|
1392
|
-
| `
|
|
1393
|
-
| `
|
|
1394
|
-
| `
|
|
1395
|
-
| `
|
|
1396
|
-
| `
|
|
1397
|
-
| `
|
|
1387
|
+
| Export | Signature | Returns | Use it when |
|
|
1388
|
+
| -------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
|
|
1389
|
+
| `isLetterTier` | `function isLetterTier(cp)` | {boolean} Whether Tier 1 admits it. | Tier 1 — the letters, and the two whitespace characters a file is made of. |
|
|
1390
|
+
| `TYPOGRAPHY` | `const TYPOGRAPHY` | — | Tier 2 — typography, enumerated one codepoint at a time. |
|
|
1391
|
+
| `NOTATION` | `const NOTATION` | — | Tier 3 — the notation the rules and price tables are written in. |
|
|
1392
|
+
| `isSettingScriptCodePoint` | `isSettingScriptCodePoint(cp)` | `boolean` | recognizing native setting-script input before the selected face checks the individual glyph |
|
|
1393
|
+
| `isAllowedCodePoint` | `function isAllowedCodePoint(cp)` | {boolean} Whether it is allowed outside a code fence. | Whether the charset admits a code point anywhere in a note. |
|
|
1394
|
+
| `isDiagramCodePoint` | `function isDiagramCodePoint(cp)` | {boolean} Whether a fence may carry it. | Whether a code point is diagram furniture, admitted inside a fence only. |
|
|
1395
|
+
| `refusalFor` | `function refusalFor(cp)` | {string} A clause naming what it is and what to do instead. | The reason a code point is refused. |
|
|
1396
|
+
| `decomposedRuns` | `function decomposedRuns(text)` | {Array<{sequence: string, composed: string, index: number}>} Each offending run, in the order it appears. | Every non-NFC run in a string, with the composed form it should have been. |
|
|
1397
|
+
| `checkText` | `function checkText(text, file)` | {Array<{file: string, line: number, column: number, severity: "warning", message: string}>} What is wrong, in file order. | Check one file's text against the charset and the normalization rule. |
|
|
1398
|
+
| `lintContentCharset` | `function lintContentCharset(contentBase,` | `{{findings: Array<{file: string, line: number, column: number, severity: "warning", message: string}>, files: number}}` The findings, and how many files produced them. | Walk a content tree and check every authored file in it. |
|
|
1399
|
+
|
|
1400
|
+
### `engine.romanCharacters`
|
|
1401
|
+
|
|
1402
|
+
Exact permitted Roman source characters for configured languages, before script
|
|
1403
|
+
mapping. Inventories come from `data.roman` on each language's own note.
|
|
1404
|
+
|
|
1405
|
+
| Export | Signature | Returns | Use it when |
|
|
1406
|
+
| ------------------------ | --------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------- |
|
|
1407
|
+
| `romanInventoryProblems` | `romanInventoryProblems(value)` | `string[]` | checking NFC, repeated characters and forbidden whitespace or controls in a literal inventory |
|
|
1408
|
+
| `romanInventoriesOf` | `romanInventoriesOf(records, languages)` | `Map<string, RomanInventory>` | looking up a configured language's own inventory by name or BCP 47 tag |
|
|
1409
|
+
| `romanTextProblems` | `romanTextProblems(text, inventory, native?)` | `{offset, message}[]` | validating visible unescaped source with exact offsets and an optional native-sign predicate |
|
|
1398
1410
|
|
|
1399
1411
|
### `engine.pathCharset`
|
|
1400
1412
|
|
|
@@ -644,6 +644,14 @@ has an error is set exactly as written.
|
|
|
644
644
|
|
|
645
645
|
## Marking language, font and mapping
|
|
646
646
|
|
|
647
|
+
Each configured language note declares `data.roman`, a literal NFC string of
|
|
648
|
+
permitted source characters. Include capitals, accents, digits and punctuation
|
|
649
|
+
explicitly; an empty string declares no written characters. Spaces and line
|
|
650
|
+
breaks are structural. `package-build lint` checks Roman source before mapping,
|
|
651
|
+
including language-only runs and visible link labels. The configured name and
|
|
652
|
+
its BCP 47 tag select the same inventory; external tags without a configured
|
|
653
|
+
language note keep their ordinary behavior.
|
|
654
|
+
|
|
647
655
|
`lang=` identifies the language. It preserves the authored spelling and the
|
|
648
656
|
surrounding prose font. Add **`.nativefont`** to request native script:
|
|
649
657
|
|
package/docs/commands.md
CHANGED
|
@@ -2215,6 +2215,14 @@ dependencies; exact Item UUID validation remains part of compilation. Reads
|
|
|
2215
2215
|
the content tree named by `root`, defaulting to
|
|
2216
2216
|
`paths.content`; writes nothing.
|
|
2217
2217
|
|
|
2218
|
+
Every configured language note declares `data.roman`, its exact NFC Roman
|
|
2219
|
+
source characters. Lint checks language-tagged text before mapping, using the
|
|
2220
|
+
same inventory for a configured name and its BCP 47 tag. Visible linked labels
|
|
2221
|
+
are checked; destinations, attributes and verse structure are excluded. An
|
|
2222
|
+
explicit empty inventory declares no written characters. External tags without
|
|
2223
|
+
a language-note association retain their ordinary behavior. Standalone
|
|
2224
|
+
`package-build markdown` does not load these inventories.
|
|
2225
|
+
|
|
2218
2226
|
Name marking is advisory: lint checks explicit `.name` spans and authored link
|
|
2219
2227
|
labels against the [name-marking
|
|
2220
2228
|
conventions](authoring/links-and-markup.md#marking-names-in-prose). Plain prose
|
|
@@ -158,6 +158,7 @@ language note says which, as one map and one face:
|
|
|
158
158
|
|
|
159
159
|
```yaml
|
|
160
160
|
data:
|
|
161
|
+
roman: "ABDEFGHIKLMNOPRSTUVWYZabdefghiklmnoprstuvwyzÁÉÍÓÚÝáéíóúýËëÀà0123456789."
|
|
161
162
|
writtenIn: { map: semaron, font: semaron-kuriale }
|
|
162
163
|
```
|
|
163
164
|
|
|
@@ -300,7 +301,6 @@ data:
|
|
|
300
301
|
- { from: "y", to: "", before: vowel } # U+F0245
|
|
301
302
|
- ["y", ""] # U+F0237
|
|
302
303
|
- [".", ""] # U+F0268
|
|
303
|
-
- [",", ""] # U+F0268
|
|
304
304
|
fonts: [{ font: semaron-kuriale }]
|
|
305
305
|
---
|
|
306
306
|
|
|
@@ -339,8 +339,8 @@ them, none included. A mapping that drops a language's vowels writes `[a, ""]`.
|
|
|
339
339
|
`y` is the consonant letter before a vowel and the vowel `i` elsewhere.
|
|
340
340
|
- **Capitals and marks need rows of their own.** A row matches the character as
|
|
341
341
|
written, so `L` and `l` are two rows, and a script with no capitals gives both
|
|
342
|
-
the same code point.
|
|
343
|
-
|
|
342
|
+
the same code point. A period is a character too and needs its own row.
|
|
343
|
+
A language's `data.roman` decides which source characters may appear.
|
|
344
344
|
- **A character no row matches passes through unchanged,** and the face-coverage
|
|
345
345
|
check then reports it. A mapping for a face that draws nothing on roman code
|
|
346
346
|
points lists every letter the spelling uses.
|
|
@@ -381,14 +381,19 @@ A row with a fault is left out of the map and the rest apply.
|
|
|
381
381
|
|
|
382
382
|
## How a mapping treats punctuation
|
|
383
383
|
|
|
384
|
+
The language note's `data.roman` lists its exact permitted Roman source
|
|
385
|
+
characters. Include capitals, accents, digits and punctuation explicitly;
|
|
386
|
+
`package-build lint` checks them before mapping. A map cannot admit a character
|
|
387
|
+
that the language's inventory excludes. There are no automatic punctuation
|
|
388
|
+
substitutions.
|
|
389
|
+
|
|
384
390
|
The toolchain is used with one convention for marks: **mood, questions and
|
|
385
391
|
quotation are carried by words, not marks,** unless a language's note rules
|
|
386
392
|
otherwise. A mapping therefore lists the marks its script has and nothing more.
|
|
387
|
-
In the sample,
|
|
393
|
+
In the sample, only a period writes the script's pause point:
|
|
388
394
|
|
|
389
395
|
```yaml
|
|
390
396
|
- [".", "\U000F0268"] # U+F0268
|
|
391
|
-
- [",", "\U000F0268"] # U+F0268
|
|
392
397
|
```
|
|
393
398
|
|
|
394
399
|
(Formatting rewrites the escapes, as above.) A character with no row passes
|
|
@@ -398,10 +403,11 @@ mapped run is an error that names the mark and the mapping:
|
|
|
398
403
|
> `` `?` (U+003F) is not drawn by the font `semaron-kuriale`, and the map `semaron` has no row for it ``
|
|
399
404
|
|
|
400
405
|
The remedy is to write the question or the quotation in words, as the language
|
|
401
|
-
does, and leave the mark out of the run. A language whose note
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
406
|
+
does, and leave the mark out of the run. A language whose note permits a mark
|
|
407
|
+
includes it in `data.roman`; its script needs a row for that mark as well.
|
|
408
|
+
Vylari permits period alone as punctuation. Its lexical catch is written with
|
|
409
|
+
a grave-accented vowel, as in `kàralzon`, and that vowel needs its own mapping
|
|
410
|
+
row; an apostrophe is not another spelling of it.
|
|
405
411
|
|
|
406
412
|
## The dictionary first
|
|
407
413
|
|
|
@@ -793,7 +799,7 @@ paragraphs: indentation, line breaks and spans survive formatting untouched.
|
|
|
793
799
|
|
|
794
800
|
````markdown
|
|
795
801
|
```verse {lang=vyl font=semaron-kuriale map=semaron .nativefont}
|
|
796
|
-
Lokazor lonak
|
|
802
|
+
Lokazor lonak.
|
|
797
803
|
lokamen zaron.
|
|
798
804
|
```
|
|
799
805
|
````
|
|
@@ -1114,6 +1120,7 @@ name: { full: Vylari Language }
|
|
|
1114
1120
|
type: skill
|
|
1115
1121
|
subType: language
|
|
1116
1122
|
data:
|
|
1123
|
+
roman: "ABDEFGHIKLMNOPRSTUVWYZabdefghiklmnoprstuvwyzÁÉÍÓÚÝáéíóúýËëÀà0123456789."
|
|
1117
1124
|
writtenIn: { map: semaron, font: semaron-kuriale }
|
|
1118
1125
|
core: true
|
|
1119
1126
|
fields:
|
|
@@ -1177,7 +1184,7 @@ The lintel over the door of the oath road is cut with a couplet, and the
|
|
|
1177
1184
|
stonecutter set the couplet in the script of the court, so that every traveller
|
|
1178
1185
|
could read it as the court reads it.
|
|
1179
1186
|
|
|
1180
|
-
[Lokazor lonak thiruthrimar
|
|
1187
|
+
[Lokazor lonak thiruthrimar. lokamen zaron.]{lang=vyl font=semaron-kuriale map=semaron .nativefont}
|
|
1181
1188
|
```
|
|
1182
1189
|
|
|
1183
1190
|
Linting reports the unknown word, at its first character:
|
|
@@ -1198,6 +1205,7 @@ name: { full: Vylari Language }
|
|
|
1198
1205
|
type: skill
|
|
1199
1206
|
subType: language
|
|
1200
1207
|
data:
|
|
1208
|
+
roman: "ABDEFGHIKLMNOPRSTUVWYZabdefghiklmnoprstuvwyzÁÉÍÓÚÝáéíóúýËëÀà0123456789."
|
|
1201
1209
|
writtenIn: { map: semaron, font: semaron-kuriale }
|
|
1202
1210
|
core: true
|
|
1203
1211
|
fields:
|
|
@@ -1251,7 +1259,7 @@ The lint is now clean, and the conversion can be read. `package-build transliter
|
|
|
1251
1259
|
prints the code points the build writes for the couplet:
|
|
1252
1260
|
|
|
1253
1261
|
```console
|
|
1254
|
-
$ package-build transliterate semaron --lang vyl --hex "Lokazor lonak
|
|
1262
|
+
$ package-build transliterate semaron --lang vyl --hex "Lokazor lonak. lokamen zaron."
|
|
1255
1263
|
U+F020A U+F023D U+F0239 U+F0230 U+F0246 U+F023D U+F023F U+0020 U+F023A U+F023D U+F023C U+F0230 U+F0239 U+F0268 U+0020 U+F023A U+F023D U+F0239 U+F0230 U+F023B U+F0233 U+F023C U+0020 U+F0246 U+F0248 U+F023F U+F023D U+F023C U+F0268
|
|
1256
1264
|
```
|
|
1257
1265
|
|
|
@@ -1264,8 +1272,8 @@ The site sets the run as
|
|
|
1264
1272
|
```
|
|
1265
1273
|
|
|
1266
1274
|
where the characters are the code points above: `Lokazor` begins with the
|
|
1267
|
-
capital `L` of the rows, `zaron` is the exception's long vowel, and
|
|
1268
|
-
|
|
1275
|
+
capital `L` of the rows, `zaron` is the exception's long vowel, and each period
|
|
1276
|
+
writes the script's pause point.
|
|
1269
1277
|
|
|
1270
1278
|
A poem in columns and a poem with its metre checked sit on one note. Both parse
|
|
1271
1279
|
with no finding: the first sets native Vylari in column one and English over Roman
|
|
@@ -1341,7 +1349,7 @@ The door is marked with the same words as the lintel. Although Semaron's
|
|
|
1341
1349
|
convention is rubric, this Vylari span uses no Markdown emphasis: spans with a
|
|
1342
1350
|
non-English language cannot contain bold or italic markup.
|
|
1343
1351
|
|
|
1344
|
-
[Lokazor lonak
|
|
1352
|
+
[Lokazor lonak. zaron.]{lang=vyl font=semaron-kuriale map=semaron .nativefont}
|
|
1345
1353
|
```
|
|
1346
1354
|
|
|
1347
1355
|
Finally, the core report lists what the language still lacks:
|
|
@@ -2002,6 +2002,30 @@ in that column are grey; each row resets that choice. `numstep` counts logical
|
|
|
2002
2002
|
rows, and `***` separates stanzas. Language and native script choices remain
|
|
2003
2003
|
on the authored inline runs, independently for every reading.
|
|
2004
2004
|
|
|
2005
|
+
**A language note declares its Roman source inventory in `data.roman`.** It is
|
|
2006
|
+
one literal NFC string containing each permitted character once, including every
|
|
2007
|
+
capital, accent, digit and punctuation mark the language uses. For example,
|
|
2008
|
+
`roman: "kaKàÀ.1"` permits exactly those seven characters. There is no range
|
|
2009
|
+
syntax, automatic case expansion, or inventory inherited from a parent language.
|
|
2010
|
+
An empty string explicitly declares that the language has no written characters.
|
|
2011
|
+
Spaces, tabs and line breaks are structural and are not listed in the inventory.
|
|
2012
|
+
A missing declaration on a configured language note is an error even when no
|
|
2013
|
+
passage uses the language. A directly typed native sign is separate from
|
|
2014
|
+
Roman input only when the run explicitly selects `.nativefont`, the character
|
|
2015
|
+
belongs to a recognized setting-script encoding, and the selected face draws it.
|
|
2016
|
+
A font's ability to draw Roman punctuation does not admit that punctuation.
|
|
2017
|
+
|
|
2018
|
+
`package-build lint` checks the visible source of language-tagged spans, divs,
|
|
2019
|
+
verse and scroll fences against that inventory before any map or native-word
|
|
2020
|
+
replacement can hide a character. Both the configured name and its BCP 47 tag
|
|
2021
|
+
select the same inventory. A nested language selects its own; link destinations,
|
|
2022
|
+
attributes, column numbers, indentation periods and `***` stanza separators are
|
|
2023
|
+
structure. Escaping a character does not admit it. Source spelling must be NFC;
|
|
2024
|
+
errors retain the original file position. Unassociated external tags such as
|
|
2025
|
+
`en` and `zxx` keep their ordinary behavior. Standalone `package-build markdown`
|
|
2026
|
+
checks markup without loading language inventories. Structured frontmatter words
|
|
2027
|
+
are separate from prose runs.
|
|
2028
|
+
|
|
2005
2029
|
**A face draws the code points its `fonts.json` entry lists in `codepoints`,
|
|
2006
2030
|
and whitespace.** After the map, any other character in a run set in a face is
|
|
2007
2031
|
an error, located at the source character it came from; through a map, at the
|
|
@@ -6095,6 +6119,7 @@ SoHL sets `system.quantity` to `1` for each compendium item.
|
|
|
6095
6119
|
| `family` | `Address` | `language` only: the parent language, naming a skill note with `subType: language`; this language reads the parent's words as its own — see [a language's words](#a-languages-words) |
|
|
6096
6120
|
| `abbreviation` | `string` | `language` only: the tag a dictionary prints for it, as `Prov.`; its full name where unstated — see [dictionaries and glossaries](#dictionaries-and-glossaries) |
|
|
6097
6121
|
| `core` | `boolean` | `language` only: `true` where the language must cover the [core vocabulary](#the-core-vocabulary) |
|
|
6122
|
+
| `roman` | literal NFC string, may be empty | Exact permitted Roman source characters, including both cases, accents, digits and punctuation; required on each configured language note. No family inheritance or range syntax. |
|
|
6098
6123
|
| `grammar` | `{ plural?, verbs?, agreement?, participles? }` | Optional noun plurals, one verb pattern, agreement and participles; supplied shapes only are checked |
|
|
6099
6124
|
| `grammar.plural` | `{ ending, becomes }[]` | Literal suffix replacements in authored order; the first match wins |
|
|
6100
6125
|
| `grammar.plural[].ending` | `string`, required, may be empty | Bare suffix to replace; empty matches every noun |
|
|
@@ -468,6 +468,7 @@ 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. |
|
|
471
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. |
|
|
472
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. |
|
|
473
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. |
|
|
@@ -176,6 +176,17 @@ const SCRIPT_BLOCKS = Object.freeze([
|
|
|
176
176
|
[0x13430, 0x1345f], // the Egyptian format controls Zethu uses
|
|
177
177
|
]);
|
|
178
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Whether a code point belongs to a declared setting-script encoding block.
|
|
181
|
+
* This identifies native input independently of a language's Roman inventory;
|
|
182
|
+
* a selected native face must still draw the individual character.
|
|
183
|
+
* @param {number} cp - A Unicode code point.
|
|
184
|
+
* @returns {boolean}
|
|
185
|
+
*/
|
|
186
|
+
export function isSettingScriptCodePoint(cp) {
|
|
187
|
+
return SCRIPT_BLOCKS.some(([from, to]) => cp >= from && cp <= to);
|
|
188
|
+
}
|
|
189
|
+
|
|
179
190
|
/**
|
|
180
191
|
* Whether the charset admits a code point anywhere in a note.
|
|
181
192
|
*
|
|
@@ -184,10 +195,7 @@ const SCRIPT_BLOCKS = Object.freeze([
|
|
|
184
195
|
*/
|
|
185
196
|
export function isAllowedCodePoint(cp) {
|
|
186
197
|
return (
|
|
187
|
-
isLetterTier(cp) ||
|
|
188
|
-
TYPOGRAPHY.has(cp) ||
|
|
189
|
-
NOTATION.has(cp) ||
|
|
190
|
-
SCRIPT_BLOCKS.some(([from, to]) => cp >= from && cp <= to)
|
|
198
|
+
isLetterTier(cp) || TYPOGRAPHY.has(cp) || NOTATION.has(cp) || isSettingScriptCodePoint(cp)
|
|
191
199
|
);
|
|
192
200
|
}
|
|
193
201
|
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
import MarkdownIt from "markdown-it";
|
|
93
93
|
|
|
94
94
|
import { scanBlocks, BLOCK_CONTAINERS } from "./content-blocks.mjs";
|
|
95
|
-
import { scanVerse, hasColumns, verseLine, verseGroups } from "./content-verse.mjs";
|
|
95
|
+
import { scanVerse, hasColumns, verseLine, verseGroups, STANZA_BREAK } from "./content-verse.mjs";
|
|
96
96
|
import { scanSpans } from "./content-spans.mjs";
|
|
97
97
|
import { scanScrolls, scrollParagraphs } from "./content-scroll.mjs";
|
|
98
98
|
import { directionsFromRecords } from "./script-direction.mjs";
|
|
@@ -113,7 +113,9 @@ import { unknownWordMessage, unknownWords, wordBooksFromRecords } from "./run-wo
|
|
|
113
113
|
import { metreBooksFromRecords, metreFindings, metresFromRecords } from "./verse-metre.mjs";
|
|
114
114
|
import { applyMap, mapsFromRecords } from "./text-maps.mjs";
|
|
115
115
|
import { writtenInOf } from "./written-in.mjs";
|
|
116
|
-
import {
|
|
116
|
+
import { romanInventoriesOf, romanTextProblems } from "./roman-characters.mjs";
|
|
117
|
+
import { isSettingScriptCodePoint } from "./content-charset.mjs";
|
|
118
|
+
import { WIKILINK } from "./wikilink-syntax.mjs";
|
|
117
119
|
import { matchAllOutsideCode } from "./code-fences.mjs";
|
|
118
120
|
import { parseExtensionAttributes } from "./extension-attributes.mjs";
|
|
119
121
|
|
|
@@ -159,6 +161,8 @@ const CONFIG = "package-build.config.yaml";
|
|
|
159
161
|
* @property {ReadonlyMap<string, import("./verse-metre.mjs").MetreBook>} [metreBooks] -
|
|
160
162
|
* What each configured language's verse is counted by, from
|
|
161
163
|
* {@link module:engine/verse-metre.metreBooksFromRecords}.
|
|
164
|
+
* @property {ReadonlyMap<string, import("./roman-characters.mjs").RomanInventory>} [romanInventories] -
|
|
165
|
+
* Exact Roman source inventories by configured alias and BCP 47 tag.
|
|
162
166
|
* @property {ReadonlyMap<string, import("./script-emphasis.mjs").Emphasis>} [emphases] -
|
|
163
167
|
* Each face's emphasis conventions, from the script notes, by
|
|
164
168
|
* {@link module:engine/script-emphasis.emphasesFromRecords}.
|
|
@@ -241,6 +245,7 @@ export function runContext(config, records) {
|
|
|
241
245
|
...base,
|
|
242
246
|
maps,
|
|
243
247
|
writtenIn: writtenInOf(languageRecords, maps),
|
|
248
|
+
romanInventories: romanInventoriesOf(languageRecords, config.languages),
|
|
244
249
|
lexicons: lexiconsFromRecords(records, config),
|
|
245
250
|
wordBooks: wordBooksFromRecords(records, config),
|
|
246
251
|
metres: metresFromRecords(records),
|
|
@@ -1176,12 +1181,11 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1176
1181
|
);
|
|
1177
1182
|
};
|
|
1178
1183
|
|
|
1184
|
+
const rawReferences = [];
|
|
1179
1185
|
// Raw wikilinks belong to the resolver, not the span rewriter. Validate
|
|
1180
1186
|
// their own control attributes with the same grammar before resolution,
|
|
1181
1187
|
// without mapping a destination or consuming the trailing attributes.
|
|
1182
1188
|
for (const match of matchAllOutsideCode(text, new RegExp(WIKILINK.source, "g"))) {
|
|
1183
|
-
const parsed = parseWikilink(match[1], match[0]);
|
|
1184
|
-
if (!parsed.attributes) continue;
|
|
1185
1189
|
const close = match[0].indexOf("]]") + 1;
|
|
1186
1190
|
const from = match.index + close + 2;
|
|
1187
1191
|
const attrs = parseExtensionAttributes(match[0].slice(close + 2, -1), { positions: true });
|
|
@@ -1200,6 +1204,19 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1200
1204
|
};
|
|
1201
1205
|
const unchecked = errors.length;
|
|
1202
1206
|
checkOwn(construct);
|
|
1207
|
+
const bar = match[1].indexOf("|");
|
|
1208
|
+
if (bar >= 0 && errors.length === unchecked) {
|
|
1209
|
+
const ancestors = around(construct);
|
|
1210
|
+
const inherited = ancestors.filter((parent) => parent.keys.lang !== undefined).at(-1)
|
|
1211
|
+
?.keys.lang;
|
|
1212
|
+
rawReferences.push({
|
|
1213
|
+
lang: construct.keys.lang ?? inherited,
|
|
1214
|
+
from: match.index + 2 + bar + 1,
|
|
1215
|
+
to: match.index + close - 1,
|
|
1216
|
+
where: construct.where,
|
|
1217
|
+
construct,
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1203
1220
|
if (errors.length !== unchecked || !native(construct)) continue;
|
|
1204
1221
|
const keys = { ...construct.keys };
|
|
1205
1222
|
nativeDefaults(construct, keys);
|
|
@@ -1215,6 +1232,9 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1215
1232
|
|
|
1216
1233
|
const verseMarkers = new Map();
|
|
1217
1234
|
for (const poem of scanVerse(text).blocks) {
|
|
1235
|
+
for (let line = poem.start + 1; line < poem.end; line++)
|
|
1236
|
+
if (STANZA_BREAK.test(lines[line].trim()))
|
|
1237
|
+
verseMarkers.set(starts[line], starts[line] + lines[line].length);
|
|
1218
1238
|
if (hasColumns(poem)) {
|
|
1219
1239
|
for (const group of verseGroups(poem, lines)) {
|
|
1220
1240
|
if (group.stanzaBreak) continue;
|
|
@@ -1401,6 +1421,47 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1401
1421
|
}
|
|
1402
1422
|
};
|
|
1403
1423
|
|
|
1424
|
+
/** The configured source inventory for either an alias or its BCP 47 tag. */
|
|
1425
|
+
const inventoryOf = (lang) =>
|
|
1426
|
+
lang === undefined ? undefined : (
|
|
1427
|
+
(ctx.romanInventories?.get(lang) ?? ctx.romanInventories?.get(lang.toLowerCase()))
|
|
1428
|
+
);
|
|
1429
|
+
|
|
1430
|
+
/** Check visible source before mapping or lexicon-native replacement. */
|
|
1431
|
+
const roman = (parts, detail) => {
|
|
1432
|
+
const inventory = inventoryOf(detail.run.lang);
|
|
1433
|
+
if (!inventory) return;
|
|
1434
|
+
if (!inventory.characters) {
|
|
1435
|
+
report(
|
|
1436
|
+
{ line: detail.run.line, column: detail.run.column },
|
|
1437
|
+
`the language note for \`${inventory.language}\` must declare \`data.roman\``,
|
|
1438
|
+
);
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
for (const [from, to] of parts) {
|
|
1442
|
+
let base = from;
|
|
1443
|
+
for (const line of text.slice(from, to).split("\n")) {
|
|
1444
|
+
// A terminal unescaped backslash is a Markdown line break.
|
|
1445
|
+
const breakMark =
|
|
1446
|
+
/(?<!\\)(?:\\\\)*\\$/.test(line) && text[base + line.length] === "\n";
|
|
1447
|
+
const plain = unescapeRoman(breakMark ? line.slice(0, -1) : line);
|
|
1448
|
+
for (const problem of romanTextProblems(plain.text, inventory, (char) => {
|
|
1449
|
+
const point = char.codePointAt(0);
|
|
1450
|
+
return (
|
|
1451
|
+
detail.run.nativefont === true &&
|
|
1452
|
+
isSettingScriptCodePoint(point) &&
|
|
1453
|
+
detail.face?.draws.has(point)
|
|
1454
|
+
);
|
|
1455
|
+
}))
|
|
1456
|
+
report(
|
|
1457
|
+
positionOf(starts, base + (plain.offsets[problem.offset] ?? 0)),
|
|
1458
|
+
problem.message,
|
|
1459
|
+
);
|
|
1460
|
+
base += line.length + 1;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
1464
|
+
|
|
1404
1465
|
/**
|
|
1405
1466
|
* Report each word of a mapped run that is not a word of its language, once
|
|
1406
1467
|
* per run, at its first character.
|
|
@@ -1515,6 +1576,7 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1515
1576
|
};
|
|
1516
1577
|
runs.push(run);
|
|
1517
1578
|
const plain =
|
|
1579
|
+
inventoryOf(lang) !== undefined ||
|
|
1518
1580
|
font !== undefined ||
|
|
1519
1581
|
map !== undefined ||
|
|
1520
1582
|
(lang !== undefined &&
|
|
@@ -1577,15 +1639,18 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1577
1639
|
}
|
|
1578
1640
|
if (plain && errors.length === before)
|
|
1579
1641
|
plainInline(inline, from, marking, detail, detail.holes);
|
|
1580
|
-
if (
|
|
1642
|
+
if (errors.length === before) {
|
|
1581
1643
|
const parts = outsideDelimiters(
|
|
1582
1644
|
outsideHoles(from, to, detail.holes),
|
|
1583
1645
|
detail.emphasis,
|
|
1584
1646
|
);
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1647
|
+
roman(parts, detail);
|
|
1648
|
+
if (named) {
|
|
1649
|
+
lexical(parts, detail);
|
|
1650
|
+
capitals(parts, detail);
|
|
1651
|
+
writings(parts, detail);
|
|
1652
|
+
covered(parts, detail);
|
|
1653
|
+
}
|
|
1589
1654
|
}
|
|
1590
1655
|
continue;
|
|
1591
1656
|
}
|
|
@@ -1647,15 +1712,30 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1647
1712
|
for (let line = first; line < last; line++)
|
|
1648
1713
|
plainInline(lines[line], starts[line], marking, detail, detail.holes);
|
|
1649
1714
|
}
|
|
1650
|
-
if (
|
|
1715
|
+
if (errors.length === before) {
|
|
1651
1716
|
const parts = outsideDelimiters(outsideHoles(from, to, detail.holes), detail.emphasis);
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1717
|
+
roman(parts, detail);
|
|
1718
|
+
if (named) {
|
|
1719
|
+
lexical(parts, detail);
|
|
1720
|
+
capitals(parts, detail);
|
|
1721
|
+
writings(parts, detail);
|
|
1722
|
+
covered(parts, detail);
|
|
1723
|
+
}
|
|
1656
1724
|
}
|
|
1657
1725
|
}
|
|
1658
1726
|
|
|
1727
|
+
for (const reference of rawReferences) {
|
|
1728
|
+
const font = reference.construct.keys.font ?? ctx.writtenIn?.get(reference.lang)?.font;
|
|
1729
|
+
roman([[reference.from, reference.to]], {
|
|
1730
|
+
run: {
|
|
1731
|
+
lang: reference.lang,
|
|
1732
|
+
nativefont: native(reference.construct),
|
|
1733
|
+
...reference.where,
|
|
1734
|
+
},
|
|
1735
|
+
face: font === undefined ? undefined : faces.get(font),
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1659
1739
|
const order = (a, b) => a.line - b.line || a.column - b.column;
|
|
1660
1740
|
runs.sort(order);
|
|
1661
1741
|
details.sort((a, b) => order(a.run, b.run));
|
package/engine/index.mjs
CHANGED
|
@@ -227,6 +227,9 @@ export * as contentLint from "./content-lint.mjs";
|
|
|
227
227
|
|
|
228
228
|
/** The charset authored content is held to, so a book can choose its face. */
|
|
229
229
|
export * as contentCharset from "./content-charset.mjs";
|
|
230
|
+
|
|
231
|
+
/** Exact permitted Roman source characters for configured languages. */
|
|
232
|
+
export * as romanCharacters from "./roman-characters.mjs";
|
|
230
233
|
export * as pathCharset from "./path-charset.mjs";
|
|
231
234
|
|
|
232
235
|
/** Naming an interface icon in a note, without drawing one there. */
|
package/engine/language-data.mjs
CHANGED
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
import { LANGUAGE_SUBTYPE } from "./word-terms.mjs";
|
|
74
74
|
import { WORD_CLASS_BASES } from "./word-classes.mjs";
|
|
75
75
|
import { nearest } from "./near-miss.mjs";
|
|
76
|
+
import { romanInventoryProblems } from "./roman-characters.mjs";
|
|
76
77
|
|
|
77
78
|
/** The bases a frame's `class` names. */
|
|
78
79
|
const CLASS_BASES = new Set(WORD_CLASS_BASES.map((item) => item.token));
|
|
@@ -614,7 +615,9 @@ export function checkLanguageData(note, { index, types } = {}) {
|
|
|
614
615
|
),
|
|
615
616
|
);
|
|
616
617
|
|
|
617
|
-
const findings =
|
|
618
|
+
const findings = romanInventoryProblems(data.roman).map((message) =>
|
|
619
|
+
finding(["data", "roman"], "error", message),
|
|
620
|
+
);
|
|
618
621
|
|
|
619
622
|
// Only the shape of a supplied declaration: no word is compared with it.
|
|
620
623
|
const grammar = isMap(data.grammar) ? data.grammar : {};
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
/** The `data` keys a language note states its sounds, names and history in. */
|
|
32
32
|
export const LANGUAGE_DATA_KEYS = Object.freeze([
|
|
33
|
+
"roman",
|
|
33
34
|
"tongues",
|
|
34
35
|
"register",
|
|
35
36
|
"retired",
|
|
@@ -1889,6 +1890,15 @@ const GRAMMAR_FIELDS = Object.freeze([
|
|
|
1889
1890
|
* @type {readonly object[]}
|
|
1890
1891
|
*/
|
|
1891
1892
|
export const LANGUAGE_DATA_FIELDS = Object.freeze([
|
|
1893
|
+
Object.freeze({
|
|
1894
|
+
name: "roman",
|
|
1895
|
+
kind: "string",
|
|
1896
|
+
shape: "a literal NFC string of permitted characters, including case, accents, digits and punctuation",
|
|
1897
|
+
describe:
|
|
1898
|
+
"The exact Roman source characters this language permits, each once. No ranges, case " +
|
|
1899
|
+
"expansion or inherited inventory; spaces and line breaks are structural. An empty " +
|
|
1900
|
+
"string declares no written characters. Required on each configured language note.",
|
|
1901
|
+
}),
|
|
1892
1902
|
Object.freeze({
|
|
1893
1903
|
name: "grammar",
|
|
1894
1904
|
kind: "map",
|
package/engine/map-lexicon.mjs
CHANGED
|
@@ -330,7 +330,18 @@ export function languageNoteFindings(config, records) {
|
|
|
330
330
|
}
|
|
331
331
|
const findings = [];
|
|
332
332
|
for (const [name, { record }] of languageNotes(languages, contentPackage, records)) {
|
|
333
|
-
if (record)
|
|
333
|
+
if (record) {
|
|
334
|
+
if (typeof record.data?.roman !== "string")
|
|
335
|
+
findings.push({
|
|
336
|
+
file: configFile ?? CONFIG,
|
|
337
|
+
...(text ?
|
|
338
|
+
positionOfYamlPath(text, ["languages", name, "note"], { key: true })
|
|
339
|
+
: {}),
|
|
340
|
+
severity: "error",
|
|
341
|
+
message: `the language note named by \`languages.${name}.note\` must declare \`data.roman\``,
|
|
342
|
+
});
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
334
345
|
findings.push({
|
|
335
346
|
file: configFile ?? CONFIG,
|
|
336
347
|
...(text ? positionOfYamlPath(text, ["languages", name, "note"], { key: true }) : {}),
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** A language's exact Roman source characters, before any script conversion. */
|
|
15
|
+
|
|
16
|
+
/** Unicode graphemes keep every canonically composing sequence together. */
|
|
17
|
+
const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
18
|
+
|
|
19
|
+
/** A code point as the author sees it in a finding. */
|
|
20
|
+
const codeName = (char) => `U+${char.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Semantic problems in a supplied inventory. Shape belongs to the vocabulary.
|
|
24
|
+
* An empty string explicitly declares no written characters.
|
|
25
|
+
* @param {unknown} value - The authored `data.roman`.
|
|
26
|
+
* @returns {string[]} Findings at that field.
|
|
27
|
+
*/
|
|
28
|
+
export function romanInventoryProblems(value) {
|
|
29
|
+
if (typeof value !== "string") return [];
|
|
30
|
+
const problems = [];
|
|
31
|
+
if (value !== value.normalize("NFC"))
|
|
32
|
+
problems.push(
|
|
33
|
+
"`data.roman` must use NFC spelling; write precomposed accented letters where available",
|
|
34
|
+
);
|
|
35
|
+
if (/\s/u.test(value))
|
|
36
|
+
problems.push(
|
|
37
|
+
"`data.roman` lists characters without whitespace; spaces and line breaks are structural",
|
|
38
|
+
);
|
|
39
|
+
if (/[\p{Cc}\p{Cf}\p{Cs}]/u.test(value))
|
|
40
|
+
problems.push("`data.roman` contains a control, format or unpaired surrogate character");
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
const repeated = new Set();
|
|
43
|
+
for (const char of value) {
|
|
44
|
+
if (seen.has(char)) repeated.add(char);
|
|
45
|
+
seen.add(char);
|
|
46
|
+
}
|
|
47
|
+
if (repeated.size)
|
|
48
|
+
problems.push(
|
|
49
|
+
`\`data.roman\` repeats ${[...repeated].map((char) => `\`${char}\` (${codeName(char)})`).join(", ")}; list each character once`,
|
|
50
|
+
);
|
|
51
|
+
return problems;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {object} RomanInventory
|
|
56
|
+
* @property {string} language - The configured language name.
|
|
57
|
+
* @property {ReadonlySet<string>|undefined} characters - Exact code points, absent for a missing declaration.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Each configured language note's own inventory, by alias and BCP 47 tag.
|
|
62
|
+
* Language-family ancestry does not supply an inventory. Unassociated external
|
|
63
|
+
* tags have no entry and keep their ordinary behavior.
|
|
64
|
+
* @param {ReadonlyMap<string, Record<string, any>>} records - Configured language notes.
|
|
65
|
+
* @param {Readonly<Record<string, {tag: string}>>} languages - Language configuration.
|
|
66
|
+
* @returns {Map<string, RomanInventory>}
|
|
67
|
+
*/
|
|
68
|
+
export function romanInventoriesOf(records, languages = {}) {
|
|
69
|
+
const out = new Map();
|
|
70
|
+
for (const [language, record] of records) {
|
|
71
|
+
const value = record.data?.roman;
|
|
72
|
+
const inventory = Object.freeze({
|
|
73
|
+
language,
|
|
74
|
+
characters: typeof value === "string" ? new Set(value) : undefined,
|
|
75
|
+
});
|
|
76
|
+
out.set(language, inventory);
|
|
77
|
+
const tag = languages[language]?.tag;
|
|
78
|
+
if (typeof tag === "string") out.set(tag.toLowerCase(), inventory);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Invalid source characters, retaining offsets in the supplied unescaped text.
|
|
85
|
+
* No punctuation, case or normalization repair is performed.
|
|
86
|
+
* @param {string} text - Visible Roman source.
|
|
87
|
+
* @param {RomanInventory} inventory - Its language's declared characters.
|
|
88
|
+
* @param {(char: string) => boolean} [native] - Whether a directly typed native sign is drawn in the selected native face.
|
|
89
|
+
* @returns {Array<{offset: number, message: string}>}
|
|
90
|
+
*/
|
|
91
|
+
export function romanTextProblems(text, inventory, native = () => false) {
|
|
92
|
+
const out = [];
|
|
93
|
+
if (!inventory.characters) return out;
|
|
94
|
+
for (const { segment: cluster, index } of graphemes.segment(text)) {
|
|
95
|
+
let offset = index;
|
|
96
|
+
if (cluster !== cluster.normalize("NFC")) {
|
|
97
|
+
out.push({
|
|
98
|
+
offset,
|
|
99
|
+
message: `\`${cluster}\` is not NFC spelling in \`${inventory.language}\`; write \`${cluster.normalize("NFC")}\``,
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
for (const char of cluster) {
|
|
104
|
+
if (!/[ \t\r\n]/u.test(char) && !inventory.characters.has(char) && !native(char))
|
|
105
|
+
out.push({
|
|
106
|
+
offset,
|
|
107
|
+
message: `\`${char}\` (${codeName(char)}) is not in \`${inventory.language}\`'s \`data.roman\` inventory`,
|
|
108
|
+
});
|
|
109
|
+
offset += char.length;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "25.0.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
* @returns {boolean} Whether Tier 1 admits it.
|
|
10
10
|
*/
|
|
11
11
|
export function isLetterTier(cp: number): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Whether a code point belongs to a declared setting-script encoding block.
|
|
14
|
+
* This identifies native input independently of a language's Roman inventory;
|
|
15
|
+
* a selected native face must still draw the individual character.
|
|
16
|
+
* @param {number} cp - A Unicode code point.
|
|
17
|
+
* @returns {boolean}
|
|
18
|
+
*/
|
|
19
|
+
export function isSettingScriptCodePoint(cp: number): boolean;
|
|
12
20
|
/**
|
|
13
21
|
* Whether the charset admits a code point anywhere in a note.
|
|
14
22
|
*
|
|
@@ -254,6 +254,11 @@ export type RunContext = {
|
|
|
254
254
|
* {@link module :engine/verse-metre.metreBooksFromRecords}.
|
|
255
255
|
*/
|
|
256
256
|
metreBooks?: ReadonlyMap<string, import("./verse-metre.mjs").MetreBook> | undefined;
|
|
257
|
+
/**
|
|
258
|
+
* -
|
|
259
|
+
* Exact Roman source inventories by configured alias and BCP 47 tag.
|
|
260
|
+
*/
|
|
261
|
+
romanInventories?: ReadonlyMap<string, import("./roman-characters.mjs").RomanInventory> | undefined;
|
|
257
262
|
/**
|
|
258
263
|
* -
|
|
259
264
|
* Each face's emphasis conventions, from the script notes, by
|
package/types/engine/index.d.mts
CHANGED
|
@@ -65,6 +65,7 @@ export * as previewIndex from "./preview-index.mjs";
|
|
|
65
65
|
export * as contentRender from "./content-render.mjs";
|
|
66
66
|
export * as contentLint from "./content-lint.mjs";
|
|
67
67
|
export * as contentCharset from "./content-charset.mjs";
|
|
68
|
+
export * as romanCharacters from "./roman-characters.mjs";
|
|
68
69
|
export * as pathCharset from "./path-charset.mjs";
|
|
69
70
|
export * as contentIcons from "./content-icons.mjs";
|
|
70
71
|
export * as contentHtml from "./content-html.mjs";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic problems in a supplied inventory. Shape belongs to the vocabulary.
|
|
3
|
+
* An empty string explicitly declares no written characters.
|
|
4
|
+
* @param {unknown} value - The authored `data.roman`.
|
|
5
|
+
* @returns {string[]} Findings at that field.
|
|
6
|
+
*/
|
|
7
|
+
export function romanInventoryProblems(value: unknown): string[];
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} RomanInventory
|
|
10
|
+
* @property {string} language - The configured language name.
|
|
11
|
+
* @property {ReadonlySet<string>|undefined} characters - Exact code points, absent for a missing declaration.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Each configured language note's own inventory, by alias and BCP 47 tag.
|
|
15
|
+
* Language-family ancestry does not supply an inventory. Unassociated external
|
|
16
|
+
* tags have no entry and keep their ordinary behavior.
|
|
17
|
+
* @param {ReadonlyMap<string, Record<string, any>>} records - Configured language notes.
|
|
18
|
+
* @param {Readonly<Record<string, {tag: string}>>} languages - Language configuration.
|
|
19
|
+
* @returns {Map<string, RomanInventory>}
|
|
20
|
+
*/
|
|
21
|
+
export function romanInventoriesOf(records: ReadonlyMap<string, Record<string, any>>, languages?: Readonly<Record<string, {
|
|
22
|
+
tag: string;
|
|
23
|
+
}>>): Map<string, RomanInventory>;
|
|
24
|
+
/**
|
|
25
|
+
* Invalid source characters, retaining offsets in the supplied unescaped text.
|
|
26
|
+
* No punctuation, case or normalization repair is performed.
|
|
27
|
+
* @param {string} text - Visible Roman source.
|
|
28
|
+
* @param {RomanInventory} inventory - Its language's declared characters.
|
|
29
|
+
* @param {(char: string) => boolean} [native] - Whether a directly typed native sign is drawn in the selected native face.
|
|
30
|
+
* @returns {Array<{offset: number, message: string}>}
|
|
31
|
+
*/
|
|
32
|
+
export function romanTextProblems(text: string, inventory: RomanInventory, native?: (char: string) => boolean): Array<{
|
|
33
|
+
offset: number;
|
|
34
|
+
message: string;
|
|
35
|
+
}>;
|
|
36
|
+
export type RomanInventory = {
|
|
37
|
+
/**
|
|
38
|
+
* - The configured language name.
|
|
39
|
+
*/
|
|
40
|
+
language: string;
|
|
41
|
+
/**
|
|
42
|
+
* - Exact code points, absent for a missing declaration.
|
|
43
|
+
*/
|
|
44
|
+
characters: ReadonlySet<string> | undefined;
|
|
45
|
+
};
|