@ai-matrx/print 0.6.1 → 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.
@@ -0,0 +1,490 @@
1
+ /**
2
+ * The document model every output of `@ai-matrx/print/document` is rendered
3
+ * from. ONE parsed tree → PDF, DOCX, EPUB, HTML — so the four always agree on
4
+ * page layout, numbering, captions, cross-references and citations.
5
+ */
6
+ type PageSizeName = "letter" | "legal" | "tabloid" | "executive" | "a3" | "a4" | "a5" | "b5";
7
+ type Orientation = "portrait" | "landscape";
8
+ /** All lengths are PostScript points (1in = 72pt). */
9
+ interface Margins {
10
+ top: number;
11
+ right: number;
12
+ bottom: number;
13
+ left: number;
14
+ }
15
+ type CitationStyle = "apa" | "mla" | "chicago";
16
+ /**
17
+ * A running header or footer. Each slot is a template: `{page}`, `{pages}`,
18
+ * `{title}`, `{subtitle}`, `{author}`, `{date}` are substituted per page.
19
+ */
20
+ interface RunningTemplate {
21
+ left?: string | undefined;
22
+ center?: string | undefined;
23
+ right?: string | undefined;
24
+ }
25
+ /** CSL-JSON name (the citation-style-language standard item shape). */
26
+ interface CslName {
27
+ family?: string | undefined;
28
+ given?: string | undefined;
29
+ literal?: string | undefined;
30
+ }
31
+ /** A CSL-JSON reference item (the subset the three built-in styles read). */
32
+ interface CslItem {
33
+ id: string;
34
+ type?: string | undefined;
35
+ title?: string | undefined;
36
+ author?: CslName[] | undefined;
37
+ editor?: CslName[] | undefined;
38
+ issued?: {
39
+ "date-parts"?: Array<Array<number | string>> | undefined;
40
+ literal?: string | undefined;
41
+ } | undefined;
42
+ "container-title"?: string | undefined;
43
+ publisher?: string | undefined;
44
+ "publisher-place"?: string | undefined;
45
+ volume?: string | number | undefined;
46
+ issue?: string | number | undefined;
47
+ page?: string | undefined;
48
+ edition?: string | number | undefined;
49
+ URL?: string | undefined;
50
+ DOI?: string | undefined;
51
+ }
52
+ interface DocumentSettings {
53
+ title?: string | undefined;
54
+ subtitle?: string | undefined;
55
+ author?: string | undefined;
56
+ date?: string | undefined;
57
+ /** Durable http(s) or data: URL for the cover logo. */
58
+ logo?: string | undefined;
59
+ lang: string;
60
+ page: {
61
+ size: PageSizeName;
62
+ /** Portrait width/height in points for `size`. */
63
+ width: number;
64
+ height: number;
65
+ orientation: Orientation;
66
+ margins: Margins;
67
+ };
68
+ cover: boolean;
69
+ toc: {
70
+ enabled: boolean;
71
+ depth: number;
72
+ title: string;
73
+ };
74
+ header: RunningTemplate | null;
75
+ footer: RunningTemplate | null;
76
+ /** No running header/footer on page 1 (always true with a cover page). */
77
+ differentFirstPage: boolean;
78
+ columns: 1 | 2 | 3;
79
+ numberSections: boolean;
80
+ citationStyle: CitationStyle;
81
+ bibliographyTitle: string;
82
+ references: CslItem[];
83
+ hyphenate: boolean;
84
+ /** Body size in points. */
85
+ fontSize: number;
86
+ }
87
+ interface DocumentNotice {
88
+ code: "frontmatter-invalid" | "setting-ignored" | "xref-unresolved" | "citation-unresolved" | "html-stripped" | "image-unavailable" | "glyph-substituted" | "empty-document";
89
+ message: string;
90
+ remedy: string;
91
+ }
92
+ type Inline = {
93
+ type: "text";
94
+ value: string;
95
+ } | {
96
+ type: "strong" | "emphasis" | "delete";
97
+ children: Inline[];
98
+ } | {
99
+ type: "code";
100
+ value: string;
101
+ } | {
102
+ type: "link";
103
+ url: string;
104
+ children: Inline[];
105
+ } | {
106
+ type: "break";
107
+ } | {
108
+ type: "image";
109
+ url: string;
110
+ alt: string;
111
+ }
112
+ /** `@fig:x` / `@tbl:x` / `@sec:x` — `label` is final text ("Figure 2"). */
113
+ | {
114
+ type: "xref";
115
+ target: string;
116
+ label: string;
117
+ resolved: boolean;
118
+ }
119
+ /** `[@key]` — `label` is the formatted in-text citation. */
120
+ | {
121
+ type: "cite";
122
+ keys: string[];
123
+ label: string;
124
+ resolved: boolean;
125
+ } | {
126
+ type: "noteRef";
127
+ n: number;
128
+ };
129
+ interface Caption {
130
+ kind: "fig" | "tbl";
131
+ /** Author id without prefix ("growth" for `{#fig:growth}`), if any. */
132
+ id?: string | undefined;
133
+ number: number;
134
+ /** "Figure 3" / "Table 1". */
135
+ label: string;
136
+ children: Inline[];
137
+ }
138
+ type Block = {
139
+ type: "heading";
140
+ level: number;
141
+ id: string;
142
+ /** "2.1" when section numbering is on. */
143
+ number?: string | undefined;
144
+ plain: string;
145
+ children: Inline[];
146
+ /** false for `{-}` / `{.unnumbered}` headings and the bibliography. */
147
+ numbered: boolean;
148
+ } | {
149
+ type: "paragraph";
150
+ children: Inline[];
151
+ } | {
152
+ type: "list";
153
+ ordered: boolean;
154
+ start: number;
155
+ items: ListItem[];
156
+ } | {
157
+ type: "blockquote";
158
+ children: Block[];
159
+ } | {
160
+ type: "code";
161
+ lang: string;
162
+ value: string;
163
+ } | {
164
+ type: "table";
165
+ align: Array<"left" | "center" | "right" | null>;
166
+ header: Inline[][];
167
+ rows: Inline[][][];
168
+ caption?: Caption | undefined;
169
+ } | {
170
+ type: "figure";
171
+ url: string;
172
+ alt: string;
173
+ caption?: Caption | undefined;
174
+ } | {
175
+ type: "rule";
176
+ } | {
177
+ type: "pageBreak";
178
+ } | {
179
+ type: "toc";
180
+ } | {
181
+ type: "bibliography";
182
+ } | {
183
+ type: "notes";
184
+ };
185
+ interface ListItem {
186
+ checked: boolean | null;
187
+ children: Block[];
188
+ }
189
+ interface DocSection {
190
+ orientation: Orientation;
191
+ columns: 1 | 2 | 3;
192
+ blocks: Block[];
193
+ }
194
+ interface TocEntry {
195
+ id: string;
196
+ level: number;
197
+ /** Number + title, as printed ("2.1 Methods"). */
198
+ text: string;
199
+ }
200
+ interface BibliographyEntry {
201
+ id: string;
202
+ children: Inline[];
203
+ }
204
+ interface DocTree {
205
+ settings: DocumentSettings;
206
+ /** The markdown body (frontmatter removed). */
207
+ body: string;
208
+ sections: DocSection[];
209
+ toc: TocEntry[];
210
+ figures: Caption[];
211
+ tables: Caption[];
212
+ bibliography: BibliographyEntry[];
213
+ notes: Array<{
214
+ n: number;
215
+ children: Block[];
216
+ }>;
217
+ notices: DocumentNotice[];
218
+ }
219
+ /** An image fetched once and shared by every renderer. */
220
+ interface ResolvedImage {
221
+ dataUrl: string;
222
+ bytes: Uint8Array;
223
+ mime: "image/png" | "image/jpeg" | "image/gif";
224
+ width: number;
225
+ height: number;
226
+ }
227
+ type ImageMap = Map<string, ResolvedImage | null>;
228
+
229
+ /**
230
+ * Document settings carried in the markdown source's YAML frontmatter.
231
+ *
232
+ * Grammar: the source's FIRST line is exactly `---`; the block ends at the
233
+ * next line that is exactly `---` or `...`; the lines between are YAML 1.2
234
+ * (core schema — `2026-09-25` stays a string). A leading `---` with no closing
235
+ * line is a thematic break, not frontmatter. This is the same rule pandoc,
236
+ * Jekyll, Hugo and Obsidian use.
237
+ *
238
+ * Keys follow pandoc's names where pandoc has one (`title`, `subtitle`,
239
+ * `author`, `date`, `lang`, `papersize`, `geometry`, `toc`, `toc-depth`,
240
+ * `numbersections`, `csl`, `references`, `fontsize`) and accept camelCase /
241
+ * snake_case / kebab-case spellings of every key. Unknown keys are ignored
242
+ * silently (frontmatter is shared with other tools); a KNOWN key with a value
243
+ * we cannot use is announced with the remedy.
244
+ */
245
+
246
+ /** Portrait page sizes in points. */
247
+ declare const PAGE_SIZES: Readonly<Record<PageSizeName, {
248
+ width: number;
249
+ height: number;
250
+ label: string;
251
+ }>>;
252
+ declare const DEFAULT_FOOTER: RunningTemplate;
253
+ interface FrontmatterSplit {
254
+ /** Parsed YAML mapping (empty when there is no frontmatter). */
255
+ data: Record<string, unknown>;
256
+ /** The markdown after the frontmatter block. */
257
+ body: string;
258
+ /** The raw YAML text, or null when the source has no frontmatter. */
259
+ raw: string | null;
260
+ }
261
+ /** Split `---` YAML frontmatter off a markdown source. Never throws. */
262
+ declare function splitFrontmatter(source: string, notices?: DocumentNotice[]): FrontmatterSplit;
263
+ /** A length: `1in`, `2.5cm`, `25mm`, `72pt`, `96px`, or a bare number (inches). */
264
+ declare function parseLength(v: unknown): number | undefined;
265
+ /** Accept CSL-JSON items plus friendly shorthands (`authors: [...]`, `year: 2020`). */
266
+ declare function normalizeReference(raw: unknown): CslItem | null;
267
+ interface ResolveSettingsOptions {
268
+ /** Used when the frontmatter has no `title`. */
269
+ title?: string | undefined;
270
+ /** Extra references (e.g. from a host's source records); frontmatter wins on id clash. */
271
+ references?: CslItem[] | undefined;
272
+ /** "Today" for `date: today` — injectable so tests are deterministic. */
273
+ now?: Date | undefined;
274
+ }
275
+ /** Turn a frontmatter mapping into complete, validated settings. */
276
+ declare function resolveSettings(data: Record<string, unknown>, notices: DocumentNotice[], options?: ResolveSettingsOptions): DocumentSettings;
277
+ /** Fill a running template for one page. */
278
+ declare function fillTemplate(template: string, s: DocumentSettings, page: number | string, pages: number | string): string;
279
+
280
+ /**
281
+ * markdown (+ frontmatter) → DocTree. The ONE parse every document output
282
+ * (PDF, DOCX, EPUB, HTML) renders from.
283
+ *
284
+ * Grammar:
285
+ * • CommonMark + GFM (tables, task lists, strikethrough, autolinks,
286
+ * footnotes) via micromark/mdast — the reference parser, not a regex.
287
+ * • Page/section breaks, TOC and bibliography placement come ONLY from
288
+ * `../directives` (the grammar the chat preview and the print skins share).
289
+ * • Captions + cross-references follow pandoc-crossref:
290
+ * ![A caption](url){#fig:growth} figure, numbered "Figure N"
291
+ * Table: A caption {#tbl:sales} caption line before/after a table
292
+ * ## Methods {#sec:methods} heading id (`{-}` = unnumbered)
293
+ * see @fig:growth, @tbl:sales, @sec:methods
294
+ * • Citations follow pandoc: [@key], [@key, p. 4], [@a; @b], [-@key], @key.
295
+ */
296
+
297
+ interface ParseDocumentOptions extends ResolveSettingsOptions {
298
+ /** Where dropped/degraded content is announced (also collected on the tree). */
299
+ onNotice?: (notice: DocumentNotice) => void;
300
+ }
301
+ declare function plainText(inlines: Inline[]): string;
302
+ /** Parse a markdown source (with optional frontmatter) into the document tree. */
303
+ declare function parseDocument(source: string, options?: ParseDocumentOptions): DocTree;
304
+
305
+ /**
306
+ * DocTree → HTML. Two uses, one renderer:
307
+ * • `documentToHtml` — a standalone, print-grade HTML file: CSS paged media
308
+ * (@page size/margins/orientation, running header/footer margin boxes with
309
+ * "Page X of Y", a different first page, named landscape pages per
310
+ * section, columns, widow/orphan control, keep-with-next headings, no
311
+ * breaks inside tables/code/figures, hyphenation), always light.
312
+ * • `renderBlocksHtml` — the block renderer the EPUB writer reuses (XHTML).
313
+ */
314
+
315
+ interface HtmlRenderContext {
316
+ tree: DocTree;
317
+ images?: ImageMap | undefined;
318
+ /** XHTML output (EPUB): self-closed void elements. */
319
+ xhtml?: boolean;
320
+ /** Where an in-document id lives (EPUB chapters split the document). */
321
+ hrefFor?: (id: string) => string;
322
+ /** Image URL override (EPUB packages images inside the book). */
323
+ srcFor?: (url: string) => string;
324
+ }
325
+ declare function renderBlocksHtml(blocks: Block[], x: HtmlRenderContext): string;
326
+ /** The print stylesheet for a document's settings. Light mode, always. */
327
+ declare function documentCss(s: DocumentSettings): string;
328
+ interface DocumentHtmlOptions {
329
+ images?: ImageMap | undefined;
330
+ }
331
+ /** A complete, standalone print-grade HTML document. */
332
+ declare function documentToHtml(tree: DocTree, options?: DocumentHtmlOptions): string;
333
+
334
+ /**
335
+ * DocTree → PDF (real text PDF via pdfmake — selectable, searchable,
336
+ * link-bearing; never a screenshot). pdfmake is loaded lazily at call time.
337
+ *
338
+ * Print behaviours: page size / margins / orientation, per-section
339
+ * orientation and 1–3 newspaper columns, cover page, running header/footer
340
+ * with "Page X of Y", different first page, table of contents WITH page
341
+ * numbers, numbered captions, cross-reference links, keep-with-next headings
342
+ * (a heading is never the last thing on a page), short paragraphs never split
343
+ * (orphan/widow control), table rows never split and header rows repeat,
344
+ * tables/code/figures kept whole when they fit on a page.
345
+ */
346
+
347
+ /** Build the pdfmake document definition (exported for tests and hosts). */
348
+ declare function buildPdfDefinition(tree: DocTree, images?: ImageMap, pageCountSink?: (n: number) => void): Record<string, unknown>;
349
+ interface PdfResult {
350
+ bytes: Uint8Array;
351
+ pageCount: number;
352
+ }
353
+ declare function documentToPdf(tree: DocTree, images?: ImageMap): Promise<PdfResult>;
354
+
355
+ /**
356
+ * DocTree → DOCX (Word). Uses the `docx` package, loaded lazily.
357
+ *
358
+ * Word-native everywhere it matters, so the file keeps working when the reader
359
+ * edits it: real sections (size, margins, orientation, columns per section),
360
+ * running headers/footers with PAGE / NUMPAGES fields, a different first page
361
+ * (titlePage), a real TOC field pre-filled with entries and page numbers (and
362
+ * `updateFields` so Word refreshes it on open), SEQ-field captions with
363
+ * bookmarks, cross-references as internal hyperlinks, heading keep-with-next,
364
+ * widow/orphan control, table rows that never split with repeating header
365
+ * rows, automatic hyphenation.
366
+ */
367
+
368
+ interface DocxOptions {
369
+ images?: ImageMap | undefined;
370
+ /** Heading id → page number (from the PDF layout) for the cached TOC. */
371
+ headingPages?: Map<string, number> | undefined;
372
+ }
373
+ declare function documentToDocx(tree: DocTree, options?: DocxOptions): Promise<Uint8Array>;
374
+
375
+ /**
376
+ * DocTree → EPUB 3 (reflowable). The same block renderer as the HTML output
377
+ * (XHTML mode), one chapter per top-level heading, a navigation document built
378
+ * from the same TOC entries, images packaged inside the book.
379
+ *
380
+ * Container rules honoured: `mimetype` is the first entry and STORED
381
+ * (uncompressed); META-INF/container.xml points at the package document;
382
+ * nav.xhtml carries `epub:type="toc"`; dcterms:modified is set.
383
+ */
384
+
385
+ interface EpubOptions {
386
+ images?: ImageMap | undefined;
387
+ /** Book identifier (defaults to a random urn:uuid). */
388
+ identifier?: string | undefined;
389
+ /** Modification time — injectable for deterministic tests. */
390
+ now?: Date | undefined;
391
+ }
392
+ declare function documentToEpub(tree: DocTree, options?: EpubOptions): Promise<Uint8Array>;
393
+
394
+ /**
395
+ * Citations and the reference list — pandoc citation syntax over CSL-JSON
396
+ * reference items, formatted in three built-in styles:
397
+ *
398
+ * apa APA 7th edition (author–date) (Smith & Jones, 2020, p. 4)
399
+ * mla MLA 9th edition (author–page) (Smith and Jones 4)
400
+ * chicago Chicago 17th, author–date (Smith and Jones 2020, 4)
401
+ *
402
+ * Syntax (pandoc): `[@smith2020]`, `[@smith2020, p. 4]`, `[@a; @b]`,
403
+ * `[-@smith2020]` (suppress the author), and a bare `@smith2020` in running
404
+ * text (narrative: "Smith (2020)") — the bare form only fires for a key that
405
+ * is actually in the reference list, so an email or an @-handle is never
406
+ * mistaken for a citation.
407
+ */
408
+
409
+ interface CiteRef {
410
+ key: string;
411
+ locator?: string | undefined;
412
+ suppressAuthor: boolean;
413
+ }
414
+ /** Parse the inside of `[...]` into cite refs; null if it is not a citation. */
415
+ declare function parseCitationGroup(inner: string): CiteRef[] | null;
416
+ /** Format one citation group as in-text text, e.g. "(Smith, 2020, p. 4; Lee, 2019)". */
417
+ declare function formatCitation(refs: CiteRef[], lookup: (key: string) => CslItem | undefined, style: CitationStyle, narrative?: boolean): {
418
+ label: string;
419
+ resolved: boolean;
420
+ };
421
+ /** Format the reference list for the cited keys, sorted as the style requires. */
422
+ declare function formatBibliography(items: CslItem[], style: CitationStyle): BibliographyEntry[];
423
+
424
+ /**
425
+ * @ai-matrx/print/document — print-grade documents from markdown.
426
+ *
427
+ * ONE parsed tree (`parseDocument`) → PDF, DOCX, EPUB, HTML (+ the markdown
428
+ * source itself), so every format carries the same page setup, cover, TOC,
429
+ * numbered captions, cross-references, citations and bibliography.
430
+ *
431
+ * Settings live in the markdown's YAML frontmatter:
432
+ *
433
+ * ---
434
+ * title: Quarterly Review
435
+ * subtitle: Operations
436
+ * author: Jane Park
437
+ * date: today
438
+ * cover: true
439
+ * toc: true # or { depth: 2, title: Contents }
440
+ * page: { size: a4, orientation: portrait, margins: 1in }
441
+ * header: "{title} | | {date}" # left | center | right
442
+ * footer: "Page {page} of {pages}"
443
+ * firstPage: different
444
+ * columns: 2
445
+ * numberSections: true
446
+ * csl: apa # apa | mla | chicago
447
+ * references:
448
+ * - id: smith2020
449
+ * title: Paper Cuts
450
+ * author: [Smith, Jane]
451
+ * issued: 2020
452
+ * ---
453
+ *
454
+ * Heavy engines (pdfmake, docx, jszip) load lazily when an export is called;
455
+ * parsing and HTML are pure string work and safe on the server.
456
+ */
457
+
458
+ type DocumentFormat = "pdf" | "docx" | "epub" | "html" | "markdown";
459
+ declare const DOCUMENT_FORMATS: ReadonlyArray<{
460
+ format: DocumentFormat;
461
+ label: string;
462
+ extension: string;
463
+ mime: string;
464
+ }>;
465
+ /** Load image bytes for a URL. Default: data: URLs decode, http(s) fetches. */
466
+ type ImageLoader = (url: string) => Promise<Uint8Array | null>;
467
+ declare const defaultImageLoader: ImageLoader;
468
+ /** Resolve every figure/logo image once. Failures are announced, never fatal. */
469
+ declare function resolveDocumentImages(tree: DocTree, loader?: ImageLoader, onNotice?: (n: DocumentNotice) => void): Promise<ImageMap>;
470
+ interface ExportDocumentOptions extends ParseDocumentOptions {
471
+ /** File name without extension (default: the document title). */
472
+ fileName?: string | undefined;
473
+ imageLoader?: ImageLoader | undefined;
474
+ }
475
+ interface DocumentExport {
476
+ format: DocumentFormat;
477
+ bytes: Uint8Array;
478
+ mime: string;
479
+ fileName: string;
480
+ /** PDF only. */
481
+ pageCount?: number | undefined;
482
+ tree: DocTree;
483
+ notices: DocumentNotice[];
484
+ }
485
+ /** Export a markdown source to any document format. */
486
+ declare function exportDocument(source: string, format: DocumentFormat, options?: ExportDocumentOptions): Promise<DocumentExport>;
487
+ /** Browser helper: save an export through a download link. */
488
+ declare function downloadDocumentExport(exp: DocumentExport): void;
489
+
490
+ export { type BibliographyEntry, type Block, type Caption, type CitationStyle, type CslItem, type CslName, DEFAULT_FOOTER, DOCUMENT_FORMATS, type DocSection, type DocTree, type DocumentExport, type DocumentFormat, type DocumentNotice, type DocumentSettings, type ExportDocumentOptions, type ImageLoader, type ImageMap, type Inline, type ListItem, type Margins, type Orientation, PAGE_SIZES, type PageSizeName, type ParseDocumentOptions, type ResolvedImage, type RunningTemplate, type TocEntry, buildPdfDefinition, defaultImageLoader, documentCss, documentToDocx, documentToEpub, documentToHtml, documentToPdf, downloadDocumentExport, exportDocument, fillTemplate, formatBibliography, formatCitation, normalizeReference, parseCitationGroup, parseDocument, parseLength, plainText, renderBlocksHtml, resolveDocumentImages, resolveSettings, splitFrontmatter };