@ai-matrx/print 0.6.2 → 0.7.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 +48 -0
- package/README.md +38 -0
- package/dist/directives.cjs +38 -1
- package/dist/directives.cjs.map +1 -1
- package/dist/directives.d.cts +17 -1
- package/dist/directives.d.ts +17 -1
- package/dist/directives.js +38 -1
- package/dist/directives.js.map +1 -1
- package/dist/document.cjs +2842 -0
- package/dist/document.cjs.map +1 -0
- package/dist/document.d.cts +490 -0
- package/dist/document.d.ts +490 -0
- package/dist/document.js +2809 -0
- package/dist/document.js.map +1 -0
- package/dist/markdown.cjs +30 -2
- package/dist/markdown.cjs.map +1 -1
- package/dist/markdown.js +30 -2
- package/dist/markdown.js.map +1 -1
- package/dist/pdf.cjs.map +1 -1
- package/dist/pdf.js.map +1 -1
- package/dist/react.cjs +117 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +24 -1
- package/dist/react.d.ts +24 -1
- package/dist/react.js +117 -0
- package/dist/react.js.map +1 -1
- package/package.json +29 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
- **Added: `@ai-matrx/print/document` — print-grade documents from markdown.**
|
|
6
|
+
One parsed tree (CommonMark + GFM via micromark/mdast) renders to **PDF**
|
|
7
|
+
(real text PDF via pdfmake), **DOCX** (docx), **EPUB 3** and a standalone
|
|
8
|
+
**HTML** file, so every format carries the same layout, numbering and
|
|
9
|
+
references. `exportDocument(markdown, "pdf" | "docx" | "epub" | "html" |
|
|
10
|
+
"markdown")` is the one call.
|
|
11
|
+
- **Document settings in YAML frontmatter** (pandoc names, any spelling):
|
|
12
|
+
page size (letter, legal, tabloid, executive, A3–A5, B5), margins (`1in`,
|
|
13
|
+
`2.5cm`, pandoc `geometry`), orientation, cover page (title, subtitle,
|
|
14
|
+
author, date, logo), running header/footer templates with `{page}` /
|
|
15
|
+
`{pages}` / `{title}` / `{date}` ("Page X of Y"), a different first page,
|
|
16
|
+
1–3 columns, section numbering, table of contents (depth, title), citation
|
|
17
|
+
style, references, font size, hyphenation.
|
|
18
|
+
- **Section breaks** — `<!-- section landscape columns=2 -->` starts a new
|
|
19
|
+
page and switches orientation/columns from there on (Word's "section break,
|
|
20
|
+
next page"). Added to `/directives` (`parseSectionBreakLine`,
|
|
21
|
+
`isBreakLine`, `SECTION_BREAK_MARKDOWN`); `splitAtPageBreaks` and both
|
|
22
|
+
markdown skins treat a section break as a page break.
|
|
23
|
+
- **Table of contents** — automatic, with real page numbers in PDF, a
|
|
24
|
+
pre-filled Word TOC field (page numbers from the PDF layout; Word refreshes
|
|
25
|
+
on open), the EPUB navigation document, and links in HTML. Placement:
|
|
26
|
+
`toc: true` or a `[[toc]]` line.
|
|
27
|
+
- **Captions, numbering, cross-references** (pandoc-crossref grammar):
|
|
28
|
+
`{#fig:id}`, `Table: Caption {#tbl:id}`, `## Title
|
|
29
|
+
{#sec:id}`, and `@fig:id` / `@tbl:id` / `@sec:id` → "Figure 2", "Table 1",
|
|
30
|
+
"Section 3" (linked; Word gets SEQ fields + bookmarks).
|
|
31
|
+
- **Citations and bibliography** — pandoc citation syntax (`[@key, p. 4]`,
|
|
32
|
+
`[@a; @b]`, `[-@key]`, bare `@key`) over CSL-JSON references in the
|
|
33
|
+
frontmatter, formatted APA 7, MLA 9 or Chicago author-date, with the
|
|
34
|
+
reference list appended (or placed at `<!-- bibliography -->`).
|
|
35
|
+
Footnotes print as endnotes.
|
|
36
|
+
- **Print quality** — keep-with-next headings (a heading is never the last
|
|
37
|
+
line on a page), short paragraphs never split, table rows never split and
|
|
38
|
+
header rows repeat, code/figures/tables kept whole when they fit, widow and
|
|
39
|
+
orphan control, hyphenation (HTML/EPUB CSS, Word auto-hyphenation), always
|
|
40
|
+
light.
|
|
41
|
+
- **Added: `DocumentPrintPreview`** in `/react` — the real PDF pages, a web
|
|
42
|
+
view with a clickable TOC, Print, and one-click PDF / Word / EPUB / HTML /
|
|
43
|
+
Markdown downloads; unresolved references and missing images are listed
|
|
44
|
+
with their fix.
|
|
45
|
+
- Nothing fails silently: unresolved cross-references/citations, unusable
|
|
46
|
+
settings, dropped raw HTML and unavailable images are reported as notices
|
|
47
|
+
with a remedy.
|
|
48
|
+
- Consumer action: none required. New dependencies (`pdfmake`, `docx`,
|
|
49
|
+
`jszip`, `yaml`, mdast/micromark) load only when a document is exported.
|
|
50
|
+
|
|
3
51
|
## 0.6.2
|
|
4
52
|
|
|
5
53
|
- **Fixed:** a page-break directive indented four or more columns is code (as
|
package/README.md
CHANGED
|
@@ -195,3 +195,41 @@ drawer are bundled dependencies.
|
|
|
195
195
|
## License
|
|
196
196
|
|
|
197
197
|
MIT
|
|
198
|
+
|
|
199
|
+
### `@ai-matrx/print/document` — print-grade documents (PDF, Word, EPUB, HTML)
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { exportDocument, downloadDocumentExport } from "@ai-matrx/print/document";
|
|
203
|
+
|
|
204
|
+
const exp = await exportDocument(markdown, "docx"); // "pdf" | "docx" | "epub" | "html" | "markdown"
|
|
205
|
+
downloadDocumentExport(exp); // browser: saves "<title>.docx"
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Settings ride in the markdown's frontmatter:
|
|
209
|
+
|
|
210
|
+
```yaml
|
|
211
|
+
---
|
|
212
|
+
title: Quarterly Review
|
|
213
|
+
subtitle: Operations
|
|
214
|
+
author: Jane Park
|
|
215
|
+
date: today
|
|
216
|
+
cover: true
|
|
217
|
+
toc: true # or { depth: 2, title: Contents }
|
|
218
|
+
page: { size: a4, orientation: portrait, margins: 1in }
|
|
219
|
+
header: "{title} | | {date}" # left | center | right
|
|
220
|
+
footer: "Page {page} of {pages}"
|
|
221
|
+
columns: 1
|
|
222
|
+
numberSections: true
|
|
223
|
+
csl: apa # apa | mla | chicago
|
|
224
|
+
references:
|
|
225
|
+
- { id: smith2020, title: Paper Cuts, author: "Smith, Jane", issued: 2020, publisher: Acme }
|
|
226
|
+
---
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
In the body: `<!-- pagebreak -->`, `<!-- section landscape columns=2 -->`,
|
|
230
|
+
`[[toc]]`, `{#fig:chart}`, `Table: Caption {#tbl:sales}`,
|
|
231
|
+
`## Methods {#sec:methods}`, `see @fig:chart`, `[@smith2020, p. 4]`.
|
|
232
|
+
All four formats come from one parsed tree (`parseDocument`), so they agree.
|
|
233
|
+
`DocumentPrintPreview` (in `/react`) shows the real PDF pages with Print and
|
|
234
|
+
every download.
|
|
235
|
+
|
package/dist/directives.cjs
CHANGED
|
@@ -20,10 +20,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/directives.ts
|
|
21
21
|
var directives_exports = {};
|
|
22
22
|
__export(directives_exports, {
|
|
23
|
+
BIBLIOGRAPHY_MARKDOWN: () => BIBLIOGRAPHY_MARKDOWN,
|
|
23
24
|
PAGE_BREAK_CLASS: () => PAGE_BREAK_CLASS,
|
|
24
25
|
PAGE_BREAK_LABEL: () => PAGE_BREAK_LABEL,
|
|
25
26
|
PAGE_BREAK_MARKDOWN: () => PAGE_BREAK_MARKDOWN,
|
|
27
|
+
SECTION_BREAK_MARKDOWN: () => SECTION_BREAK_MARKDOWN,
|
|
28
|
+
TOC_MARKDOWN: () => TOC_MARKDOWN,
|
|
29
|
+
isBibliographyLine: () => isBibliographyLine,
|
|
30
|
+
isBreakLine: () => isBreakLine,
|
|
26
31
|
isPageBreakLine: () => isPageBreakLine,
|
|
32
|
+
isTocLine: () => isTocLine,
|
|
33
|
+
parseSectionBreakLine: () => parseSectionBreakLine,
|
|
27
34
|
splitAtPageBreaks: () => splitAtPageBreaks
|
|
28
35
|
});
|
|
29
36
|
module.exports = __toCommonJS(directives_exports);
|
|
@@ -52,6 +59,36 @@ function isPageBreakLine(line) {
|
|
|
52
59
|
if (!div) return false;
|
|
53
60
|
return DIV_BREAK_ATTR.test(div[1] ?? div[2] ?? "");
|
|
54
61
|
}
|
|
62
|
+
var SECTION_BREAK_MARKDOWN = "<!-- section -->";
|
|
63
|
+
var SECTION_FORM = /^<!--\s*section(?:[\s_-]?break)?\b\s*:?\s*([^>]*?)\s*-->$/i;
|
|
64
|
+
function parseSectionBreakLine(line) {
|
|
65
|
+
if (indentColumns(line) >= 4) return null;
|
|
66
|
+
const m = SECTION_FORM.exec(line.trim());
|
|
67
|
+
if (!m) return null;
|
|
68
|
+
const words = (m[1] ?? "").toLowerCase();
|
|
69
|
+
const out = {};
|
|
70
|
+
if (/\blandscape\b/.test(words)) out.orientation = "landscape";
|
|
71
|
+
else if (/\bportrait\b/.test(words)) out.orientation = "portrait";
|
|
72
|
+
const cols = /\b(?:columns?|cols?)\s*[=:]\s*(\d)\b/.exec(words) ?? /\b(\d)\s*[-\s]?columns?\b/.exec(words);
|
|
73
|
+
if (cols) {
|
|
74
|
+
const n = Math.min(3, Math.max(1, Number(cols[1])));
|
|
75
|
+
out.columns = n;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
var TOC_MARKDOWN = "[[toc]]";
|
|
80
|
+
var BIBLIOGRAPHY_MARKDOWN = "<!-- bibliography -->";
|
|
81
|
+
var TOC_FORM = /^(?:\[\[\s*toc\s*\]\]|\[toc\]|\$\{toc\}|<!--\s*toc\s*-->|\\tableofcontents)$/i;
|
|
82
|
+
var BIBLIOGRAPHY_FORM = /^(?:\[\[\s*(?:bibliography|references)\s*\]\]|<!--\s*(?:bibliography|references)\s*-->|\\printbibliography|\\bibliography\{[^}]*\})$/i;
|
|
83
|
+
function isTocLine(line) {
|
|
84
|
+
return indentColumns(line) < 4 && TOC_FORM.test(line.trim());
|
|
85
|
+
}
|
|
86
|
+
function isBibliographyLine(line) {
|
|
87
|
+
return indentColumns(line) < 4 && BIBLIOGRAPHY_FORM.test(line.trim());
|
|
88
|
+
}
|
|
89
|
+
function isBreakLine(line) {
|
|
90
|
+
return isPageBreakLine(line) || parseSectionBreakLine(line) !== null;
|
|
91
|
+
}
|
|
55
92
|
function splitAtPageBreaks(markdown) {
|
|
56
93
|
const lines = markdown.split(/\r?\n/);
|
|
57
94
|
const pieces = [];
|
|
@@ -63,7 +100,7 @@ function splitAtPageBreaks(markdown) {
|
|
|
63
100
|
const marker = fenceMatch[1];
|
|
64
101
|
if (fence === null) fence = marker;
|
|
65
102
|
else if (marker[0] === fence[0] && marker.length >= fence.length) fence = null;
|
|
66
|
-
} else if (fence === null &&
|
|
103
|
+
} else if (fence === null && isBreakLine(line)) {
|
|
67
104
|
if (current.join("\n").trim()) pieces.push(current.join("\n"));
|
|
68
105
|
current = [];
|
|
69
106
|
continue;
|
package/dist/directives.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/directives.ts"],"sourcesContent":["/**\n * @ai-matrx/print/directives — the ONE definition of the print directives\n * markdown can carry, shared by every surface that reads markdown.\n *\n * Why a separate, zero-dependency entry: the directive syntax has three\n * readers — this package's two converters (print window / PDF / HTML page),\n * the chat renderer's remark plugin (the on-screen preview), and any editor\n * that inserts one. If each re-derived the regex they would drift the first\n * time someone added an alias. `/markdown` carries ~20KB of stylesheet, so the\n * chat bundle imports the syntax from here instead and pays nothing.\n *\n * ── Page break ────────────────────────────────────────────────────────────\n * A line that is exactly one of these (case-insensitive, surrounding\n * whitespace ignored) forces the next content onto a new printed page:\n *\n * <!-- pagebreak --> ← canonical; invisible in any other renderer\n * <!-- page-break --> <!-- page break --> <!-- newpage -->\n * \\pagebreak \\newpage ← LaTeX / pandoc habit\n * <div style=\"page-break-after: always\"></div> ← Typora / VS Code habit\n * (also `page-break-before`, `break-after: page`, `break-before: page`,\n * and `<div class=\"page-break\"></div>` / `class=\"pagebreak\"`)\n *\n * On screen every surface draws the same idea: a dashed rule labelled\n * \"Page break\" with generous padding. In print it is a zero-height element\n * with `break-after: page`.\n */\n\n/** The form editors insert and docs recommend. */\nexport const PAGE_BREAK_MARKDOWN = \"<!-- pagebreak -->\";\n\n/** The class every surface puts on a rendered page break. */\nexport const PAGE_BREAK_CLASS = \"matrx-page-break\";\n\n/** Screen label for a rendered page break. */\nexport const PAGE_BREAK_LABEL = \"Page break\";\n\nconst COMMENT_FORM = /^<!--\\s*(?:page[\\s_-]?break|new[\\s_-]?page)\\s*-->$/i;\nconst LATEX_FORM = /^\\\\(?:pagebreak|newpage|clearpage)\\s*(?:\\{\\s*\\})?$/i;\nconst DIV_FORM = /^<div\\b([^>]*)>\\s*<\\/div>$|^<div\\b([^>]*)\\/>$/i;\nconst DIV_BREAK_ATTR =\n /(?:page-break-(?:after|before)\\s*:\\s*always|break-(?:after|before)\\s*:\\s*page|class\\s*=\\s*[\"'](?:[^\"']*\\s)?page-?break(?:\\s[^\"']*)?[\"'])/i;\n\n/** Columns of leading indentation (tab = 4, NBSP counts as a space). */\nfunction indentColumns(line: string): number {\n let cols = 0;\n for (const ch of line) {\n if (ch === \" \" || ch === \"\\u00A0\") cols += 1;\n else if (ch === \"\\t\") cols += 4;\n else break;\n }\n return cols;\n}\n\n/**\n * Is this single line a page-break directive? Indented four or more columns\n * it is not — in markdown that is a code block, and every surface must agree.\n */\nexport function isPageBreakLine(line: string): boolean {\n if (indentColumns(line) >= 4) return false;\n const t = line.trim();\n if (t.length < 8 || t.length > 200) return false;\n if (COMMENT_FORM.test(t) || LATEX_FORM.test(t)) return true;\n const div = DIV_FORM.exec(t);\n if (!div) return false;\n return DIV_BREAK_ATTR.test(div[1] ?? div[2] ?? \"\");\n}\n\n/**\n * Split markdown into the pieces that belong on separate pages. Fence-aware:\n * a directive inside a ``` / ~~~ code block is code, not a break. Always\n * returns at least one piece; empty pieces (two breaks in a row, a break at\n * the very start or end) are kept out so no blank page is produced.\n */\nexport function splitAtPageBreaks(markdown: string): string[] {\n const lines = markdown.split(/\\r?\\n/);\n const pieces: string[] = [];\n let current: string[] = [];\n let fence: string | null = null;\n\n for (const line of lines) {\n // Any indent — the converters accept list-nested fences, so must this.\n const fenceMatch = /^\\s*(`{3,}|~{3,})/.exec(line);\n if (fenceMatch) {\n const marker = fenceMatch[1] as string;\n if (fence === null) fence = marker;\n else if (marker[0] === fence[0] && marker.length >= fence.length) fence = null;\n } else if (fence === null &&
|
|
1
|
+
{"version":3,"sources":["../src/directives.ts"],"sourcesContent":["/**\n * @ai-matrx/print/directives — the ONE definition of the print directives\n * markdown can carry, shared by every surface that reads markdown.\n *\n * Why a separate, zero-dependency entry: the directive syntax has three\n * readers — this package's two converters (print window / PDF / HTML page),\n * the chat renderer's remark plugin (the on-screen preview), and any editor\n * that inserts one. If each re-derived the regex they would drift the first\n * time someone added an alias. `/markdown` carries ~20KB of stylesheet, so the\n * chat bundle imports the syntax from here instead and pays nothing.\n *\n * ── Page break ────────────────────────────────────────────────────────────\n * A line that is exactly one of these (case-insensitive, surrounding\n * whitespace ignored) forces the next content onto a new printed page:\n *\n * <!-- pagebreak --> ← canonical; invisible in any other renderer\n * <!-- page-break --> <!-- page break --> <!-- newpage -->\n * \\pagebreak \\newpage ← LaTeX / pandoc habit\n * <div style=\"page-break-after: always\"></div> ← Typora / VS Code habit\n * (also `page-break-before`, `break-after: page`, `break-before: page`,\n * and `<div class=\"page-break\"></div>` / `class=\"pagebreak\"`)\n *\n * On screen every surface draws the same idea: a dashed rule labelled\n * \"Page break\" with generous padding. In print it is a zero-height element\n * with `break-after: page`.\n */\n\n/** The form editors insert and docs recommend. */\nexport const PAGE_BREAK_MARKDOWN = \"<!-- pagebreak -->\";\n\n/** The class every surface puts on a rendered page break. */\nexport const PAGE_BREAK_CLASS = \"matrx-page-break\";\n\n/** Screen label for a rendered page break. */\nexport const PAGE_BREAK_LABEL = \"Page break\";\n\nconst COMMENT_FORM = /^<!--\\s*(?:page[\\s_-]?break|new[\\s_-]?page)\\s*-->$/i;\nconst LATEX_FORM = /^\\\\(?:pagebreak|newpage|clearpage)\\s*(?:\\{\\s*\\})?$/i;\nconst DIV_FORM = /^<div\\b([^>]*)>\\s*<\\/div>$|^<div\\b([^>]*)\\/>$/i;\nconst DIV_BREAK_ATTR =\n /(?:page-break-(?:after|before)\\s*:\\s*always|break-(?:after|before)\\s*:\\s*page|class\\s*=\\s*[\"'](?:[^\"']*\\s)?page-?break(?:\\s[^\"']*)?[\"'])/i;\n\n/** Columns of leading indentation (tab = 4, NBSP counts as a space). */\nfunction indentColumns(line: string): number {\n let cols = 0;\n for (const ch of line) {\n if (ch === \" \" || ch === \"\\u00A0\") cols += 1;\n else if (ch === \"\\t\") cols += 4;\n else break;\n }\n return cols;\n}\n\n/**\n * Is this single line a page-break directive? Indented four or more columns\n * it is not — in markdown that is a code block, and every surface must agree.\n */\nexport function isPageBreakLine(line: string): boolean {\n if (indentColumns(line) >= 4) return false;\n const t = line.trim();\n if (t.length < 8 || t.length > 200) return false;\n if (COMMENT_FORM.test(t) || LATEX_FORM.test(t)) return true;\n const div = DIV_FORM.exec(t);\n if (!div) return false;\n return DIV_BREAK_ATTR.test(div[1] ?? div[2] ?? \"\");\n}\n\n// ── Section break ─────────────────────────────────────────────────────────\n//\n// A section break is a page break that can also change the page layout for\n// everything after it (Word's \"Section break (next page)\"):\n//\n// <!-- section --> ← new page, same layout\n// <!-- section: landscape --> ← new page, landscape from here on\n// <!-- section landscape columns=2 --> ← landscape AND two columns\n// <!-- section portrait columns=1 --> ← back to the default layout\n//\n// Recognised words: `landscape` / `portrait`, `columns=N` (or `cols=N`,\n// `N columns`, 1–3). Unknown words are ignored, never an error — the break\n// still happens. Every page-break reader treats a section break as a break.\n\n/** The form editors insert for a plain section break. */\nexport const SECTION_BREAK_MARKDOWN = \"<!-- section -->\";\n\nexport interface SectionBreakDirective {\n /** Absent = inherit the document's orientation. */\n orientation?: \"portrait\" | \"landscape\";\n /** Absent = inherit the document's column count. 1–3. */\n columns?: 1 | 2 | 3;\n}\n\nconst SECTION_FORM = /^<!--\\s*section(?:[\\s_-]?break)?\\b\\s*:?\\s*([^>]*?)\\s*-->$/i;\n\n/** Parse a section-break line; `null` when the line is not one. */\nexport function parseSectionBreakLine(line: string): SectionBreakDirective | null {\n if (indentColumns(line) >= 4) return null;\n const m = SECTION_FORM.exec(line.trim());\n if (!m) return null;\n const words = (m[1] ?? \"\").toLowerCase();\n const out: SectionBreakDirective = {};\n if (/\\blandscape\\b/.test(words)) out.orientation = \"landscape\";\n else if (/\\bportrait\\b/.test(words)) out.orientation = \"portrait\";\n const cols = /\\b(?:columns?|cols?)\\s*[=:]\\s*(\\d)\\b/.exec(words) ?? /\\b(\\d)\\s*[-\\s]?columns?\\b/.exec(words);\n if (cols) {\n const n = Math.min(3, Math.max(1, Number(cols[1])));\n out.columns = n as 1 | 2 | 3;\n }\n return out;\n}\n\n// ── Table of contents / bibliography placement ───────────────────────────\n//\n// `[[toc]]`, `[toc]`, `[TOC]`, `${toc}`, `<!-- toc -->`, `\\tableofcontents`\n// put the table of contents at that spot (otherwise frontmatter `toc: true`\n// puts it at the start). `<!-- bibliography -->`, `[[bibliography]]`,\n// `\\printbibliography` put the reference list there (otherwise it closes the\n// document whenever something is cited).\n\nexport const TOC_MARKDOWN = \"[[toc]]\";\nexport const BIBLIOGRAPHY_MARKDOWN = \"<!-- bibliography -->\";\n\nconst TOC_FORM = /^(?:\\[\\[\\s*toc\\s*\\]\\]|\\[toc\\]|\\$\\{toc\\}|<!--\\s*toc\\s*-->|\\\\tableofcontents)$/i;\nconst BIBLIOGRAPHY_FORM =\n /^(?:\\[\\[\\s*(?:bibliography|references)\\s*\\]\\]|<!--\\s*(?:bibliography|references)\\s*-->|\\\\printbibliography|\\\\bibliography\\{[^}]*\\})$/i;\n\nexport function isTocLine(line: string): boolean {\n return indentColumns(line) < 4 && TOC_FORM.test(line.trim());\n}\n\nexport function isBibliographyLine(line: string): boolean {\n return indentColumns(line) < 4 && BIBLIOGRAPHY_FORM.test(line.trim());\n}\n\n/** Any directive that ends the current printed page (page or section break). */\nexport function isBreakLine(line: string): boolean {\n return isPageBreakLine(line) || parseSectionBreakLine(line) !== null;\n}\n\n/**\n * Split markdown into the pieces that belong on separate pages. Fence-aware:\n * a directive inside a ``` / ~~~ code block is code, not a break. Always\n * returns at least one piece; empty pieces (two breaks in a row, a break at\n * the very start or end) are kept out so no blank page is produced.\n */\nexport function splitAtPageBreaks(markdown: string): string[] {\n const lines = markdown.split(/\\r?\\n/);\n const pieces: string[] = [];\n let current: string[] = [];\n let fence: string | null = null;\n\n for (const line of lines) {\n // Any indent — the converters accept list-nested fences, so must this.\n const fenceMatch = /^\\s*(`{3,}|~{3,})/.exec(line);\n if (fenceMatch) {\n const marker = fenceMatch[1] as string;\n if (fence === null) fence = marker;\n else if (marker[0] === fence[0] && marker.length >= fence.length) fence = null;\n } else if (fence === null && isBreakLine(line)) {\n if (current.join(\"\\n\").trim()) pieces.push(current.join(\"\\n\"));\n current = [];\n continue;\n }\n current.push(line);\n }\n if (current.join(\"\\n\").trim() || pieces.length === 0) pieces.push(current.join(\"\\n\"));\n return pieces;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BO,IAAM,sBAAsB;AAG5B,IAAM,mBAAmB;AAGzB,IAAM,mBAAmB;AAEhC,IAAM,eAAe;AACrB,IAAM,aAAa;AACnB,IAAM,WAAW;AACjB,IAAM,iBACJ;AAGF,SAAS,cAAc,MAAsB;AAC3C,MAAI,OAAO;AACX,aAAW,MAAM,MAAM;AACrB,QAAI,OAAO,OAAO,OAAO,OAAU,SAAQ;AAAA,aAClC,OAAO,IAAM,SAAQ;AAAA,QACzB;AAAA,EACP;AACA,SAAO;AACT;AAMO,SAAS,gBAAgB,MAAuB;AACrD,MAAI,cAAc,IAAI,KAAK,EAAG,QAAO;AACrC,QAAM,IAAI,KAAK,KAAK;AACpB,MAAI,EAAE,SAAS,KAAK,EAAE,SAAS,IAAK,QAAO;AAC3C,MAAI,aAAa,KAAK,CAAC,KAAK,WAAW,KAAK,CAAC,EAAG,QAAO;AACvD,QAAM,MAAM,SAAS,KAAK,CAAC;AAC3B,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,eAAe,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE;AACnD;AAiBO,IAAM,yBAAyB;AAStC,IAAM,eAAe;AAGd,SAAS,sBAAsB,MAA4C;AAChF,MAAI,cAAc,IAAI,KAAK,EAAG,QAAO;AACrC,QAAM,IAAI,aAAa,KAAK,KAAK,KAAK,CAAC;AACvC,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,SAAS,EAAE,CAAC,KAAK,IAAI,YAAY;AACvC,QAAM,MAA6B,CAAC;AACpC,MAAI,gBAAgB,KAAK,KAAK,EAAG,KAAI,cAAc;AAAA,WAC1C,eAAe,KAAK,KAAK,EAAG,KAAI,cAAc;AACvD,QAAM,OAAO,uCAAuC,KAAK,KAAK,KAAK,4BAA4B,KAAK,KAAK;AACzG,MAAI,MAAM;AACR,UAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;AAClD,QAAI,UAAU;AAAA,EAChB;AACA,SAAO;AACT;AAUO,IAAM,eAAe;AACrB,IAAM,wBAAwB;AAErC,IAAM,WAAW;AACjB,IAAM,oBACJ;AAEK,SAAS,UAAU,MAAuB;AAC/C,SAAO,cAAc,IAAI,IAAI,KAAK,SAAS,KAAK,KAAK,KAAK,CAAC;AAC7D;AAEO,SAAS,mBAAmB,MAAuB;AACxD,SAAO,cAAc,IAAI,IAAI,KAAK,kBAAkB,KAAK,KAAK,KAAK,CAAC;AACtE;AAGO,SAAS,YAAY,MAAuB;AACjD,SAAO,gBAAgB,IAAI,KAAK,sBAAsB,IAAI,MAAM;AAClE;AAQO,SAAS,kBAAkB,UAA4B;AAC5D,QAAM,QAAQ,SAAS,MAAM,OAAO;AACpC,QAAM,SAAmB,CAAC;AAC1B,MAAI,UAAoB,CAAC;AACzB,MAAI,QAAuB;AAE3B,aAAW,QAAQ,OAAO;AAExB,UAAM,aAAa,oBAAoB,KAAK,IAAI;AAChD,QAAI,YAAY;AACd,YAAM,SAAS,WAAW,CAAC;AAC3B,UAAI,UAAU,KAAM,SAAQ;AAAA,eACnB,OAAO,CAAC,MAAM,MAAM,CAAC,KAAK,OAAO,UAAU,MAAM,OAAQ,SAAQ;AAAA,IAC5E,WAAW,UAAU,QAAQ,YAAY,IAAI,GAAG;AAC9C,UAAI,QAAQ,KAAK,IAAI,EAAE,KAAK,EAAG,QAAO,KAAK,QAAQ,KAAK,IAAI,CAAC;AAC7D,gBAAU,CAAC;AACX;AAAA,IACF;AACA,YAAQ,KAAK,IAAI;AAAA,EACnB;AACA,MAAI,QAAQ,KAAK,IAAI,EAAE,KAAK,KAAK,OAAO,WAAW,EAAG,QAAO,KAAK,QAAQ,KAAK,IAAI,CAAC;AACpF,SAAO;AACT;","names":[]}
|
package/dist/directives.d.cts
CHANGED
|
@@ -35,6 +35,22 @@ declare const PAGE_BREAK_LABEL = "Page break";
|
|
|
35
35
|
* it is not — in markdown that is a code block, and every surface must agree.
|
|
36
36
|
*/
|
|
37
37
|
declare function isPageBreakLine(line: string): boolean;
|
|
38
|
+
/** The form editors insert for a plain section break. */
|
|
39
|
+
declare const SECTION_BREAK_MARKDOWN = "<!-- section -->";
|
|
40
|
+
interface SectionBreakDirective {
|
|
41
|
+
/** Absent = inherit the document's orientation. */
|
|
42
|
+
orientation?: "portrait" | "landscape";
|
|
43
|
+
/** Absent = inherit the document's column count. 1–3. */
|
|
44
|
+
columns?: 1 | 2 | 3;
|
|
45
|
+
}
|
|
46
|
+
/** Parse a section-break line; `null` when the line is not one. */
|
|
47
|
+
declare function parseSectionBreakLine(line: string): SectionBreakDirective | null;
|
|
48
|
+
declare const TOC_MARKDOWN = "[[toc]]";
|
|
49
|
+
declare const BIBLIOGRAPHY_MARKDOWN = "<!-- bibliography -->";
|
|
50
|
+
declare function isTocLine(line: string): boolean;
|
|
51
|
+
declare function isBibliographyLine(line: string): boolean;
|
|
52
|
+
/** Any directive that ends the current printed page (page or section break). */
|
|
53
|
+
declare function isBreakLine(line: string): boolean;
|
|
38
54
|
/**
|
|
39
55
|
* Split markdown into the pieces that belong on separate pages. Fence-aware:
|
|
40
56
|
* a directive inside a ``` / ~~~ code block is code, not a break. Always
|
|
@@ -43,4 +59,4 @@ declare function isPageBreakLine(line: string): boolean;
|
|
|
43
59
|
*/
|
|
44
60
|
declare function splitAtPageBreaks(markdown: string): string[];
|
|
45
61
|
|
|
46
|
-
export { PAGE_BREAK_CLASS, PAGE_BREAK_LABEL, PAGE_BREAK_MARKDOWN, isPageBreakLine, splitAtPageBreaks };
|
|
62
|
+
export { BIBLIOGRAPHY_MARKDOWN, PAGE_BREAK_CLASS, PAGE_BREAK_LABEL, PAGE_BREAK_MARKDOWN, SECTION_BREAK_MARKDOWN, type SectionBreakDirective, TOC_MARKDOWN, isBibliographyLine, isBreakLine, isPageBreakLine, isTocLine, parseSectionBreakLine, splitAtPageBreaks };
|
package/dist/directives.d.ts
CHANGED
|
@@ -35,6 +35,22 @@ declare const PAGE_BREAK_LABEL = "Page break";
|
|
|
35
35
|
* it is not — in markdown that is a code block, and every surface must agree.
|
|
36
36
|
*/
|
|
37
37
|
declare function isPageBreakLine(line: string): boolean;
|
|
38
|
+
/** The form editors insert for a plain section break. */
|
|
39
|
+
declare const SECTION_BREAK_MARKDOWN = "<!-- section -->";
|
|
40
|
+
interface SectionBreakDirective {
|
|
41
|
+
/** Absent = inherit the document's orientation. */
|
|
42
|
+
orientation?: "portrait" | "landscape";
|
|
43
|
+
/** Absent = inherit the document's column count. 1–3. */
|
|
44
|
+
columns?: 1 | 2 | 3;
|
|
45
|
+
}
|
|
46
|
+
/** Parse a section-break line; `null` when the line is not one. */
|
|
47
|
+
declare function parseSectionBreakLine(line: string): SectionBreakDirective | null;
|
|
48
|
+
declare const TOC_MARKDOWN = "[[toc]]";
|
|
49
|
+
declare const BIBLIOGRAPHY_MARKDOWN = "<!-- bibliography -->";
|
|
50
|
+
declare function isTocLine(line: string): boolean;
|
|
51
|
+
declare function isBibliographyLine(line: string): boolean;
|
|
52
|
+
/** Any directive that ends the current printed page (page or section break). */
|
|
53
|
+
declare function isBreakLine(line: string): boolean;
|
|
38
54
|
/**
|
|
39
55
|
* Split markdown into the pieces that belong on separate pages. Fence-aware:
|
|
40
56
|
* a directive inside a ``` / ~~~ code block is code, not a break. Always
|
|
@@ -43,4 +59,4 @@ declare function isPageBreakLine(line: string): boolean;
|
|
|
43
59
|
*/
|
|
44
60
|
declare function splitAtPageBreaks(markdown: string): string[];
|
|
45
61
|
|
|
46
|
-
export { PAGE_BREAK_CLASS, PAGE_BREAK_LABEL, PAGE_BREAK_MARKDOWN, isPageBreakLine, splitAtPageBreaks };
|
|
62
|
+
export { BIBLIOGRAPHY_MARKDOWN, PAGE_BREAK_CLASS, PAGE_BREAK_LABEL, PAGE_BREAK_MARKDOWN, SECTION_BREAK_MARKDOWN, type SectionBreakDirective, TOC_MARKDOWN, isBibliographyLine, isBreakLine, isPageBreakLine, isTocLine, parseSectionBreakLine, splitAtPageBreaks };
|
package/dist/directives.js
CHANGED
|
@@ -24,6 +24,36 @@ function isPageBreakLine(line) {
|
|
|
24
24
|
if (!div) return false;
|
|
25
25
|
return DIV_BREAK_ATTR.test(div[1] ?? div[2] ?? "");
|
|
26
26
|
}
|
|
27
|
+
var SECTION_BREAK_MARKDOWN = "<!-- section -->";
|
|
28
|
+
var SECTION_FORM = /^<!--\s*section(?:[\s_-]?break)?\b\s*:?\s*([^>]*?)\s*-->$/i;
|
|
29
|
+
function parseSectionBreakLine(line) {
|
|
30
|
+
if (indentColumns(line) >= 4) return null;
|
|
31
|
+
const m = SECTION_FORM.exec(line.trim());
|
|
32
|
+
if (!m) return null;
|
|
33
|
+
const words = (m[1] ?? "").toLowerCase();
|
|
34
|
+
const out = {};
|
|
35
|
+
if (/\blandscape\b/.test(words)) out.orientation = "landscape";
|
|
36
|
+
else if (/\bportrait\b/.test(words)) out.orientation = "portrait";
|
|
37
|
+
const cols = /\b(?:columns?|cols?)\s*[=:]\s*(\d)\b/.exec(words) ?? /\b(\d)\s*[-\s]?columns?\b/.exec(words);
|
|
38
|
+
if (cols) {
|
|
39
|
+
const n = Math.min(3, Math.max(1, Number(cols[1])));
|
|
40
|
+
out.columns = n;
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
var TOC_MARKDOWN = "[[toc]]";
|
|
45
|
+
var BIBLIOGRAPHY_MARKDOWN = "<!-- bibliography -->";
|
|
46
|
+
var TOC_FORM = /^(?:\[\[\s*toc\s*\]\]|\[toc\]|\$\{toc\}|<!--\s*toc\s*-->|\\tableofcontents)$/i;
|
|
47
|
+
var BIBLIOGRAPHY_FORM = /^(?:\[\[\s*(?:bibliography|references)\s*\]\]|<!--\s*(?:bibliography|references)\s*-->|\\printbibliography|\\bibliography\{[^}]*\})$/i;
|
|
48
|
+
function isTocLine(line) {
|
|
49
|
+
return indentColumns(line) < 4 && TOC_FORM.test(line.trim());
|
|
50
|
+
}
|
|
51
|
+
function isBibliographyLine(line) {
|
|
52
|
+
return indentColumns(line) < 4 && BIBLIOGRAPHY_FORM.test(line.trim());
|
|
53
|
+
}
|
|
54
|
+
function isBreakLine(line) {
|
|
55
|
+
return isPageBreakLine(line) || parseSectionBreakLine(line) !== null;
|
|
56
|
+
}
|
|
27
57
|
function splitAtPageBreaks(markdown) {
|
|
28
58
|
const lines = markdown.split(/\r?\n/);
|
|
29
59
|
const pieces = [];
|
|
@@ -35,7 +65,7 @@ function splitAtPageBreaks(markdown) {
|
|
|
35
65
|
const marker = fenceMatch[1];
|
|
36
66
|
if (fence === null) fence = marker;
|
|
37
67
|
else if (marker[0] === fence[0] && marker.length >= fence.length) fence = null;
|
|
38
|
-
} else if (fence === null &&
|
|
68
|
+
} else if (fence === null && isBreakLine(line)) {
|
|
39
69
|
if (current.join("\n").trim()) pieces.push(current.join("\n"));
|
|
40
70
|
current = [];
|
|
41
71
|
continue;
|
|
@@ -46,10 +76,17 @@ function splitAtPageBreaks(markdown) {
|
|
|
46
76
|
return pieces;
|
|
47
77
|
}
|
|
48
78
|
export {
|
|
79
|
+
BIBLIOGRAPHY_MARKDOWN,
|
|
49
80
|
PAGE_BREAK_CLASS,
|
|
50
81
|
PAGE_BREAK_LABEL,
|
|
51
82
|
PAGE_BREAK_MARKDOWN,
|
|
83
|
+
SECTION_BREAK_MARKDOWN,
|
|
84
|
+
TOC_MARKDOWN,
|
|
85
|
+
isBibliographyLine,
|
|
86
|
+
isBreakLine,
|
|
52
87
|
isPageBreakLine,
|
|
88
|
+
isTocLine,
|
|
89
|
+
parseSectionBreakLine,
|
|
53
90
|
splitAtPageBreaks
|
|
54
91
|
};
|
|
55
92
|
//# sourceMappingURL=directives.js.map
|
package/dist/directives.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/directives.ts"],"sourcesContent":["/**\n * @ai-matrx/print/directives — the ONE definition of the print directives\n * markdown can carry, shared by every surface that reads markdown.\n *\n * Why a separate, zero-dependency entry: the directive syntax has three\n * readers — this package's two converters (print window / PDF / HTML page),\n * the chat renderer's remark plugin (the on-screen preview), and any editor\n * that inserts one. If each re-derived the regex they would drift the first\n * time someone added an alias. `/markdown` carries ~20KB of stylesheet, so the\n * chat bundle imports the syntax from here instead and pays nothing.\n *\n * ── Page break ────────────────────────────────────────────────────────────\n * A line that is exactly one of these (case-insensitive, surrounding\n * whitespace ignored) forces the next content onto a new printed page:\n *\n * <!-- pagebreak --> ← canonical; invisible in any other renderer\n * <!-- page-break --> <!-- page break --> <!-- newpage -->\n * \\pagebreak \\newpage ← LaTeX / pandoc habit\n * <div style=\"page-break-after: always\"></div> ← Typora / VS Code habit\n * (also `page-break-before`, `break-after: page`, `break-before: page`,\n * and `<div class=\"page-break\"></div>` / `class=\"pagebreak\"`)\n *\n * On screen every surface draws the same idea: a dashed rule labelled\n * \"Page break\" with generous padding. In print it is a zero-height element\n * with `break-after: page`.\n */\n\n/** The form editors insert and docs recommend. */\nexport const PAGE_BREAK_MARKDOWN = \"<!-- pagebreak -->\";\n\n/** The class every surface puts on a rendered page break. */\nexport const PAGE_BREAK_CLASS = \"matrx-page-break\";\n\n/** Screen label for a rendered page break. */\nexport const PAGE_BREAK_LABEL = \"Page break\";\n\nconst COMMENT_FORM = /^<!--\\s*(?:page[\\s_-]?break|new[\\s_-]?page)\\s*-->$/i;\nconst LATEX_FORM = /^\\\\(?:pagebreak|newpage|clearpage)\\s*(?:\\{\\s*\\})?$/i;\nconst DIV_FORM = /^<div\\b([^>]*)>\\s*<\\/div>$|^<div\\b([^>]*)\\/>$/i;\nconst DIV_BREAK_ATTR =\n /(?:page-break-(?:after|before)\\s*:\\s*always|break-(?:after|before)\\s*:\\s*page|class\\s*=\\s*[\"'](?:[^\"']*\\s)?page-?break(?:\\s[^\"']*)?[\"'])/i;\n\n/** Columns of leading indentation (tab = 4, NBSP counts as a space). */\nfunction indentColumns(line: string): number {\n let cols = 0;\n for (const ch of line) {\n if (ch === \" \" || ch === \"\\u00A0\") cols += 1;\n else if (ch === \"\\t\") cols += 4;\n else break;\n }\n return cols;\n}\n\n/**\n * Is this single line a page-break directive? Indented four or more columns\n * it is not — in markdown that is a code block, and every surface must agree.\n */\nexport function isPageBreakLine(line: string): boolean {\n if (indentColumns(line) >= 4) return false;\n const t = line.trim();\n if (t.length < 8 || t.length > 200) return false;\n if (COMMENT_FORM.test(t) || LATEX_FORM.test(t)) return true;\n const div = DIV_FORM.exec(t);\n if (!div) return false;\n return DIV_BREAK_ATTR.test(div[1] ?? div[2] ?? \"\");\n}\n\n/**\n * Split markdown into the pieces that belong on separate pages. Fence-aware:\n * a directive inside a ``` / ~~~ code block is code, not a break. Always\n * returns at least one piece; empty pieces (two breaks in a row, a break at\n * the very start or end) are kept out so no blank page is produced.\n */\nexport function splitAtPageBreaks(markdown: string): string[] {\n const lines = markdown.split(/\\r?\\n/);\n const pieces: string[] = [];\n let current: string[] = [];\n let fence: string | null = null;\n\n for (const line of lines) {\n // Any indent — the converters accept list-nested fences, so must this.\n const fenceMatch = /^\\s*(`{3,}|~{3,})/.exec(line);\n if (fenceMatch) {\n const marker = fenceMatch[1] as string;\n if (fence === null) fence = marker;\n else if (marker[0] === fence[0] && marker.length >= fence.length) fence = null;\n } else if (fence === null &&
|
|
1
|
+
{"version":3,"sources":["../src/directives.ts"],"sourcesContent":["/**\n * @ai-matrx/print/directives — the ONE definition of the print directives\n * markdown can carry, shared by every surface that reads markdown.\n *\n * Why a separate, zero-dependency entry: the directive syntax has three\n * readers — this package's two converters (print window / PDF / HTML page),\n * the chat renderer's remark plugin (the on-screen preview), and any editor\n * that inserts one. If each re-derived the regex they would drift the first\n * time someone added an alias. `/markdown` carries ~20KB of stylesheet, so the\n * chat bundle imports the syntax from here instead and pays nothing.\n *\n * ── Page break ────────────────────────────────────────────────────────────\n * A line that is exactly one of these (case-insensitive, surrounding\n * whitespace ignored) forces the next content onto a new printed page:\n *\n * <!-- pagebreak --> ← canonical; invisible in any other renderer\n * <!-- page-break --> <!-- page break --> <!-- newpage -->\n * \\pagebreak \\newpage ← LaTeX / pandoc habit\n * <div style=\"page-break-after: always\"></div> ← Typora / VS Code habit\n * (also `page-break-before`, `break-after: page`, `break-before: page`,\n * and `<div class=\"page-break\"></div>` / `class=\"pagebreak\"`)\n *\n * On screen every surface draws the same idea: a dashed rule labelled\n * \"Page break\" with generous padding. In print it is a zero-height element\n * with `break-after: page`.\n */\n\n/** The form editors insert and docs recommend. */\nexport const PAGE_BREAK_MARKDOWN = \"<!-- pagebreak -->\";\n\n/** The class every surface puts on a rendered page break. */\nexport const PAGE_BREAK_CLASS = \"matrx-page-break\";\n\n/** Screen label for a rendered page break. */\nexport const PAGE_BREAK_LABEL = \"Page break\";\n\nconst COMMENT_FORM = /^<!--\\s*(?:page[\\s_-]?break|new[\\s_-]?page)\\s*-->$/i;\nconst LATEX_FORM = /^\\\\(?:pagebreak|newpage|clearpage)\\s*(?:\\{\\s*\\})?$/i;\nconst DIV_FORM = /^<div\\b([^>]*)>\\s*<\\/div>$|^<div\\b([^>]*)\\/>$/i;\nconst DIV_BREAK_ATTR =\n /(?:page-break-(?:after|before)\\s*:\\s*always|break-(?:after|before)\\s*:\\s*page|class\\s*=\\s*[\"'](?:[^\"']*\\s)?page-?break(?:\\s[^\"']*)?[\"'])/i;\n\n/** Columns of leading indentation (tab = 4, NBSP counts as a space). */\nfunction indentColumns(line: string): number {\n let cols = 0;\n for (const ch of line) {\n if (ch === \" \" || ch === \"\\u00A0\") cols += 1;\n else if (ch === \"\\t\") cols += 4;\n else break;\n }\n return cols;\n}\n\n/**\n * Is this single line a page-break directive? Indented four or more columns\n * it is not — in markdown that is a code block, and every surface must agree.\n */\nexport function isPageBreakLine(line: string): boolean {\n if (indentColumns(line) >= 4) return false;\n const t = line.trim();\n if (t.length < 8 || t.length > 200) return false;\n if (COMMENT_FORM.test(t) || LATEX_FORM.test(t)) return true;\n const div = DIV_FORM.exec(t);\n if (!div) return false;\n return DIV_BREAK_ATTR.test(div[1] ?? div[2] ?? \"\");\n}\n\n// ── Section break ─────────────────────────────────────────────────────────\n//\n// A section break is a page break that can also change the page layout for\n// everything after it (Word's \"Section break (next page)\"):\n//\n// <!-- section --> ← new page, same layout\n// <!-- section: landscape --> ← new page, landscape from here on\n// <!-- section landscape columns=2 --> ← landscape AND two columns\n// <!-- section portrait columns=1 --> ← back to the default layout\n//\n// Recognised words: `landscape` / `portrait`, `columns=N` (or `cols=N`,\n// `N columns`, 1–3). Unknown words are ignored, never an error — the break\n// still happens. Every page-break reader treats a section break as a break.\n\n/** The form editors insert for a plain section break. */\nexport const SECTION_BREAK_MARKDOWN = \"<!-- section -->\";\n\nexport interface SectionBreakDirective {\n /** Absent = inherit the document's orientation. */\n orientation?: \"portrait\" | \"landscape\";\n /** Absent = inherit the document's column count. 1–3. */\n columns?: 1 | 2 | 3;\n}\n\nconst SECTION_FORM = /^<!--\\s*section(?:[\\s_-]?break)?\\b\\s*:?\\s*([^>]*?)\\s*-->$/i;\n\n/** Parse a section-break line; `null` when the line is not one. */\nexport function parseSectionBreakLine(line: string): SectionBreakDirective | null {\n if (indentColumns(line) >= 4) return null;\n const m = SECTION_FORM.exec(line.trim());\n if (!m) return null;\n const words = (m[1] ?? \"\").toLowerCase();\n const out: SectionBreakDirective = {};\n if (/\\blandscape\\b/.test(words)) out.orientation = \"landscape\";\n else if (/\\bportrait\\b/.test(words)) out.orientation = \"portrait\";\n const cols = /\\b(?:columns?|cols?)\\s*[=:]\\s*(\\d)\\b/.exec(words) ?? /\\b(\\d)\\s*[-\\s]?columns?\\b/.exec(words);\n if (cols) {\n const n = Math.min(3, Math.max(1, Number(cols[1])));\n out.columns = n as 1 | 2 | 3;\n }\n return out;\n}\n\n// ── Table of contents / bibliography placement ───────────────────────────\n//\n// `[[toc]]`, `[toc]`, `[TOC]`, `${toc}`, `<!-- toc -->`, `\\tableofcontents`\n// put the table of contents at that spot (otherwise frontmatter `toc: true`\n// puts it at the start). `<!-- bibliography -->`, `[[bibliography]]`,\n// `\\printbibliography` put the reference list there (otherwise it closes the\n// document whenever something is cited).\n\nexport const TOC_MARKDOWN = \"[[toc]]\";\nexport const BIBLIOGRAPHY_MARKDOWN = \"<!-- bibliography -->\";\n\nconst TOC_FORM = /^(?:\\[\\[\\s*toc\\s*\\]\\]|\\[toc\\]|\\$\\{toc\\}|<!--\\s*toc\\s*-->|\\\\tableofcontents)$/i;\nconst BIBLIOGRAPHY_FORM =\n /^(?:\\[\\[\\s*(?:bibliography|references)\\s*\\]\\]|<!--\\s*(?:bibliography|references)\\s*-->|\\\\printbibliography|\\\\bibliography\\{[^}]*\\})$/i;\n\nexport function isTocLine(line: string): boolean {\n return indentColumns(line) < 4 && TOC_FORM.test(line.trim());\n}\n\nexport function isBibliographyLine(line: string): boolean {\n return indentColumns(line) < 4 && BIBLIOGRAPHY_FORM.test(line.trim());\n}\n\n/** Any directive that ends the current printed page (page or section break). */\nexport function isBreakLine(line: string): boolean {\n return isPageBreakLine(line) || parseSectionBreakLine(line) !== null;\n}\n\n/**\n * Split markdown into the pieces that belong on separate pages. Fence-aware:\n * a directive inside a ``` / ~~~ code block is code, not a break. Always\n * returns at least one piece; empty pieces (two breaks in a row, a break at\n * the very start or end) are kept out so no blank page is produced.\n */\nexport function splitAtPageBreaks(markdown: string): string[] {\n const lines = markdown.split(/\\r?\\n/);\n const pieces: string[] = [];\n let current: string[] = [];\n let fence: string | null = null;\n\n for (const line of lines) {\n // Any indent — the converters accept list-nested fences, so must this.\n const fenceMatch = /^\\s*(`{3,}|~{3,})/.exec(line);\n if (fenceMatch) {\n const marker = fenceMatch[1] as string;\n if (fence === null) fence = marker;\n else if (marker[0] === fence[0] && marker.length >= fence.length) fence = null;\n } else if (fence === null && isBreakLine(line)) {\n if (current.join(\"\\n\").trim()) pieces.push(current.join(\"\\n\"));\n current = [];\n continue;\n }\n current.push(line);\n }\n if (current.join(\"\\n\").trim() || pieces.length === 0) pieces.push(current.join(\"\\n\"));\n return pieces;\n}\n"],"mappings":";AA4BO,IAAM,sBAAsB;AAG5B,IAAM,mBAAmB;AAGzB,IAAM,mBAAmB;AAEhC,IAAM,eAAe;AACrB,IAAM,aAAa;AACnB,IAAM,WAAW;AACjB,IAAM,iBACJ;AAGF,SAAS,cAAc,MAAsB;AAC3C,MAAI,OAAO;AACX,aAAW,MAAM,MAAM;AACrB,QAAI,OAAO,OAAO,OAAO,OAAU,SAAQ;AAAA,aAClC,OAAO,IAAM,SAAQ;AAAA,QACzB;AAAA,EACP;AACA,SAAO;AACT;AAMO,SAAS,gBAAgB,MAAuB;AACrD,MAAI,cAAc,IAAI,KAAK,EAAG,QAAO;AACrC,QAAM,IAAI,KAAK,KAAK;AACpB,MAAI,EAAE,SAAS,KAAK,EAAE,SAAS,IAAK,QAAO;AAC3C,MAAI,aAAa,KAAK,CAAC,KAAK,WAAW,KAAK,CAAC,EAAG,QAAO;AACvD,QAAM,MAAM,SAAS,KAAK,CAAC;AAC3B,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,eAAe,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE;AACnD;AAiBO,IAAM,yBAAyB;AAStC,IAAM,eAAe;AAGd,SAAS,sBAAsB,MAA4C;AAChF,MAAI,cAAc,IAAI,KAAK,EAAG,QAAO;AACrC,QAAM,IAAI,aAAa,KAAK,KAAK,KAAK,CAAC;AACvC,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,SAAS,EAAE,CAAC,KAAK,IAAI,YAAY;AACvC,QAAM,MAA6B,CAAC;AACpC,MAAI,gBAAgB,KAAK,KAAK,EAAG,KAAI,cAAc;AAAA,WAC1C,eAAe,KAAK,KAAK,EAAG,KAAI,cAAc;AACvD,QAAM,OAAO,uCAAuC,KAAK,KAAK,KAAK,4BAA4B,KAAK,KAAK;AACzG,MAAI,MAAM;AACR,UAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;AAClD,QAAI,UAAU;AAAA,EAChB;AACA,SAAO;AACT;AAUO,IAAM,eAAe;AACrB,IAAM,wBAAwB;AAErC,IAAM,WAAW;AACjB,IAAM,oBACJ;AAEK,SAAS,UAAU,MAAuB;AAC/C,SAAO,cAAc,IAAI,IAAI,KAAK,SAAS,KAAK,KAAK,KAAK,CAAC;AAC7D;AAEO,SAAS,mBAAmB,MAAuB;AACxD,SAAO,cAAc,IAAI,IAAI,KAAK,kBAAkB,KAAK,KAAK,KAAK,CAAC;AACtE;AAGO,SAAS,YAAY,MAAuB;AACjD,SAAO,gBAAgB,IAAI,KAAK,sBAAsB,IAAI,MAAM;AAClE;AAQO,SAAS,kBAAkB,UAA4B;AAC5D,QAAM,QAAQ,SAAS,MAAM,OAAO;AACpC,QAAM,SAAmB,CAAC;AAC1B,MAAI,UAAoB,CAAC;AACzB,MAAI,QAAuB;AAE3B,aAAW,QAAQ,OAAO;AAExB,UAAM,aAAa,oBAAoB,KAAK,IAAI;AAChD,QAAI,YAAY;AACd,YAAM,SAAS,WAAW,CAAC;AAC3B,UAAI,UAAU,KAAM,SAAQ;AAAA,eACnB,OAAO,CAAC,MAAM,MAAM,CAAC,KAAK,OAAO,UAAU,MAAM,OAAQ,SAAQ;AAAA,IAC5E,WAAW,UAAU,QAAQ,YAAY,IAAI,GAAG;AAC9C,UAAI,QAAQ,KAAK,IAAI,EAAE,KAAK,EAAG,QAAO,KAAK,QAAQ,KAAK,IAAI,CAAC;AAC7D,gBAAU,CAAC;AACX;AAAA,IACF;AACA,YAAQ,KAAK,IAAI;AAAA,EACnB;AACA,MAAI,QAAQ,KAAK,IAAI,EAAE,KAAK,KAAK,OAAO,WAAW,EAAG,QAAO,KAAK,QAAQ,KAAK,IAAI,CAAC;AACpF,SAAO;AACT;","names":[]}
|