@heroiclands/package-build 25.2.1 → 25.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/assets/styles/content-base.css +65 -116
- package/docs/authoring/links-and-markup.md +114 -109
- package/docs/content-format.md +19 -2
- package/docs/diagnostics.md +25 -26
- package/docs/guides/writing-in-invented-scripts.md +41 -62
- package/docs/reference/format-details.md +0 -1
- package/docs/reference/theme-pages.md +30 -57
- package/engine/content-blocks.mjs +3 -2
- package/engine/content-render.mjs +31 -29
- package/engine/content-scripts.mjs +180 -86
- package/engine/content-spans.mjs +4 -6
- package/engine/content-verse.mjs +482 -281
- package/engine/pdf-render.mjs +65 -117
- package/engine/roman-verse.mjs +68 -57
- package/engine/verse-metre.mjs +33 -40
- package/package.json +1 -1
- package/site-theme/static/css/style.css +49 -101
- package/types/engine/content-blocks.d.mts +3 -1
- package/types/engine/content-scripts.d.mts +9 -5
- package/types/engine/content-verse.d.mts +269 -83
- package/types/engine/roman-verse.d.mts +7 -6
package/engine/pdf-render.mjs
CHANGED
|
@@ -86,13 +86,7 @@ import { scanSpans } from "./content-spans.mjs";
|
|
|
86
86
|
import { GENERIC_FONTS, renderRuns, scanRuns } from "./content-scripts.mjs";
|
|
87
87
|
import { scanFigures } from "./content-figures.mjs";
|
|
88
88
|
import { BLOCK_CONTAINERS, scanBlocks, BLOCK_NAMES } from "./content-blocks.mjs";
|
|
89
|
-
import {
|
|
90
|
-
hasColumns,
|
|
91
|
-
lineNumber,
|
|
92
|
-
rightToLeft,
|
|
93
|
-
scanVerse,
|
|
94
|
-
verseColumnCount,
|
|
95
|
-
} from "./content-verse.mjs";
|
|
89
|
+
import { rightToLeft, scanVerse } from "./content-verse.mjs";
|
|
96
90
|
import { matchAllOutsideCode, replaceOutsideCode } from "./code-fences.mjs";
|
|
97
91
|
import { panelRoman, scanScrolls, scrollPanels, scrollParagraphs } from "./content-scroll.mjs";
|
|
98
92
|
import { BOOK_INK, BOOK_RUBRIC, fontOutlines, scrollPanelSvg } from "./font-outlines.mjs";
|
|
@@ -522,7 +516,10 @@ export function markdownToTypst(markdown, opts = {}) {
|
|
|
522
516
|
const marked =
|
|
523
517
|
opts.nested ? undefined : renderRuns(authored, opts.runs, undefined, { target: "book" });
|
|
524
518
|
const source = marked ? marked.markdown : authored;
|
|
525
|
-
if (marked)
|
|
519
|
+
if (marked) {
|
|
520
|
+
sharedOptions.scrolls = marked.scrolls;
|
|
521
|
+
sharedOptions.verses = marked.verses;
|
|
522
|
+
}
|
|
526
523
|
// One map for the whole body, not one per block: a heading inside a
|
|
527
524
|
// blockquote or a list item shares the entry's anchor namespace with every
|
|
528
525
|
// other heading in the same body, because `sectionLabel` scopes by entry
|
|
@@ -543,6 +540,7 @@ export function markdownToTypst(markdown, opts = {}) {
|
|
|
543
540
|
footnoteState,
|
|
544
541
|
footnotePrefix: opts.footnotePrefix ?? `footnote-${slugify(anchorPrefix || "body")}`,
|
|
545
542
|
scrolls: marked?.scrolls ?? opts.scrolls ?? new Map(),
|
|
543
|
+
verses: marked?.verses ?? opts.verses ?? new Map(),
|
|
546
544
|
};
|
|
547
545
|
// A withheld section is set in the box a `:::secret` is set in, and its
|
|
548
546
|
// heading is set inside it: a printed page honours no reader permission, so
|
|
@@ -691,7 +689,7 @@ export function markdownToTypst(markdown, opts = {}) {
|
|
|
691
689
|
/** Render one fragment with the note's footnote definitions available. */
|
|
692
690
|
function renderMarkdownSegment(source, md, ctx, definitions) {
|
|
693
691
|
const input = definitions ? `${source}\n\n${definitions}` : source;
|
|
694
|
-
const tokens = md.parse(input, {});
|
|
692
|
+
const tokens = md.parse(input, { verses: ctx.verses });
|
|
695
693
|
const footnoteStart = tokens.findIndex((token) => token.type === "footnote_block_open");
|
|
696
694
|
const visible = footnoteStart < 0 ? tokens : tokens.slice(0, footnoteStart);
|
|
697
695
|
const footnotes = new Map();
|
|
@@ -779,34 +777,19 @@ function renderBlock(tokens, i, out, ctx) {
|
|
|
779
777
|
}
|
|
780
778
|
if (token.type === "fence" && /^verse(?:\s|$)/.test(token.info?.trim() || "")) {
|
|
781
779
|
const opening = `${token.markup}${token.info}`;
|
|
782
|
-
const scanned = scanVerse(`${opening}\n${token.content}${token.markup}
|
|
780
|
+
const scanned = scanVerse(`${opening}\n${token.content}${token.markup}`, {
|
|
781
|
+
checkWorks: false,
|
|
782
|
+
});
|
|
783
783
|
// Findings are collected before rendering. Keep malformed verse
|
|
784
784
|
// literal rather than assigning invalid parts to display cells.
|
|
785
|
-
if (scanned.errors.length) {
|
|
785
|
+
if (scanned.errors.length && !token.meta?.versePaired) {
|
|
786
786
|
out.push(rawBlock(token.content, token.info));
|
|
787
787
|
return 1;
|
|
788
788
|
}
|
|
789
789
|
const poem = scanned.blocks[0];
|
|
790
|
-
const
|
|
791
|
-
const content =
|
|
792
|
-
hasColumns(poem) ? verseGrid(token, poem, step, ctx)
|
|
793
|
-
: step ? numberedVerse(token, step, ctx)
|
|
794
|
-
: token.meta.verseStanzas
|
|
795
|
-
.map((stanza) =>
|
|
796
|
-
stanza[0]?.stanzaBreak ?
|
|
797
|
-
"#v(0.5em)"
|
|
798
|
-
: stanza
|
|
799
|
-
.map(({ children, level }) => {
|
|
800
|
-
const verse = renderInline({ children }, ctx);
|
|
801
|
-
return `${level ? `#h(${level * 1.25}em)` : ""}${verse}`;
|
|
802
|
-
})
|
|
803
|
-
.join(" \\\n"),
|
|
804
|
-
)
|
|
805
|
-
.join("\n\n");
|
|
790
|
+
const content = workVerse(token, poem, ctx);
|
|
806
791
|
const anchor = poem?.id ? ` <${sectionLabel(ctx.anchorPrefix, poem.id)}>` : "";
|
|
807
|
-
out.push(
|
|
808
|
-
`\n#block(width: 100%)[${scriptWrapped(content, poem?.classes, poem?.attributes)}]${anchor}\n`,
|
|
809
|
-
);
|
|
792
|
+
out.push(`\n#block(width: 100%)[${content}]${anchor}\n`);
|
|
810
793
|
} else out.push(rawBlock(token.content, token.info?.trim() || ""));
|
|
811
794
|
return 1;
|
|
812
795
|
}
|
|
@@ -1300,90 +1283,51 @@ function typstLanguage(tag) {
|
|
|
1300
1283
|
/** The classes that set a run in a font: a face's run carries `script`. */
|
|
1301
1284
|
const FONT_CLASSES = Object.freeze(["script", ...Object.values(GENERIC_FONTS)]);
|
|
1302
1285
|
|
|
1303
|
-
/**
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
.
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
* @param {object} poem - The verse, from `scanVerse`.
|
|
1344
|
-
* @param {number} step - Its `numstep`.
|
|
1345
|
-
* @param {object} ctx - Render context.
|
|
1346
|
-
* @returns {string} Typst markup.
|
|
1347
|
-
*/
|
|
1348
|
-
function verseGrid(token, poem, step, ctx) {
|
|
1349
|
-
const count = verseColumnCount(poem);
|
|
1350
|
-
const width = count + (step ? 1 : 0);
|
|
1351
|
-
const columns = [...(step ? ["2.5em"] : []), ...Array(count).fill("1fr")];
|
|
1352
|
-
const rows = [];
|
|
1353
|
-
let row = 0;
|
|
1354
|
-
for (const group of token.meta.verseGroups) {
|
|
1355
|
-
if (group.stanzaBreak) {
|
|
1356
|
-
rows.push(` grid.cell(colspan: ${width})[#v(0.5em)],`);
|
|
1357
|
-
continue;
|
|
1358
|
-
}
|
|
1359
|
-
const cells = Array.from({ length: count }, () => []);
|
|
1360
|
-
for (const part of group.parts) {
|
|
1361
|
-
const rtl = rightToLeft(part.text);
|
|
1362
|
-
const secondary = cells[part.column - 1].length > 0;
|
|
1363
|
-
const text = renderInline(
|
|
1364
|
-
{ children: part.children },
|
|
1365
|
-
secondary ? { ...ctx, textFill: "luma(45%)" } : ctx,
|
|
1286
|
+
/** The logical lines of one stanza, with a shared label and paired scripts. */
|
|
1287
|
+
function workVerse(token, poem, ctx) {
|
|
1288
|
+
const paired = token.meta.versePaired;
|
|
1289
|
+
const labelled = Boolean(poem?.attributes.work);
|
|
1290
|
+
const nativeClasses = paired?.classes ?? poem?.classes;
|
|
1291
|
+
const nativeAttributes = paired?.attributes ?? poem?.attributes;
|
|
1292
|
+
return (token.meta.verseGroups ?? [])
|
|
1293
|
+
.map((group) => {
|
|
1294
|
+
if (group.cantoBreak) {
|
|
1295
|
+
const mark =
|
|
1296
|
+
group.children?.length ? renderInline({ children: group.children }, ctx) : "";
|
|
1297
|
+
return `${mark ? scriptWrapped(mark, nativeClasses, nativeAttributes) + "\n" : ""}#v(0.5em)`;
|
|
1298
|
+
}
|
|
1299
|
+
const label = labelled ? `#text(size: 0.75em, fill: luma(45%))[${group.label}]` : "";
|
|
1300
|
+
const lineText = (line, roman = false) => {
|
|
1301
|
+
const text = renderInline(
|
|
1302
|
+
{ children: roman ? line.romanChildren : line.children },
|
|
1303
|
+
ctx,
|
|
1304
|
+
);
|
|
1305
|
+
const padded = `${line.level ? `#h(${line.level * 1.25}em)` : ""}${text}`;
|
|
1306
|
+
if (roman || !paired) return padded;
|
|
1307
|
+
const wrapped = scriptWrapped(padded, nativeClasses, nativeAttributes);
|
|
1308
|
+
return rightToLeft(line.text) ? `#set text(dir: rtl)\n${wrapped}` : wrapped;
|
|
1309
|
+
};
|
|
1310
|
+
if (!paired) {
|
|
1311
|
+
const lines = scriptWrapped(
|
|
1312
|
+
group.lines.map((line) => lineText(line)).join(" \\\n"),
|
|
1313
|
+
nativeClasses,
|
|
1314
|
+
nativeAttributes,
|
|
1315
|
+
);
|
|
1316
|
+
return labelled ?
|
|
1317
|
+
`#grid(columns: (2.5em, 1fr), column-gutter: 0.5em, [${label}], [${lines}])`
|
|
1318
|
+
: lines;
|
|
1319
|
+
}
|
|
1320
|
+
const cells = group.lines.map((line, index) =>
|
|
1321
|
+
[
|
|
1322
|
+
...(labelled ? [index === 0 ? `[${label}]` : "[]"] : []),
|
|
1323
|
+
`[${lineText(line)}]`,
|
|
1324
|
+
`[${lineText(line, true)}]`,
|
|
1325
|
+
].join(", "),
|
|
1366
1326
|
);
|
|
1367
|
-
const
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
cells[part.column - 1].push(rtl ? `#set text(dir: rtl)\n${padded}` : padded);
|
|
1372
|
-
}
|
|
1373
|
-
const rendered = cells.map((stacked) => {
|
|
1374
|
-
if (!stacked.length) return "[]";
|
|
1375
|
-
if (stacked.length === 1) return `[${stacked[0]}]`;
|
|
1376
|
-
return `[#stack(dir: ttb, spacing: 0.3em, ${stacked.map((part, k) => (k ? `[#text(fill: luma(45%))[${part}]]` : `[${part}]`)).join(", ")})]`;
|
|
1377
|
-
});
|
|
1378
|
-
const number = step ? [typstNumber(lineNumber(++row, step))] : [];
|
|
1379
|
-
rows.push(` ${[...number, ...rendered].join(", ")},`);
|
|
1380
|
-
}
|
|
1381
|
-
return [
|
|
1382
|
-
"#set text(dir: ltr)",
|
|
1383
|
-
`#grid(columns: (${columns.join(", ")}), column-gutter: 1.5em, row-gutter: 0.65em,`,
|
|
1384
|
-
...rows,
|
|
1385
|
-
")",
|
|
1386
|
-
].join("\n");
|
|
1327
|
+
const columns = labelled ? "2.5em, 1fr, 1fr" : "1fr, 1fr";
|
|
1328
|
+
return `#set text(dir: ltr)\n#grid(columns: (${columns}), column-gutter: 1.5em, row-gutter: 0.35em,\n ${cells.join(",\n ")}\n)`;
|
|
1329
|
+
})
|
|
1330
|
+
.join("\n\n");
|
|
1387
1331
|
}
|
|
1388
1332
|
|
|
1389
1333
|
/**
|
|
@@ -1719,8 +1663,7 @@ function pageOfLabel(label) {
|
|
|
1719
1663
|
* @returns {string} Typst markup.
|
|
1720
1664
|
*/
|
|
1721
1665
|
function renderLink(href, inner, ctx, { bracketed = false } = {}) {
|
|
1722
|
-
// A
|
|
1723
|
-
// link presentation, including any native spans or emphasis in that label.
|
|
1666
|
+
// A caller's explicit text fill applies inside the link's label.
|
|
1724
1667
|
if (ctx.textFill) inner = `#text(fill: ${ctx.textFill})[${inner}]`;
|
|
1725
1668
|
const url = String(href ?? "");
|
|
1726
1669
|
const fragment = /#([^?]*)/.exec(url)?.[1] ?? "";
|
|
@@ -2012,9 +1955,14 @@ export function bookTypstPreamble(runs = {}, families = {}) {
|
|
|
2012
1955
|
" }\n" +
|
|
2013
1956
|
" if lang != none { text(lang: lang, styled) } else { styled }\n" +
|
|
2014
1957
|
"}",
|
|
2015
|
-
//
|
|
1958
|
+
// Plain semantic hooks retain the span classes without choosing their appearance.
|
|
1959
|
+
"#let book-direction(body) = body",
|
|
1960
|
+
"#let book-speaker(body) = body",
|
|
2016
1961
|
"#let book-span(body, classes: (), lang: none) = {\n" +
|
|
2017
|
-
"
|
|
1962
|
+
" let styled = book-script-text(body, classes: classes, lang: lang)\n" +
|
|
1963
|
+
' if "direction" in classes { styled = book-direction(styled) }\n' +
|
|
1964
|
+
' if "speaker" in classes { styled = book-speaker(styled) }\n' +
|
|
1965
|
+
" box(styled)\n" +
|
|
2018
1966
|
"}",
|
|
2019
1967
|
// Use the file's natural measure for auto. Every requested width is
|
|
2020
1968
|
// bounded by the available measure and by the height of a page.
|
package/engine/roman-verse.mjs
CHANGED
|
@@ -12,16 +12,15 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/** Logical verse text and separately located structural boundaries. @module */
|
|
15
|
-
import {
|
|
15
|
+
import { parseVerse } from "./content-verse.mjs";
|
|
16
16
|
import { plainRomanRun } from "./roman-run.mjs";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* The caller writes the boundaries through its selected encoding.
|
|
19
|
+
* Project verse text with explicit authored line, stanza and canto boundaries.
|
|
20
|
+
* Source newlines within a logical line are whitespace. Delimiters and visual
|
|
21
|
+
* indentation do not reach the encoder as letters.
|
|
23
22
|
* @param {string} source - Complete authored source.
|
|
24
|
-
* @param {Parameters<typeof plainRomanRun>[1] & {
|
|
23
|
+
* @param {Parameters<typeof plainRomanRun>[1] & {model?: ReturnType<typeof parseVerse>, modelOffset?: number}} [options] - Run bounds, spans and layout projection.
|
|
25
24
|
* @returns {ReturnType<typeof plainRomanRun> & {boundaries: Array<{kind: "line"|"stanza"|"canto", at: number, sourceOffset: number}>, errors: Array<{offset: number, message: string}>}} Visible text, positions and structural events.
|
|
26
25
|
*/
|
|
27
26
|
export function plainRomanVerse(source, options = {}) {
|
|
@@ -29,28 +28,60 @@ export function plainRomanVerse(source, options = {}) {
|
|
|
29
28
|
from = 0,
|
|
30
29
|
to = source.length,
|
|
31
30
|
exclude = [],
|
|
32
|
-
|
|
31
|
+
model: sharedModel,
|
|
32
|
+
modelOffset = from,
|
|
33
33
|
} = options;
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
const model = sharedModel ?? parseVerse(source.slice(from, to));
|
|
35
|
+
const base = sharedModel ? modelOffset : from;
|
|
36
|
+
const boundaries = model.boundaries
|
|
37
|
+
.map((boundary) => ({ ...boundary, sourceOffset: base + boundary.sourceOffset }))
|
|
38
|
+
.filter((boundary) => boundary.sourceOffset >= from && boundary.sourceOffset <= to);
|
|
39
|
+
const projected = plainRomanRun(source, {
|
|
40
|
+
...options,
|
|
41
|
+
from,
|
|
42
|
+
to,
|
|
43
|
+
exclude: [
|
|
44
|
+
...exclude,
|
|
45
|
+
...model.layout
|
|
46
|
+
.map(([first, past]) => [Math.max(from, base + first), Math.min(to, base + past)])
|
|
47
|
+
.filter(([first, past]) => past > first),
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
const text = [],
|
|
51
|
+
offsets = [],
|
|
52
|
+
positions = [],
|
|
53
|
+
ends = [];
|
|
54
|
+
const pending = [...boundaries];
|
|
55
|
+
let boundaryIndex = 0;
|
|
56
|
+
const appendBoundary = (boundary) => {
|
|
57
|
+
text.push("\n");
|
|
58
|
+
offsets.push(boundary.sourceOffset);
|
|
59
|
+
};
|
|
60
|
+
for (let index = 0; index < projected.text.length; index++) {
|
|
61
|
+
ends[index] = text.length;
|
|
62
|
+
while (
|
|
63
|
+
pending[boundaryIndex] &&
|
|
64
|
+
pending[boundaryIndex].sourceOffset <= projected.offsets[index]
|
|
65
|
+
)
|
|
66
|
+
appendBoundary(pending[boundaryIndex++]);
|
|
67
|
+
positions[index] = text.length;
|
|
68
|
+
const char = projected.text[index];
|
|
69
|
+
if (char === "\n" && projected.text[index - 1] === "\r") continue;
|
|
70
|
+
text.push(/[\r\n]/.test(char) ? " " : char);
|
|
71
|
+
offsets.push(projected.offsets[index]);
|
|
52
72
|
}
|
|
53
|
-
|
|
73
|
+
ends[projected.text.length] = text.length;
|
|
74
|
+
while (pending[boundaryIndex]) appendBoundary(pending[boundaryIndex++]);
|
|
75
|
+
positions[projected.text.length] = text.length;
|
|
76
|
+
projected.text = text.join("");
|
|
77
|
+
projected.offsets = offsets;
|
|
78
|
+
projected.spans = projected.spans.map((span) => ({
|
|
79
|
+
...span,
|
|
80
|
+
from: positions[span.from],
|
|
81
|
+
to: ends[span.to],
|
|
82
|
+
contentFrom: positions[span.contentFrom],
|
|
83
|
+
contentTo: ends[span.contentTo],
|
|
84
|
+
}));
|
|
54
85
|
const logicalAt = (offset) => {
|
|
55
86
|
let first = 0,
|
|
56
87
|
past = projected.offsets.length;
|
|
@@ -61,35 +92,15 @@ export function plainRomanVerse(source, options = {}) {
|
|
|
61
92
|
}
|
|
62
93
|
return first;
|
|
63
94
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
boundaries.push({
|
|
76
|
-
kind: "stanza",
|
|
77
|
-
at: logicalAt(line.from),
|
|
78
|
-
sourceOffset: line.from,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const errors = [];
|
|
83
|
-
for (let index = 0; index < projected.text.length; index++) {
|
|
84
|
-
if (projected.text[index] === "⟦") {
|
|
85
|
-
const close = projected.text.indexOf("⟧", index + 1);
|
|
86
|
-
index = close < 0 ? projected.text.length : close;
|
|
87
|
-
} else if (projected.text[index] === "/")
|
|
88
|
-
errors.push({
|
|
89
|
-
offset: projected.offsets[index],
|
|
90
|
-
message:
|
|
91
|
-
"a verse fence uses authored line endings for verse boundaries; write no literal `/` inside it",
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
return { ...projected, boundaries, errors };
|
|
95
|
+
return {
|
|
96
|
+
...projected,
|
|
97
|
+
boundaries: boundaries.map(({ kind, sourceOffset }) => ({
|
|
98
|
+
kind,
|
|
99
|
+
sourceOffset,
|
|
100
|
+
at: logicalAt(sourceOffset),
|
|
101
|
+
})),
|
|
102
|
+
errors: model.errors
|
|
103
|
+
.map(({ offset, message }) => ({ offset: base + offset, message }))
|
|
104
|
+
.filter((error) => error.offset >= from && error.offset < to),
|
|
105
|
+
};
|
|
95
106
|
}
|
package/engine/verse-metre.mjs
CHANGED
|
@@ -60,7 +60,7 @@ import { WORD } from "./word-pattern.mjs";
|
|
|
60
60
|
|
|
61
61
|
import MarkdownIt from "markdown-it";
|
|
62
62
|
import { spanMarkdownPlugin } from "./content-spans.mjs";
|
|
63
|
-
import {
|
|
63
|
+
import { parseVerse } from "./content-verse.mjs";
|
|
64
64
|
import { inlineText } from "./name-markup.mjs";
|
|
65
65
|
|
|
66
66
|
const verseParser = new MarkdownIt().use(spanMarkdownPlugin);
|
|
@@ -311,13 +311,6 @@ export function metreFindings(blocks, lines, ctx) {
|
|
|
311
311
|
column: ctx.attributeColumns(opening, key)[part],
|
|
312
312
|
});
|
|
313
313
|
const error = (position, message) => errors.push({ ...position, message });
|
|
314
|
-
if (Number(block.attributes.cols ?? 1) > 1) {
|
|
315
|
-
error(
|
|
316
|
-
at("scan", "key"),
|
|
317
|
-
"`scan=` counts the beats of one language's lines; a verse with cols>1 groups independent physical lines",
|
|
318
|
-
);
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
314
|
const metre = metres.get(name);
|
|
322
315
|
if (!metre) {
|
|
323
316
|
const near = nearest(name, [...metres.keys()]);
|
|
@@ -346,40 +339,40 @@ export function metreFindings(blocks, lines, ctx) {
|
|
|
346
339
|
);
|
|
347
340
|
continue;
|
|
348
341
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
(reading.markerStart <= reading.textColumn - 1 + foreign.meta.start ?
|
|
369
|
-
reading.markerLength
|
|
370
|
-
: 0),
|
|
371
|
-
},
|
|
372
|
-
"`scan=` counts one language's beats; this verse line contains another language",
|
|
342
|
+
const body = lines.slice(block.start + 1, block.end).join("\n");
|
|
343
|
+
const model = block.model ?? parseVerse(body);
|
|
344
|
+
const sourcePosition = (offset) => {
|
|
345
|
+
const preceding = body.slice(0, offset);
|
|
346
|
+
return {
|
|
347
|
+
line: block.start + 2 + preceding.split("\n").length - 1,
|
|
348
|
+
column: preceding.length - preceding.lastIndexOf("\n"),
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
for (const group of model.groups) {
|
|
352
|
+
if (group.cantoBreak) continue;
|
|
353
|
+
for (const reading of group.lines) {
|
|
354
|
+
const tokens = verseParser.parseInline(reading.text, {})[0]?.children ?? [];
|
|
355
|
+
const tag = (name) => (ctx.languages?.[name]?.tag ?? name).toLowerCase();
|
|
356
|
+
const foreign = tokens.find(
|
|
357
|
+
(token) =>
|
|
358
|
+
token.type === "span_open" &&
|
|
359
|
+
token.attrGet("lang") &&
|
|
360
|
+
tag(token.attrGet("lang")) !== tag(lang),
|
|
373
361
|
);
|
|
374
|
-
|
|
362
|
+
if (foreign) {
|
|
363
|
+
error(
|
|
364
|
+
sourcePosition(reading.offsets[foreign.meta.start] ?? reading.from),
|
|
365
|
+
"`scan=` counts one language's beats; this verse line contains another language",
|
|
366
|
+
);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
const beats = lineBeats(inlineText(tokens), book);
|
|
370
|
+
if (metre.beats >= beats.min && metre.beats <= beats.max) continue;
|
|
371
|
+
warnings.push({
|
|
372
|
+
...sourcePosition(reading.from),
|
|
373
|
+
message: metreMessage(beats, metre),
|
|
374
|
+
});
|
|
375
375
|
}
|
|
376
|
-
const beats = lineBeats(inlineText(tokens), book);
|
|
377
|
-
if (metre.beats >= beats.min && metre.beats <= beats.max) continue;
|
|
378
|
-
warnings.push({
|
|
379
|
-
line: line + 1,
|
|
380
|
-
column: /^\s*/.exec(text)[0].length + 1,
|
|
381
|
-
message: metreMessage(beats, metre),
|
|
382
|
-
});
|
|
383
376
|
}
|
|
384
377
|
}
|
|
385
378
|
return { errors, warnings };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "25.
|
|
3
|
+
"version": "25.3.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package \u2014 content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|