@ai-matrx/print 0.6.2 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -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 +2860 -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 +2827 -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
|
@@ -0,0 +1,2860 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/document.ts
|
|
31
|
+
var document_exports = {};
|
|
32
|
+
__export(document_exports, {
|
|
33
|
+
DEFAULT_FOOTER: () => DEFAULT_FOOTER,
|
|
34
|
+
DOCUMENT_FORMATS: () => DOCUMENT_FORMATS,
|
|
35
|
+
PAGE_SIZES: () => PAGE_SIZES,
|
|
36
|
+
buildPdfDefinition: () => buildPdfDefinition,
|
|
37
|
+
defaultImageLoader: () => defaultImageLoader,
|
|
38
|
+
documentCss: () => documentCss,
|
|
39
|
+
documentToDocx: () => documentToDocx,
|
|
40
|
+
documentToEpub: () => documentToEpub,
|
|
41
|
+
documentToHtml: () => documentToHtml,
|
|
42
|
+
documentToPdf: () => documentToPdf,
|
|
43
|
+
downloadDocumentExport: () => downloadDocumentExport,
|
|
44
|
+
exportDocument: () => exportDocument,
|
|
45
|
+
fillTemplate: () => fillTemplate,
|
|
46
|
+
formatBibliography: () => formatBibliography,
|
|
47
|
+
formatCitation: () => formatCitation,
|
|
48
|
+
normalizeReference: () => normalizeReference,
|
|
49
|
+
parseCitationGroup: () => parseCitationGroup,
|
|
50
|
+
parseDocument: () => parseDocument,
|
|
51
|
+
parseLength: () => parseLength,
|
|
52
|
+
plainText: () => plainText,
|
|
53
|
+
renderBlocksHtml: () => renderBlocksHtml,
|
|
54
|
+
resolveDocumentImages: () => resolveDocumentImages,
|
|
55
|
+
resolveSettings: () => resolveSettings,
|
|
56
|
+
splitFrontmatter: () => splitFrontmatter
|
|
57
|
+
});
|
|
58
|
+
module.exports = __toCommonJS(document_exports);
|
|
59
|
+
|
|
60
|
+
// src/document/settings.ts
|
|
61
|
+
var import_yaml = require("yaml");
|
|
62
|
+
var PAGE_SIZES = {
|
|
63
|
+
letter: { width: 612, height: 792, label: "Letter" },
|
|
64
|
+
legal: { width: 612, height: 1008, label: "Legal" },
|
|
65
|
+
tabloid: { width: 792, height: 1224, label: "Tabloid" },
|
|
66
|
+
executive: { width: 522, height: 756, label: "Executive" },
|
|
67
|
+
a3: { width: 841.89, height: 1190.55, label: "A3" },
|
|
68
|
+
a4: { width: 595.28, height: 841.89, label: "A4" },
|
|
69
|
+
a5: { width: 419.53, height: 595.28, label: "A5" },
|
|
70
|
+
b5: { width: 498.9, height: 708.66, label: "B5" }
|
|
71
|
+
};
|
|
72
|
+
var DEFAULT_FOOTER = { center: "Page {page} of {pages}" };
|
|
73
|
+
var BIBLIOGRAPHY_TITLES = {
|
|
74
|
+
apa: "References",
|
|
75
|
+
mla: "Works Cited",
|
|
76
|
+
chicago: "Bibliography"
|
|
77
|
+
};
|
|
78
|
+
function splitFrontmatter(source, notices) {
|
|
79
|
+
const text = source.replace(/^/, "");
|
|
80
|
+
const lines = text.split(/\r?\n/);
|
|
81
|
+
if (lines[0]?.trim() !== "---" || lines[0] !== lines[0].trimStart()) {
|
|
82
|
+
return { data: {}, body: text, raw: null };
|
|
83
|
+
}
|
|
84
|
+
let close = -1;
|
|
85
|
+
for (let i = 1; i < lines.length; i++) {
|
|
86
|
+
const t2 = lines[i].trimEnd();
|
|
87
|
+
if (t2 === "---" || t2 === "...") {
|
|
88
|
+
close = i;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (close === -1) return { data: {}, body: text, raw: null };
|
|
93
|
+
const raw = lines.slice(1, close).join("\n");
|
|
94
|
+
const body = lines.slice(close + 1).join("\n");
|
|
95
|
+
let data;
|
|
96
|
+
try {
|
|
97
|
+
data = raw.trim() ? (0, import_yaml.parse)(raw) : {};
|
|
98
|
+
} catch (err) {
|
|
99
|
+
notices?.push({
|
|
100
|
+
code: "frontmatter-invalid",
|
|
101
|
+
message: `The document settings at the top could not be read: ${err.message.split("\n")[0]}`,
|
|
102
|
+
remedy: "Fix the YAML between the two --- lines (every setting is `name: value`, lists use `- item`)."
|
|
103
|
+
});
|
|
104
|
+
return { data: {}, body, raw };
|
|
105
|
+
}
|
|
106
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
107
|
+
notices?.push({
|
|
108
|
+
code: "frontmatter-invalid",
|
|
109
|
+
message: "The document settings at the top are not a list of `name: value` settings.",
|
|
110
|
+
remedy: "Write one setting per line between the --- lines, e.g. `title: Annual report`."
|
|
111
|
+
});
|
|
112
|
+
return { data: {}, body, raw };
|
|
113
|
+
}
|
|
114
|
+
return { data, body, raw };
|
|
115
|
+
}
|
|
116
|
+
var norm = (k) => k.replace(/[-_\s]/g, "").toLowerCase();
|
|
117
|
+
function flatten(data) {
|
|
118
|
+
const out = /* @__PURE__ */ new Map();
|
|
119
|
+
for (const [k, v] of Object.entries(data)) out.set(norm(k), v);
|
|
120
|
+
for (const nest of ["page", "print", "document", "layout"]) {
|
|
121
|
+
const v = out.get(nest);
|
|
122
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
123
|
+
for (const [k, inner] of Object.entries(v)) {
|
|
124
|
+
const key = norm(k);
|
|
125
|
+
if (!out.has(key) || nest !== "page") out.set(key, inner);
|
|
126
|
+
if (nest === "page" && !out.has(`page${key}`)) out.set(`page${key}`, inner);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
var str = (v) => {
|
|
133
|
+
if (v === null || v === void 0) return void 0;
|
|
134
|
+
if (typeof v === "string") return v.trim() || void 0;
|
|
135
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
136
|
+
if (v instanceof Date) return v.toISOString().slice(0, 10);
|
|
137
|
+
return void 0;
|
|
138
|
+
};
|
|
139
|
+
var bool = (v) => {
|
|
140
|
+
if (typeof v === "boolean") return v;
|
|
141
|
+
if (typeof v === "string") {
|
|
142
|
+
const t2 = v.trim().toLowerCase();
|
|
143
|
+
if (["true", "yes", "on", "1"].includes(t2)) return true;
|
|
144
|
+
if (["false", "no", "off", "0", "none"].includes(t2)) return false;
|
|
145
|
+
}
|
|
146
|
+
if (typeof v === "number") return v !== 0;
|
|
147
|
+
return void 0;
|
|
148
|
+
};
|
|
149
|
+
function parseLength(v) {
|
|
150
|
+
if (typeof v === "number" && Number.isFinite(v)) return v * 72;
|
|
151
|
+
if (typeof v !== "string") return void 0;
|
|
152
|
+
const m = /^\s*(-?\d+(?:\.\d+)?)\s*(in|inch|inches|cm|mm|pt|px|")?\s*$/i.exec(v);
|
|
153
|
+
if (!m) return void 0;
|
|
154
|
+
const n = Number(m[1]);
|
|
155
|
+
switch ((m[2] ?? "in").toLowerCase()) {
|
|
156
|
+
case "cm":
|
|
157
|
+
return n / 2.54 * 72;
|
|
158
|
+
case "mm":
|
|
159
|
+
return n / 25.4 * 72;
|
|
160
|
+
case "pt":
|
|
161
|
+
return n;
|
|
162
|
+
case "px":
|
|
163
|
+
return n * 0.75;
|
|
164
|
+
default:
|
|
165
|
+
return n * 72;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function parseMargins(v, base) {
|
|
169
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
170
|
+
const o = v;
|
|
171
|
+
const all = parseLength(o.all ?? o.margin);
|
|
172
|
+
const pick = (k) => parseLength(o[k]) ?? parseLength(k === "left" || k === "right" ? o.x : o.y) ?? all ?? base[k];
|
|
173
|
+
return { top: pick("top"), right: pick("right"), bottom: pick("bottom"), left: pick("left") };
|
|
174
|
+
}
|
|
175
|
+
const parts = (typeof v === "number" ? [String(v)] : typeof v === "string" ? v.trim().split(/[\s,]+/) : []).map((p) => parseLength(/^\d+(\.\d+)?$/.test(p) ? Number(p) : p)).filter((n) => n !== void 0);
|
|
176
|
+
if (parts.length === 0) return void 0;
|
|
177
|
+
const [t2, r = t2, b = t2, l = r] = parts;
|
|
178
|
+
return { top: t2, right: r, bottom: b, left: l };
|
|
179
|
+
}
|
|
180
|
+
function parseGeometry(v, base) {
|
|
181
|
+
const items = Array.isArray(v) ? v.map(String) : typeof v === "string" ? v.split(",") : [];
|
|
182
|
+
if (items.length === 0) return void 0;
|
|
183
|
+
const m = { ...base };
|
|
184
|
+
let hit = false;
|
|
185
|
+
for (const item of items) {
|
|
186
|
+
const [k, val] = item.split("=").map((s) => s.trim().toLowerCase());
|
|
187
|
+
const len = parseLength(val);
|
|
188
|
+
if (!k || len === void 0) continue;
|
|
189
|
+
hit = true;
|
|
190
|
+
if (k === "margin") m.top = m.right = m.bottom = m.left = len;
|
|
191
|
+
else if (k === "top" || k === "tmargin") m.top = len;
|
|
192
|
+
else if (k === "bottom" || k === "bmargin") m.bottom = len;
|
|
193
|
+
else if (k === "left" || k === "lmargin" || k === "inner") m.left = len;
|
|
194
|
+
else if (k === "right" || k === "rmargin" || k === "outer") m.right = len;
|
|
195
|
+
else if (k === "hmargin") m.left = m.right = len;
|
|
196
|
+
else if (k === "vmargin") m.top = m.bottom = len;
|
|
197
|
+
else hit = hit || false;
|
|
198
|
+
}
|
|
199
|
+
return hit ? m : void 0;
|
|
200
|
+
}
|
|
201
|
+
function parseRunning(v) {
|
|
202
|
+
if (v === void 0) return void 0;
|
|
203
|
+
if (v === false || v === null) return null;
|
|
204
|
+
if (typeof v === "string") {
|
|
205
|
+
const t2 = v.trim();
|
|
206
|
+
if (!t2 || /^(none|off|false)$/i.test(t2)) return null;
|
|
207
|
+
if (t2.includes("|")) {
|
|
208
|
+
const [left, center, right] = t2.split("|").map((s) => s.trim());
|
|
209
|
+
return { left: left || void 0, center: center || void 0, right: right || void 0 };
|
|
210
|
+
}
|
|
211
|
+
return { center: t2 };
|
|
212
|
+
}
|
|
213
|
+
if (typeof v === "object" && !Array.isArray(v)) {
|
|
214
|
+
const o = v;
|
|
215
|
+
return { left: str(o.left), center: str(o.center ?? o.centre ?? o.middle), right: str(o.right) };
|
|
216
|
+
}
|
|
217
|
+
return void 0;
|
|
218
|
+
}
|
|
219
|
+
function parseName(n) {
|
|
220
|
+
if (typeof n === "string") {
|
|
221
|
+
const t2 = n.trim();
|
|
222
|
+
if (!t2) return null;
|
|
223
|
+
if (t2.includes(",")) {
|
|
224
|
+
const [family2, given] = t2.split(",").map((s) => s.trim());
|
|
225
|
+
return { family: family2, given: given || void 0 };
|
|
226
|
+
}
|
|
227
|
+
const parts = t2.split(/\s+/);
|
|
228
|
+
if (parts.length === 1) return { family: parts[0] };
|
|
229
|
+
return { family: parts[parts.length - 1], given: parts.slice(0, -1).join(" ") };
|
|
230
|
+
}
|
|
231
|
+
if (n && typeof n === "object") {
|
|
232
|
+
const o = n;
|
|
233
|
+
const family2 = str(o.family ?? o.last ?? o.surname);
|
|
234
|
+
const given = str(o.given ?? o.first);
|
|
235
|
+
const literal = str(o.literal ?? o.name);
|
|
236
|
+
if (!family2 && !given && !literal) return null;
|
|
237
|
+
return { family: family2, given, literal };
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
function normalizeReference(raw) {
|
|
242
|
+
if (!raw || typeof raw !== "object") return null;
|
|
243
|
+
const o = raw;
|
|
244
|
+
const id = str(o.id ?? o.key ?? o["citation-key"]);
|
|
245
|
+
if (!id) return null;
|
|
246
|
+
const names = (v) => {
|
|
247
|
+
const arr = Array.isArray(v) ? v : typeof v === "string" ? v.split(/\s+and\s+|;/) : [];
|
|
248
|
+
const out = arr.map(parseName).filter((x) => x !== null);
|
|
249
|
+
return out.length ? out : void 0;
|
|
250
|
+
};
|
|
251
|
+
let issued = o.issued && typeof o.issued === "object" ? o.issued : void 0;
|
|
252
|
+
const year2 = o.year ?? o.date ?? (typeof o.issued === "object" ? void 0 : o.issued);
|
|
253
|
+
if (!issued && year2 !== void 0) {
|
|
254
|
+
const y = str(year2);
|
|
255
|
+
const m = y ? /^(\d{4})(?:-(\d{1,2}))?(?:-(\d{1,2}))?/.exec(y) : null;
|
|
256
|
+
issued = m ? { "date-parts": [[Number(m[1]), ...m[2] ? [Number(m[2])] : [], ...m[3] ? [Number(m[3])] : []]] } : y ? { literal: y } : void 0;
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
id,
|
|
260
|
+
type: str(o.type),
|
|
261
|
+
title: str(o.title),
|
|
262
|
+
author: names(o.author ?? o.authors),
|
|
263
|
+
editor: names(o.editor ?? o.editors),
|
|
264
|
+
issued,
|
|
265
|
+
"container-title": str(o["container-title"] ?? o.containerTitle ?? o.journal ?? o.website ?? o.booktitle),
|
|
266
|
+
publisher: str(o.publisher),
|
|
267
|
+
"publisher-place": str(o["publisher-place"] ?? o.publisherPlace ?? o.place ?? o.address),
|
|
268
|
+
volume: str(o.volume),
|
|
269
|
+
issue: str(o.issue ?? o.number),
|
|
270
|
+
page: str(o.page ?? o.pages),
|
|
271
|
+
edition: str(o.edition),
|
|
272
|
+
URL: str(o.URL ?? o.url),
|
|
273
|
+
DOI: str(o.DOI ?? o.doi)
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function resolveSettings(data, notices, options = {}) {
|
|
277
|
+
const f = flatten(data);
|
|
278
|
+
const get = (...keys) => {
|
|
279
|
+
for (const k of keys) if (f.has(k)) return f.get(k);
|
|
280
|
+
return void 0;
|
|
281
|
+
};
|
|
282
|
+
const ignore = (key, value, remedy) => notices.push({
|
|
283
|
+
code: "setting-ignored",
|
|
284
|
+
message: `The setting "${key}: ${typeof value === "object" ? JSON.stringify(value) : String(value)}" was not understood, so the default is used.`,
|
|
285
|
+
remedy
|
|
286
|
+
});
|
|
287
|
+
let size = "letter";
|
|
288
|
+
let orientation = "portrait";
|
|
289
|
+
const sizeRaw = get("pagesize", "papersize", "paper", "size");
|
|
290
|
+
if (sizeRaw !== void 0) {
|
|
291
|
+
const s = String(sizeRaw).toLowerCase().replace(/paper$/, "").replace(/[\s-]/g, "");
|
|
292
|
+
const words = s.match(/[a-z]+\d*/g) ?? [];
|
|
293
|
+
const name = words.find((w) => w in PAGE_SIZES);
|
|
294
|
+
if (name) size = name;
|
|
295
|
+
else ignore("page size", sizeRaw, `Use one of: ${Object.keys(PAGE_SIZES).join(", ")}.`);
|
|
296
|
+
if (/landscape/.test(s)) orientation = "landscape";
|
|
297
|
+
}
|
|
298
|
+
const orientRaw = get("orientation", "pageorientation");
|
|
299
|
+
if (orientRaw !== void 0) {
|
|
300
|
+
const o = String(orientRaw).toLowerCase();
|
|
301
|
+
if (o === "landscape" || o === "portrait") orientation = o;
|
|
302
|
+
else ignore("orientation", orientRaw, "Use `portrait` or `landscape`.");
|
|
303
|
+
}
|
|
304
|
+
if (bool(get("landscape")) === true) orientation = "landscape";
|
|
305
|
+
let margins = { top: 72, right: 72, bottom: 72, left: 72 };
|
|
306
|
+
const marginRaw = get("margins", "margin", "pagemargins", "pagemargin");
|
|
307
|
+
if (marginRaw !== void 0) {
|
|
308
|
+
const m = parseMargins(marginRaw, margins);
|
|
309
|
+
if (m) margins = m;
|
|
310
|
+
else ignore("margins", marginRaw, "Write margins like `1in`, `2.5cm`, `20mm`, or `1in 0.75in` (top/bottom left/right).");
|
|
311
|
+
}
|
|
312
|
+
const geometry = get("geometry");
|
|
313
|
+
if (geometry !== void 0) {
|
|
314
|
+
const m = parseGeometry(geometry, margins);
|
|
315
|
+
if (m) margins = m;
|
|
316
|
+
else ignore("geometry", geometry, "Write it like pandoc: `geometry: margin=1in`.");
|
|
317
|
+
}
|
|
318
|
+
for (const side of ["top", "right", "bottom", "left"]) {
|
|
319
|
+
const v = get(`margin${side}`, `${side}margin`);
|
|
320
|
+
const len = parseLength(v);
|
|
321
|
+
if (len !== void 0) margins[side] = len;
|
|
322
|
+
}
|
|
323
|
+
const title = str(get("title")) ?? options.title;
|
|
324
|
+
const subtitle = str(get("subtitle"));
|
|
325
|
+
const authorRaw = get("author", "authors");
|
|
326
|
+
const author = Array.isArray(authorRaw) ? authorRaw.map((a) => typeof a === "string" ? a : str(a?.name)).filter(Boolean).join(", ") : str(authorRaw);
|
|
327
|
+
let date = str(get("date"));
|
|
328
|
+
if (date && /^today$/i.test(date)) {
|
|
329
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
330
|
+
date = now.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });
|
|
331
|
+
}
|
|
332
|
+
const logo = str(get("logo", "coverlogo"));
|
|
333
|
+
const coverRaw = get("cover", "coverpage", "titlepage");
|
|
334
|
+
let cover = false;
|
|
335
|
+
if (coverRaw !== void 0) {
|
|
336
|
+
const b = bool(coverRaw);
|
|
337
|
+
if (b === void 0 && coverRaw && typeof coverRaw === "object") cover = true;
|
|
338
|
+
else if (b === void 0) ignore("cover", coverRaw, "Use `cover: true` or `cover: false`.");
|
|
339
|
+
else cover = b;
|
|
340
|
+
}
|
|
341
|
+
if (cover && !title) {
|
|
342
|
+
notices.push({
|
|
343
|
+
code: "setting-ignored",
|
|
344
|
+
message: "A cover page was asked for, but the document has no title.",
|
|
345
|
+
remedy: "Add `title: \u2026` to the settings at the top (subtitle, author, date and logo are optional)."
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
const tocRaw = get("toc", "tableofcontents", "contents");
|
|
349
|
+
let tocEnabled = false;
|
|
350
|
+
let tocDepth = 3;
|
|
351
|
+
let tocTitle = "Contents";
|
|
352
|
+
if (tocRaw !== void 0) {
|
|
353
|
+
if (tocRaw && typeof tocRaw === "object" && !Array.isArray(tocRaw)) {
|
|
354
|
+
const o = tocRaw;
|
|
355
|
+
tocEnabled = bool(o.enabled ?? true) ?? true;
|
|
356
|
+
const d = Number(o.depth);
|
|
357
|
+
if (Number.isFinite(d)) tocDepth = d;
|
|
358
|
+
tocTitle = str(o.title) ?? tocTitle;
|
|
359
|
+
} else {
|
|
360
|
+
const b = bool(tocRaw);
|
|
361
|
+
if (b === void 0) ignore("toc", tocRaw, "Use `toc: true`, or `toc: { depth: 2, title: Contents }`.");
|
|
362
|
+
else tocEnabled = b;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const depthRaw = get("tocdepth");
|
|
366
|
+
if (depthRaw !== void 0 && Number.isFinite(Number(depthRaw))) tocDepth = Number(depthRaw);
|
|
367
|
+
tocDepth = Math.min(6, Math.max(1, Math.round(tocDepth)));
|
|
368
|
+
tocTitle = str(get("toctitle")) ?? tocTitle;
|
|
369
|
+
const headerRaw = get("header", "pageheader", "runningheader");
|
|
370
|
+
const footerRaw = get("footer", "pagefooter", "runningfooter");
|
|
371
|
+
let header = parseRunning(headerRaw);
|
|
372
|
+
if (header === void 0) {
|
|
373
|
+
if (headerRaw !== void 0) ignore("header", headerRaw, 'Use text like `header: "{title} | | {date}"`, or `header: false`.');
|
|
374
|
+
header = null;
|
|
375
|
+
}
|
|
376
|
+
let footer = parseRunning(footerRaw);
|
|
377
|
+
if (footer === void 0) {
|
|
378
|
+
if (footerRaw !== void 0) ignore("footer", footerRaw, 'Use text like `footer: "Page {page} of {pages}"`, or `footer: false`.');
|
|
379
|
+
footer = DEFAULT_FOOTER;
|
|
380
|
+
}
|
|
381
|
+
if (bool(get("pagenumbers")) === false && footerRaw === void 0) footer = null;
|
|
382
|
+
const firstRaw = get("firstpage", "differentfirstpage", "titlepageheader");
|
|
383
|
+
let differentFirstPage = cover;
|
|
384
|
+
if (firstRaw !== void 0) {
|
|
385
|
+
const b = bool(firstRaw);
|
|
386
|
+
if (b !== void 0) differentFirstPage = b || cover;
|
|
387
|
+
else if (/different/i.test(String(firstRaw))) differentFirstPage = true;
|
|
388
|
+
else if (/same/i.test(String(firstRaw))) differentFirstPage = cover;
|
|
389
|
+
else ignore("firstPage", firstRaw, "Use `firstPage: different` or `firstPage: same`.");
|
|
390
|
+
}
|
|
391
|
+
let columns = 1;
|
|
392
|
+
const colRaw = get("columns", "cols", "columncount");
|
|
393
|
+
if (colRaw !== void 0) {
|
|
394
|
+
const n = Number(colRaw);
|
|
395
|
+
if (Number.isInteger(n) && n >= 1 && n <= 3) columns = n;
|
|
396
|
+
else ignore("columns", colRaw, "Use 1, 2 or 3.");
|
|
397
|
+
}
|
|
398
|
+
let citationStyle = "apa";
|
|
399
|
+
const cslRaw = get("citationstyle", "csl", "citestyle", "referencestyle");
|
|
400
|
+
if (cslRaw !== void 0) {
|
|
401
|
+
const s = String(cslRaw).toLowerCase();
|
|
402
|
+
if (/apa/.test(s)) citationStyle = "apa";
|
|
403
|
+
else if (/mla|modern-language/.test(s)) citationStyle = "mla";
|
|
404
|
+
else if (/chicago/.test(s)) citationStyle = "chicago";
|
|
405
|
+
else ignore("citation style", cslRaw, "Use `apa`, `mla` or `chicago`.");
|
|
406
|
+
}
|
|
407
|
+
const refs = /* @__PURE__ */ new Map();
|
|
408
|
+
for (const r of options.references ?? []) refs.set(r.id, r);
|
|
409
|
+
const refsRaw = get("references", "bibliography", "sources");
|
|
410
|
+
if (refsRaw !== void 0) {
|
|
411
|
+
if (Array.isArray(refsRaw)) {
|
|
412
|
+
for (const r of refsRaw) {
|
|
413
|
+
const item = normalizeReference(r);
|
|
414
|
+
if (item) refs.set(item.id, item);
|
|
415
|
+
else ignore("references", r, "Every reference needs an `id:` (the key you cite as [@id]) and usually `title`, `author`, `issued`/`year`.");
|
|
416
|
+
}
|
|
417
|
+
} else if (refsRaw && typeof refsRaw === "object") {
|
|
418
|
+
for (const [id, r] of Object.entries(refsRaw)) {
|
|
419
|
+
const item = normalizeReference({ id, ...r });
|
|
420
|
+
if (item) refs.set(item.id, item);
|
|
421
|
+
}
|
|
422
|
+
} else if (typeof refsRaw !== "string") {
|
|
423
|
+
ignore("references", refsRaw, "List references as CSL-JSON items: `- id: smith2020` with `title`, `author`, `issued`.");
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
let fontSize = 11;
|
|
427
|
+
const fsRaw = get("fontsize", "basefontsize");
|
|
428
|
+
if (fsRaw !== void 0) {
|
|
429
|
+
const n = parseLength(typeof fsRaw === "number" ? `${fsRaw}pt` : fsRaw);
|
|
430
|
+
if (n && n >= 6 && n <= 24) fontSize = n;
|
|
431
|
+
else ignore("fontSize", fsRaw, "Use a size in points between 6 and 24, e.g. `fontSize: 11`.");
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
title,
|
|
435
|
+
subtitle,
|
|
436
|
+
author,
|
|
437
|
+
date,
|
|
438
|
+
logo,
|
|
439
|
+
lang: str(get("lang", "language")) ?? "en",
|
|
440
|
+
page: { size, ...PAGE_SIZES[size], orientation, margins },
|
|
441
|
+
cover: cover && Boolean(title),
|
|
442
|
+
toc: { enabled: tocEnabled, depth: tocDepth, title: tocTitle },
|
|
443
|
+
header,
|
|
444
|
+
footer,
|
|
445
|
+
differentFirstPage,
|
|
446
|
+
columns,
|
|
447
|
+
numberSections: bool(get("numbersections", "sectionnumbers", "numberedheadings", "numberheadings")) ?? false,
|
|
448
|
+
citationStyle,
|
|
449
|
+
bibliographyTitle: str(get("bibliographytitle", "referencestitle", "referencesectiontitle")) ?? BIBLIOGRAPHY_TITLES[citationStyle],
|
|
450
|
+
references: [...refs.values()],
|
|
451
|
+
hyphenate: bool(get("hyphenate", "hyphenation")) ?? true,
|
|
452
|
+
fontSize
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function fillTemplate(template, s, page, pages) {
|
|
456
|
+
return template.replace(/\{(page|pages|title|subtitle|author|date)\}/gi, (_m, k) => {
|
|
457
|
+
switch (k.toLowerCase()) {
|
|
458
|
+
case "page":
|
|
459
|
+
return String(page);
|
|
460
|
+
case "pages":
|
|
461
|
+
return String(pages);
|
|
462
|
+
case "title":
|
|
463
|
+
return s.title ?? "";
|
|
464
|
+
case "subtitle":
|
|
465
|
+
return s.subtitle ?? "";
|
|
466
|
+
case "author":
|
|
467
|
+
return s.author ?? "";
|
|
468
|
+
default:
|
|
469
|
+
return s.date ?? "";
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
function templateParts(template, s) {
|
|
474
|
+
const filled = template.replace(/\{(title|subtitle|author|date)\}/gi, (m) => fillTemplate(m, s, "", ""));
|
|
475
|
+
const out = [];
|
|
476
|
+
let last = 0;
|
|
477
|
+
for (const m of filled.matchAll(/\{(page|pages)\}/gi)) {
|
|
478
|
+
if (m.index > last) out.push({ text: filled.slice(last, m.index) });
|
|
479
|
+
out.push({ field: m[1].toLowerCase() });
|
|
480
|
+
last = m.index + m[0].length;
|
|
481
|
+
}
|
|
482
|
+
if (last < filled.length) out.push({ text: filled.slice(last) });
|
|
483
|
+
return out;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/document/docx.ts
|
|
487
|
+
var TW = 20;
|
|
488
|
+
function strip(o) {
|
|
489
|
+
const out = {};
|
|
490
|
+
for (const [k, v] of Object.entries(o)) if (v !== void 0) out[k] = v;
|
|
491
|
+
return out;
|
|
492
|
+
}
|
|
493
|
+
var HEX = { text: "1F2937", heading: "111827", muted: "6B7280", link: "1D4ED8", rule: "D1D5DB", codeBg: "F6F8FA", danger: "B91C1C" };
|
|
494
|
+
var MONO = "Consolas";
|
|
495
|
+
function bm(c, id) {
|
|
496
|
+
let name = c.bookmarks.get(id);
|
|
497
|
+
if (name) return name;
|
|
498
|
+
const base = `m_${id.replace(/[^A-Za-z0-9_]/g, "_")}`.slice(0, 36);
|
|
499
|
+
name = base;
|
|
500
|
+
const used = new Set(c.bookmarks.values());
|
|
501
|
+
for (let n = 2; used.has(name); n++) name = `${base.slice(0, 33)}_${n}`;
|
|
502
|
+
c.bookmarks.set(id, name);
|
|
503
|
+
return name;
|
|
504
|
+
}
|
|
505
|
+
function runs(nodes, c, st = {}) {
|
|
506
|
+
const { d } = c;
|
|
507
|
+
const out = [];
|
|
508
|
+
const tr = (text, extra = {}) => new d.TextRun(
|
|
509
|
+
strip({
|
|
510
|
+
text,
|
|
511
|
+
bold: st.bold,
|
|
512
|
+
italics: st.italics,
|
|
513
|
+
strike: st.strike,
|
|
514
|
+
size: st.size ? st.size * 2 : void 0,
|
|
515
|
+
color: st.color,
|
|
516
|
+
...extra
|
|
517
|
+
})
|
|
518
|
+
);
|
|
519
|
+
for (const n of nodes) {
|
|
520
|
+
switch (n.type) {
|
|
521
|
+
case "text":
|
|
522
|
+
out.push(tr(n.value));
|
|
523
|
+
break;
|
|
524
|
+
case "strong":
|
|
525
|
+
out.push(...runs(n.children, c, { ...st, bold: true }));
|
|
526
|
+
break;
|
|
527
|
+
case "emphasis":
|
|
528
|
+
out.push(...runs(n.children, c, { ...st, italics: true }));
|
|
529
|
+
break;
|
|
530
|
+
case "delete":
|
|
531
|
+
out.push(...runs(n.children, c, { ...st, strike: true }));
|
|
532
|
+
break;
|
|
533
|
+
case "code":
|
|
534
|
+
out.push(tr(n.value, { font: MONO, shading: { type: d.ShadingType.CLEAR, fill: "F3F4F6", color: "auto" } }));
|
|
535
|
+
break;
|
|
536
|
+
case "break":
|
|
537
|
+
out.push(new d.TextRun({ break: 1 }));
|
|
538
|
+
break;
|
|
539
|
+
case "link":
|
|
540
|
+
if (/^(https?:|mailto:|tel:)/i.test(n.url)) {
|
|
541
|
+
out.push(
|
|
542
|
+
new d.ExternalHyperlink({
|
|
543
|
+
link: n.url,
|
|
544
|
+
children: runs(n.children, c, { ...st, color: HEX.link })
|
|
545
|
+
})
|
|
546
|
+
);
|
|
547
|
+
} else out.push(...runs(n.children, c, st));
|
|
548
|
+
break;
|
|
549
|
+
case "image":
|
|
550
|
+
out.push(tr(n.alt));
|
|
551
|
+
break;
|
|
552
|
+
case "xref": {
|
|
553
|
+
if (!n.resolved) {
|
|
554
|
+
out.push(tr(n.label, { bold: true, color: HEX.danger }));
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
const id = n.target.startsWith("sec:") ? n.target.slice(4) : n.target;
|
|
558
|
+
out.push(new d.InternalHyperlink({ anchor: bm(c, id), children: [tr(n.label, { color: HEX.link })] }));
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
case "cite":
|
|
562
|
+
if (n.resolved) {
|
|
563
|
+
out.push(new d.InternalHyperlink({ anchor: bm(c, `ref-${n.keys[0]}`), children: [tr(n.label)] }));
|
|
564
|
+
} else out.push(tr(n.label, { bold: true, color: HEX.danger }));
|
|
565
|
+
break;
|
|
566
|
+
case "noteRef":
|
|
567
|
+
out.push(new d.InternalHyperlink({ anchor: bm(c, `note-${n.n}`), children: [tr(String(n.n), { superScript: true, color: HEX.link })] }));
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return out;
|
|
572
|
+
}
|
|
573
|
+
function captionParagraph(cap, c, keepNext) {
|
|
574
|
+
const { d } = c;
|
|
575
|
+
const word = cap.kind === "fig" ? "Figure" : "Table";
|
|
576
|
+
const labelRuns = [new d.TextRun({ text: `${word} `, bold: true }), new d.SimpleField(`SEQ ${word} \\* ARABIC`, String(cap.number))];
|
|
577
|
+
const children = cap.id ? [new d.Bookmark({ id: bm(c, `${cap.kind}:${cap.id}`), children: labelRuns })] : labelRuns;
|
|
578
|
+
if (cap.children.length) children.push(new d.TextRun({ text: ": " }), ...runs(cap.children, c));
|
|
579
|
+
return new d.Paragraph({
|
|
580
|
+
style: "Caption",
|
|
581
|
+
keepNext,
|
|
582
|
+
keepLines: true,
|
|
583
|
+
alignment: cap.kind === "fig" ? d.AlignmentType.CENTER : d.AlignmentType.LEFT,
|
|
584
|
+
children
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
function numberingRef(c, ordered, start) {
|
|
588
|
+
const ref = ordered ? `matrx-num-${start}` : "matrx-bullets";
|
|
589
|
+
if (!c.numbering.has(ref)) {
|
|
590
|
+
const { d } = c;
|
|
591
|
+
const bullets = ["\u2022", "\u25E6", "\u25AA"];
|
|
592
|
+
const formats = [d.LevelFormat.DECIMAL, d.LevelFormat.LOWER_LETTER, d.LevelFormat.LOWER_ROMAN];
|
|
593
|
+
c.numbering.set(
|
|
594
|
+
ref,
|
|
595
|
+
Array.from({ length: 6 }, (_, level) => ({
|
|
596
|
+
level,
|
|
597
|
+
format: ordered ? formats[level % 3] : d.LevelFormat.BULLET,
|
|
598
|
+
text: ordered ? `%${level + 1}.` : bullets[level % 3],
|
|
599
|
+
start: ordered && level === 0 ? start : 1,
|
|
600
|
+
alignment: d.AlignmentType.LEFT,
|
|
601
|
+
style: { paragraph: { indent: { left: 360 * (level + 1) + 60, hanging: 300 } } }
|
|
602
|
+
}))
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
return ref;
|
|
606
|
+
}
|
|
607
|
+
function blocks(list, c, o = {}) {
|
|
608
|
+
const out = [];
|
|
609
|
+
for (const b of list) out.push(...block(b, c, o));
|
|
610
|
+
return out;
|
|
611
|
+
}
|
|
612
|
+
function para(c, o, opts) {
|
|
613
|
+
const { d } = c;
|
|
614
|
+
return new d.Paragraph(
|
|
615
|
+
strip({
|
|
616
|
+
...opts,
|
|
617
|
+
indent: o.indent ? { left: o.indent } : opts.indent,
|
|
618
|
+
border: o.quote ? { left: { style: d.BorderStyle.SINGLE, size: 18, color: HEX.rule, space: 8 } } : opts.border
|
|
619
|
+
})
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
function block(b, c, o) {
|
|
623
|
+
const { d } = c;
|
|
624
|
+
switch (b.type) {
|
|
625
|
+
case "heading": {
|
|
626
|
+
const level = Math.min(6, Math.max(1, b.level - c.minLevel + 1));
|
|
627
|
+
const heading = [d.HeadingLevel.HEADING_1, d.HeadingLevel.HEADING_2, d.HeadingLevel.HEADING_3, d.HeadingLevel.HEADING_4, d.HeadingLevel.HEADING_5, d.HeadingLevel.HEADING_6][level - 1];
|
|
628
|
+
const kids = [...b.number ? [new d.TextRun({ text: `${b.number} `, color: HEX.muted })] : [], ...runs(b.children, c)];
|
|
629
|
+
return [
|
|
630
|
+
new d.Paragraph({
|
|
631
|
+
heading,
|
|
632
|
+
keepNext: true,
|
|
633
|
+
keepLines: true,
|
|
634
|
+
children: [new d.Bookmark({ id: bm(c, b.id), children: kids })]
|
|
635
|
+
// The bibliography/notes headings are not numbered and still in the TOC.
|
|
636
|
+
})
|
|
637
|
+
];
|
|
638
|
+
}
|
|
639
|
+
case "paragraph":
|
|
640
|
+
return [para(c, o, { children: runs(b.children, c), widowControl: true })];
|
|
641
|
+
case "list": {
|
|
642
|
+
const level = o.listLevel ?? 0;
|
|
643
|
+
const ref = numberingRef(c, b.ordered, b.start);
|
|
644
|
+
const instance = ++c.listInstance;
|
|
645
|
+
const out = [];
|
|
646
|
+
for (const it of b.items) {
|
|
647
|
+
const [first, ...rest] = it.children;
|
|
648
|
+
const firstRuns = first?.type === "paragraph" ? runs(first.children, c) : [];
|
|
649
|
+
if (it.checked !== null) {
|
|
650
|
+
out.push(
|
|
651
|
+
para(c, { ...o, indent: (o.indent ?? 0) + 360 * (level + 1) }, {
|
|
652
|
+
children: [new d.TextRun({ text: it.checked ? "\u2612 " : "\u2610 ", font: "Segoe UI Symbol" }), ...firstRuns],
|
|
653
|
+
widowControl: true
|
|
654
|
+
})
|
|
655
|
+
);
|
|
656
|
+
} else {
|
|
657
|
+
out.push(new d.Paragraph({ numbering: { reference: ref, level, instance }, children: firstRuns, widowControl: true }));
|
|
658
|
+
}
|
|
659
|
+
const tail = first && first.type !== "paragraph" ? [first, ...rest] : rest;
|
|
660
|
+
for (const child of tail) {
|
|
661
|
+
if (child.type === "list") out.push(...block(child, c, { ...o, listLevel: level + 1 }));
|
|
662
|
+
else out.push(...block(child, c, { ...o, indent: (o.indent ?? 0) + 360 * (level + 1) + 60 }));
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return out;
|
|
666
|
+
}
|
|
667
|
+
case "blockquote":
|
|
668
|
+
return blocks(b.children, c, { ...o, indent: (o.indent ?? 0) + 360, quote: true });
|
|
669
|
+
case "code": {
|
|
670
|
+
const lines = b.value.replace(/\n$/, "").split("\n");
|
|
671
|
+
const keep = lines.length <= 45;
|
|
672
|
+
return lines.map(
|
|
673
|
+
(l, i) => new d.Paragraph({
|
|
674
|
+
style: "SourceCode",
|
|
675
|
+
keepNext: keep && i < lines.length - 1,
|
|
676
|
+
keepLines: true,
|
|
677
|
+
...o.indent ? { indent: { left: o.indent } } : {},
|
|
678
|
+
children: [new d.TextRun({ text: l.replace(/\t/g, " ") || " ", font: MONO })]
|
|
679
|
+
})
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
case "table": {
|
|
683
|
+
const n = Math.max(b.header.length, ...b.rows.map((r) => r.length), 1);
|
|
684
|
+
const keep = b.rows.length <= 20;
|
|
685
|
+
const alignOf = (i) => b.align[i] === "center" ? d.AlignmentType.CENTER : b.align[i] === "right" ? d.AlignmentType.RIGHT : d.AlignmentType.LEFT;
|
|
686
|
+
const border = { style: d.BorderStyle.SINGLE, size: 4, color: "E5E7EB" };
|
|
687
|
+
const none = { style: d.BorderStyle.NONE, size: 0, color: "FFFFFF" };
|
|
688
|
+
const row = (cells, head, last) => new d.TableRow({
|
|
689
|
+
tableHeader: head,
|
|
690
|
+
cantSplit: true,
|
|
691
|
+
children: Array.from(
|
|
692
|
+
{ length: n },
|
|
693
|
+
(_, i) => new d.TableCell({
|
|
694
|
+
...head ? { shading: { type: d.ShadingType.CLEAR, fill: "F3F4F6", color: "auto" } } : {},
|
|
695
|
+
margins: { top: 60, bottom: 60, left: 100, right: 100 },
|
|
696
|
+
borders: { top: none, left: none, right: none, bottom: head ? { ...border, size: 10, color: HEX.heading } : border },
|
|
697
|
+
children: [
|
|
698
|
+
new d.Paragraph({
|
|
699
|
+
alignment: alignOf(i),
|
|
700
|
+
keepNext: keep && !last,
|
|
701
|
+
children: runs(cells[i] ?? [], c, head ? { bold: true } : {})
|
|
702
|
+
})
|
|
703
|
+
]
|
|
704
|
+
})
|
|
705
|
+
)
|
|
706
|
+
});
|
|
707
|
+
const out = [];
|
|
708
|
+
if (b.caption) out.push(captionParagraph(b.caption, c, true));
|
|
709
|
+
out.push(
|
|
710
|
+
new d.Table({
|
|
711
|
+
width: { size: 100, type: d.WidthType.PERCENTAGE },
|
|
712
|
+
rows: [row(b.header, true, b.rows.length === 0), ...b.rows.map((r, i) => row(r, false, i === b.rows.length - 1))]
|
|
713
|
+
})
|
|
714
|
+
);
|
|
715
|
+
out.push(new d.Paragraph({ spacing: { after: 60 }, children: [] }));
|
|
716
|
+
return out;
|
|
717
|
+
}
|
|
718
|
+
case "figure": {
|
|
719
|
+
const img = c.images?.get(b.url);
|
|
720
|
+
const out = [];
|
|
721
|
+
if (img) {
|
|
722
|
+
const maxW = c.width;
|
|
723
|
+
const maxH = (c.s.page.height - c.s.page.margins.top - c.s.page.margins.bottom) * 0.6;
|
|
724
|
+
const scale = Math.min(1, maxW / img.width, maxH / img.height);
|
|
725
|
+
const px = (v) => Math.round(v * scale * (96 / 72));
|
|
726
|
+
out.push(
|
|
727
|
+
new d.Paragraph({
|
|
728
|
+
alignment: d.AlignmentType.CENTER,
|
|
729
|
+
keepNext: Boolean(b.caption),
|
|
730
|
+
keepLines: true,
|
|
731
|
+
children: [
|
|
732
|
+
new d.ImageRun({
|
|
733
|
+
type: img.mime === "image/png" ? "png" : img.mime === "image/gif" ? "gif" : "jpg",
|
|
734
|
+
data: img.bytes,
|
|
735
|
+
transformation: { width: px(img.width), height: px(img.height) },
|
|
736
|
+
altText: { name: b.alt || "image", description: b.alt, title: b.alt }
|
|
737
|
+
})
|
|
738
|
+
]
|
|
739
|
+
})
|
|
740
|
+
);
|
|
741
|
+
} else {
|
|
742
|
+
out.push(
|
|
743
|
+
new d.Paragraph({
|
|
744
|
+
alignment: d.AlignmentType.CENTER,
|
|
745
|
+
keepNext: Boolean(b.caption),
|
|
746
|
+
children: [new d.TextRun({ text: `[Image unavailable: ${b.alt || b.url}]`, italics: true, color: HEX.muted })]
|
|
747
|
+
})
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
if (b.caption) out.push(captionParagraph(b.caption, c, false));
|
|
751
|
+
return out;
|
|
752
|
+
}
|
|
753
|
+
case "rule":
|
|
754
|
+
return [new d.Paragraph({ border: { bottom: { style: d.BorderStyle.SINGLE, size: 6, color: HEX.rule, space: 1 } }, children: [] })];
|
|
755
|
+
case "pageBreak":
|
|
756
|
+
return [new d.Paragraph({ children: [new d.PageBreak()] })];
|
|
757
|
+
case "toc":
|
|
758
|
+
return [
|
|
759
|
+
new d.Paragraph({ style: "TOCHeading", keepNext: true, children: [new d.TextRun(c.s.toc.title)] }),
|
|
760
|
+
new d.TableOfContents(c.s.toc.title, {
|
|
761
|
+
hyperlink: true,
|
|
762
|
+
headingStyleRange: `1-${c.s.toc.depth}`,
|
|
763
|
+
cachedEntries: c.tree.toc.map((e) => {
|
|
764
|
+
const page = pagesFor(c).get(e.id);
|
|
765
|
+
return { title: e.text, level: e.level, href: bm(c, e.id), ...page ? { page } : {} };
|
|
766
|
+
})
|
|
767
|
+
})
|
|
768
|
+
];
|
|
769
|
+
case "notes":
|
|
770
|
+
return c.tree.notes.flatMap((n) => {
|
|
771
|
+
const [first, ...rest] = n.children;
|
|
772
|
+
const lead = new d.Paragraph({
|
|
773
|
+
indent: { left: 360, hanging: 360 },
|
|
774
|
+
children: [
|
|
775
|
+
new d.Bookmark({ id: bm(c, `note-${n.n}`), children: [new d.TextRun({ text: `${n.n}. `, color: HEX.muted })] }),
|
|
776
|
+
...first?.type === "paragraph" ? runs(first.children, c) : []
|
|
777
|
+
]
|
|
778
|
+
});
|
|
779
|
+
const tail = first && first.type !== "paragraph" ? [first, ...rest] : rest;
|
|
780
|
+
return [lead, ...blocks(tail, c, { indent: 360 })];
|
|
781
|
+
});
|
|
782
|
+
case "bibliography":
|
|
783
|
+
return c.tree.bibliography.map(
|
|
784
|
+
(e) => new d.Paragraph({
|
|
785
|
+
indent: { left: 720, hanging: 720 },
|
|
786
|
+
spacing: { after: 120 },
|
|
787
|
+
children: [new d.Bookmark({ id: bm(c, `ref-${e.id}`), children: [] }), ...runs(e.children, c)]
|
|
788
|
+
})
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
var PAGES_KEY = /* @__PURE__ */ Symbol("pages");
|
|
793
|
+
function pagesFor(c) {
|
|
794
|
+
return c[PAGES_KEY] ??= /* @__PURE__ */ new Map();
|
|
795
|
+
}
|
|
796
|
+
function runningParagraph(t2, c, widthPt) {
|
|
797
|
+
const { d } = c;
|
|
798
|
+
const slot = (v) => v ? templateParts(v, c.s).map(
|
|
799
|
+
(p) => "field" in p ? new d.TextRun({ children: [p.field === "page" ? d.PageNumber.CURRENT : d.PageNumber.TOTAL_PAGES], size: 18, color: HEX.muted }) : new d.TextRun({ text: p.text, size: 18, color: HEX.muted })
|
|
800
|
+
) : [];
|
|
801
|
+
return new d.Paragraph({
|
|
802
|
+
tabStops: [
|
|
803
|
+
{ type: d.TabStopType.CENTER, position: Math.round(widthPt / 2 * TW) },
|
|
804
|
+
{ type: d.TabStopType.RIGHT, position: Math.round(widthPt * TW) }
|
|
805
|
+
],
|
|
806
|
+
children: [...slot(t2.left), new d.TextRun({ children: [new d.Tab()] }), ...slot(t2.center), new d.TextRun({ children: [new d.Tab()] }), ...slot(t2.right)]
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
function coverChildren(c) {
|
|
810
|
+
const { d, s } = c;
|
|
811
|
+
const out = [];
|
|
812
|
+
const pageH = (s.page.orientation === "landscape" ? s.page.width : s.page.height) - s.page.margins.top - s.page.margins.bottom;
|
|
813
|
+
out.push(new d.Paragraph({ spacing: { before: Math.round(pageH * 0.28 * TW) }, children: [] }));
|
|
814
|
+
const logo = s.logo ? c.images?.get(s.logo) : void 0;
|
|
815
|
+
if (logo) {
|
|
816
|
+
const scale = Math.min(1, 160 / logo.width, 72 / logo.height);
|
|
817
|
+
out.push(
|
|
818
|
+
new d.Paragraph({
|
|
819
|
+
spacing: { after: 480 },
|
|
820
|
+
children: [
|
|
821
|
+
new d.ImageRun({
|
|
822
|
+
type: logo.mime === "image/png" ? "png" : logo.mime === "image/gif" ? "gif" : "jpg",
|
|
823
|
+
data: logo.bytes,
|
|
824
|
+
transformation: { width: Math.round(logo.width * scale * (96 / 72)), height: Math.round(logo.height * scale * (96 / 72)) }
|
|
825
|
+
})
|
|
826
|
+
]
|
|
827
|
+
})
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
out.push(new d.Paragraph({ style: "Title", children: [new d.TextRun(s.title ?? "")] }));
|
|
831
|
+
if (s.subtitle) out.push(new d.Paragraph({ style: "Subtitle", children: [new d.TextRun(s.subtitle)] }));
|
|
832
|
+
if (s.author) out.push(new d.Paragraph({ spacing: { before: 720 }, children: [new d.TextRun({ text: s.author, size: 24, color: "4B5563" })] }));
|
|
833
|
+
if (s.date) out.push(new d.Paragraph({ children: [new d.TextRun({ text: s.date, size: 24, color: "4B5563" })] }));
|
|
834
|
+
out.push(new d.Paragraph({ children: [new d.PageBreak()] }));
|
|
835
|
+
return out;
|
|
836
|
+
}
|
|
837
|
+
async function documentToDocx(tree, options = {}) {
|
|
838
|
+
const d = await import("docx");
|
|
839
|
+
const s = tree.settings;
|
|
840
|
+
const levels = [];
|
|
841
|
+
for (const sec of tree.sections) for (const b of sec.blocks) if (b.type === "heading") levels.push(b.level);
|
|
842
|
+
const c = {
|
|
843
|
+
d,
|
|
844
|
+
tree,
|
|
845
|
+
s,
|
|
846
|
+
images: options.images,
|
|
847
|
+
bookmarks: /* @__PURE__ */ new Map(),
|
|
848
|
+
numbering: /* @__PURE__ */ new Map(),
|
|
849
|
+
listInstance: 0,
|
|
850
|
+
width: s.page.width - s.page.margins.left - s.page.margins.right,
|
|
851
|
+
tocIds: new Set(tree.toc.map((e) => e.id)),
|
|
852
|
+
minLevel: Math.min(7, ...levels)
|
|
853
|
+
};
|
|
854
|
+
if (options.headingPages) for (const [k, v] of options.headingPages) pagesFor(c).set(k, v);
|
|
855
|
+
const m = s.page.margins;
|
|
856
|
+
const sections = tree.sections.map((sec, idx) => {
|
|
857
|
+
const landscape = sec.orientation === "landscape";
|
|
858
|
+
const pageW = landscape ? s.page.height : s.page.width;
|
|
859
|
+
const full = pageW - m.left - m.right;
|
|
860
|
+
const gap = 18;
|
|
861
|
+
c.width = (full - gap * (sec.columns - 1)) / sec.columns;
|
|
862
|
+
const children = [];
|
|
863
|
+
if (idx === 0 && s.cover) children.push(...coverChildren(c));
|
|
864
|
+
children.push(...blocks(sec.blocks, c));
|
|
865
|
+
if (children.length === 0) children.push(new d.Paragraph({ children: [] }));
|
|
866
|
+
const header = s.header ? new d.Header({ children: [runningParagraph(s.header, c, full)] }) : void 0;
|
|
867
|
+
const footer = s.footer ? new d.Footer({ children: [runningParagraph(s.footer, c, full)] }) : void 0;
|
|
868
|
+
const firstPageOnly = idx === 0 && s.differentFirstPage;
|
|
869
|
+
return strip({
|
|
870
|
+
properties: strip({
|
|
871
|
+
type: d.SectionType.NEXT_PAGE,
|
|
872
|
+
titlePage: firstPageOnly,
|
|
873
|
+
page: {
|
|
874
|
+
size: { width: Math.round(s.page.width * TW), height: Math.round(s.page.height * TW), orientation: landscape ? d.PageOrientation.LANDSCAPE : d.PageOrientation.PORTRAIT },
|
|
875
|
+
margin: {
|
|
876
|
+
top: Math.round(m.top * TW),
|
|
877
|
+
right: Math.round(m.right * TW),
|
|
878
|
+
bottom: Math.round(m.bottom * TW),
|
|
879
|
+
left: Math.round(m.left * TW),
|
|
880
|
+
header: Math.round(Math.max(18, m.top / 2 - 6) * TW),
|
|
881
|
+
footer: Math.round(Math.max(18, m.bottom / 2 - 6) * TW)
|
|
882
|
+
}
|
|
883
|
+
},
|
|
884
|
+
column: sec.columns > 1 ? { count: sec.columns, space: gap * TW, equalWidth: true } : void 0
|
|
885
|
+
}),
|
|
886
|
+
headers: header ? { default: header, ...firstPageOnly ? { first: new d.Header({ children: [new d.Paragraph({ children: [] })] }) } : {} } : void 0,
|
|
887
|
+
footers: footer ? { default: footer, ...firstPageOnly ? { first: new d.Footer({ children: [new d.Paragraph({ children: [] })] }) } : {} } : void 0,
|
|
888
|
+
children
|
|
889
|
+
});
|
|
890
|
+
});
|
|
891
|
+
const size = s.fontSize * 2;
|
|
892
|
+
const heading = (id, name, pt2, level) => ({
|
|
893
|
+
id,
|
|
894
|
+
name,
|
|
895
|
+
basedOn: "Normal",
|
|
896
|
+
next: "Normal",
|
|
897
|
+
quickFormat: true,
|
|
898
|
+
run: { font: "Calibri", size: Math.round(pt2 * 2), bold: true, color: HEX.heading },
|
|
899
|
+
paragraph: { spacing: { before: Math.round(pt2 * 14), after: Math.round(pt2 * 5) }, keepNext: true, keepLines: true, outlineLevel: level }
|
|
900
|
+
});
|
|
901
|
+
const doc = new d.Document({
|
|
902
|
+
creator: s.author ?? "AI Matrx",
|
|
903
|
+
title: s.title ?? "Document",
|
|
904
|
+
...s.subtitle ? { description: s.subtitle } : {},
|
|
905
|
+
features: { updateFields: true },
|
|
906
|
+
hyphenation: { autoHyphenation: s.hyphenate, doNotHyphenateCaps: true },
|
|
907
|
+
numbering: { config: [...c.numbering.entries()].map(([reference, levels2]) => ({ reference, levels: levels2 })) },
|
|
908
|
+
styles: {
|
|
909
|
+
default: {
|
|
910
|
+
document: { run: { font: "Cambria", size, color: HEX.text }, paragraph: { spacing: { after: 140, line: 300 } } }
|
|
911
|
+
},
|
|
912
|
+
paragraphStyles: [
|
|
913
|
+
heading("Heading1", "Heading 1", s.fontSize * 1.8, 0),
|
|
914
|
+
heading("Heading2", "Heading 2", s.fontSize * 1.45, 1),
|
|
915
|
+
heading("Heading3", "Heading 3", s.fontSize * 1.2, 2),
|
|
916
|
+
heading("Heading4", "Heading 4", s.fontSize * 1.05, 3),
|
|
917
|
+
heading("Heading5", "Heading 5", s.fontSize, 4),
|
|
918
|
+
heading("Heading6", "Heading 6", s.fontSize, 5),
|
|
919
|
+
{ id: "Title", name: "Title", basedOn: "Normal", run: { font: "Calibri", size: 60, bold: true, color: HEX.heading }, paragraph: { spacing: { after: 200 } } },
|
|
920
|
+
{ id: "Subtitle", name: "Subtitle", basedOn: "Normal", run: { font: "Calibri", size: 32, color: "4B5563" } },
|
|
921
|
+
{ id: "Caption", name: "Caption", basedOn: "Normal", next: "Normal", run: { font: "Calibri", size: Math.round(s.fontSize * 1.8), color: HEX.muted }, paragraph: { spacing: { before: 60, after: 160 } } },
|
|
922
|
+
{ id: "TOCHeading", name: "TOC Heading", basedOn: "Normal", next: "Normal", run: { font: "Calibri", size: Math.round(s.fontSize * 2.9), bold: true, color: HEX.heading }, paragraph: { spacing: { after: 200 } } },
|
|
923
|
+
{ id: "SourceCode", name: "Source Code", basedOn: "Normal", run: { font: MONO, size: Math.round(s.fontSize * 1.6) }, paragraph: { spacing: { after: 0, line: 260 }, shading: { type: d.ShadingType.CLEAR, fill: HEX.codeBg, color: "auto" } } }
|
|
924
|
+
]
|
|
925
|
+
},
|
|
926
|
+
sections
|
|
927
|
+
});
|
|
928
|
+
const buf = await d.Packer.toArrayBuffer(doc);
|
|
929
|
+
return renumberBookmarks(new Uint8Array(buf));
|
|
930
|
+
}
|
|
931
|
+
function renumberBookmarkXml(xml) {
|
|
932
|
+
let n = 0;
|
|
933
|
+
let open = null;
|
|
934
|
+
return xml.replace(/<w:bookmark(Start|End)\b([^>]*?)w:id="\d+"/g, (_m, kind, attrs) => {
|
|
935
|
+
if (kind === "Start") {
|
|
936
|
+
open = ++n;
|
|
937
|
+
return `<w:bookmarkStart${attrs}w:id="${open}"`;
|
|
938
|
+
}
|
|
939
|
+
const id = open ?? ++n;
|
|
940
|
+
open = null;
|
|
941
|
+
return `<w:bookmarkEnd${attrs}w:id="${id}"`;
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
async function renumberBookmarks(bytes) {
|
|
945
|
+
const JSZipMod = await import("jszip");
|
|
946
|
+
const JSZip = JSZipMod.default ?? JSZipMod;
|
|
947
|
+
const zip = await JSZip.loadAsync(bytes);
|
|
948
|
+
const entry = zip.file("word/document.xml");
|
|
949
|
+
if (!entry) return bytes;
|
|
950
|
+
const xml = await entry.async("string");
|
|
951
|
+
zip.file("word/document.xml", renumberBookmarkXml(xml));
|
|
952
|
+
return zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// src/document/epub.ts
|
|
956
|
+
var import_html_escape2 = require("@ai-matrx/kit/html-escape");
|
|
957
|
+
|
|
958
|
+
// src/document/html.ts
|
|
959
|
+
var import_html_escape = require("@ai-matrx/kit/html-escape");
|
|
960
|
+
|
|
961
|
+
// src/directives.ts
|
|
962
|
+
var PAGE_BREAK_CLASS = "matrx-page-break";
|
|
963
|
+
var PAGE_BREAK_LABEL = "Page break";
|
|
964
|
+
var COMMENT_FORM = /^<!--\s*(?:page[\s_-]?break|new[\s_-]?page)\s*-->$/i;
|
|
965
|
+
var LATEX_FORM = /^\\(?:pagebreak|newpage|clearpage)\s*(?:\{\s*\})?$/i;
|
|
966
|
+
var DIV_FORM = /^<div\b([^>]*)>\s*<\/div>$|^<div\b([^>]*)\/>$/i;
|
|
967
|
+
var DIV_BREAK_ATTR = /(?:page-break-(?:after|before)\s*:\s*always|break-(?:after|before)\s*:\s*page|class\s*=\s*["'](?:[^"']*\s)?page-?break(?:\s[^"']*)?["'])/i;
|
|
968
|
+
function indentColumns(line) {
|
|
969
|
+
let cols = 0;
|
|
970
|
+
for (const ch of line) {
|
|
971
|
+
if (ch === " " || ch === "\xA0") cols += 1;
|
|
972
|
+
else if (ch === " ") cols += 4;
|
|
973
|
+
else break;
|
|
974
|
+
}
|
|
975
|
+
return cols;
|
|
976
|
+
}
|
|
977
|
+
function isPageBreakLine(line) {
|
|
978
|
+
if (indentColumns(line) >= 4) return false;
|
|
979
|
+
const t2 = line.trim();
|
|
980
|
+
if (t2.length < 8 || t2.length > 200) return false;
|
|
981
|
+
if (COMMENT_FORM.test(t2) || LATEX_FORM.test(t2)) return true;
|
|
982
|
+
const div = DIV_FORM.exec(t2);
|
|
983
|
+
if (!div) return false;
|
|
984
|
+
return DIV_BREAK_ATTR.test(div[1] ?? div[2] ?? "");
|
|
985
|
+
}
|
|
986
|
+
var SECTION_FORM = /^<!--\s*section(?:[\s_-]?break)?\b\s*:?\s*([^>]*?)\s*-->$/i;
|
|
987
|
+
function parseSectionBreakLine(line) {
|
|
988
|
+
if (indentColumns(line) >= 4) return null;
|
|
989
|
+
const m = SECTION_FORM.exec(line.trim());
|
|
990
|
+
if (!m) return null;
|
|
991
|
+
const words = (m[1] ?? "").toLowerCase();
|
|
992
|
+
const out = {};
|
|
993
|
+
if (/\blandscape\b/.test(words)) out.orientation = "landscape";
|
|
994
|
+
else if (/\bportrait\b/.test(words)) out.orientation = "portrait";
|
|
995
|
+
const cols = /\b(?:columns?|cols?)\s*[=:]\s*(\d)\b/.exec(words) ?? /\b(\d)\s*[-\s]?columns?\b/.exec(words);
|
|
996
|
+
if (cols) {
|
|
997
|
+
const n = Math.min(3, Math.max(1, Number(cols[1])));
|
|
998
|
+
out.columns = n;
|
|
999
|
+
}
|
|
1000
|
+
return out;
|
|
1001
|
+
}
|
|
1002
|
+
var TOC_FORM = /^(?:\[\[\s*toc\s*\]\]|\[toc\]|\$\{toc\}|<!--\s*toc\s*-->|\\tableofcontents)$/i;
|
|
1003
|
+
var BIBLIOGRAPHY_FORM = /^(?:\[\[\s*(?:bibliography|references)\s*\]\]|<!--\s*(?:bibliography|references)\s*-->|\\printbibliography|\\bibliography\{[^}]*\})$/i;
|
|
1004
|
+
function isTocLine(line) {
|
|
1005
|
+
return indentColumns(line) < 4 && TOC_FORM.test(line.trim());
|
|
1006
|
+
}
|
|
1007
|
+
function isBibliographyLine(line) {
|
|
1008
|
+
return indentColumns(line) < 4 && BIBLIOGRAPHY_FORM.test(line.trim());
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/document/html.ts
|
|
1012
|
+
var SAFE_URL = /^(?:https?:|mailto:|tel:|#|\/|\.{0,2}\/|data:image\/(?:png|jpe?g|gif|webp);)/i;
|
|
1013
|
+
function safeUrl(url) {
|
|
1014
|
+
const u = url.trim();
|
|
1015
|
+
if (!u) return null;
|
|
1016
|
+
if (!/^[a-z][a-z0-9+.-]*:/i.test(u)) return u;
|
|
1017
|
+
return SAFE_URL.test(u) ? u : null;
|
|
1018
|
+
}
|
|
1019
|
+
var br = (x) => x.xhtml ? "<br/>" : "<br>";
|
|
1020
|
+
function renderInlineHtml(nodes, x) {
|
|
1021
|
+
return nodes.map((n) => {
|
|
1022
|
+
switch (n.type) {
|
|
1023
|
+
case "text":
|
|
1024
|
+
return (0, import_html_escape.escapeHtml)(n.value);
|
|
1025
|
+
case "strong":
|
|
1026
|
+
return `<strong>${renderInlineHtml(n.children, x)}</strong>`;
|
|
1027
|
+
case "emphasis":
|
|
1028
|
+
return `<em>${renderInlineHtml(n.children, x)}</em>`;
|
|
1029
|
+
case "delete":
|
|
1030
|
+
return `<del>${renderInlineHtml(n.children, x)}</del>`;
|
|
1031
|
+
case "code":
|
|
1032
|
+
return `<code>${(0, import_html_escape.escapeHtml)(n.value)}</code>`;
|
|
1033
|
+
case "break":
|
|
1034
|
+
return br(x);
|
|
1035
|
+
case "link": {
|
|
1036
|
+
const href2 = safeUrl(n.url);
|
|
1037
|
+
const inner = renderInlineHtml(n.children, x);
|
|
1038
|
+
return href2 ? `<a href="${(0, import_html_escape.escapeHtml)(href2)}">${inner}</a>` : inner;
|
|
1039
|
+
}
|
|
1040
|
+
case "image": {
|
|
1041
|
+
const src = imageSrc(n.url, x);
|
|
1042
|
+
if (!src) return (0, import_html_escape.escapeHtml)(n.alt);
|
|
1043
|
+
return `<img class="doc-inline-img" src="${(0, import_html_escape.escapeHtml)(src)}" alt="${(0, import_html_escape.escapeHtml)(n.alt)}"${x.xhtml ? "/" : ""}>`;
|
|
1044
|
+
}
|
|
1045
|
+
case "xref": {
|
|
1046
|
+
if (!n.resolved) return `<span class="doc-xref-missing">${(0, import_html_escape.escapeHtml)(n.label)}</span>`;
|
|
1047
|
+
const id = n.target.startsWith("sec:") ? n.target.slice(4) : n.target;
|
|
1048
|
+
return `<a class="doc-xref" href="${(0, import_html_escape.escapeHtml)(href(id, x))}">${(0, import_html_escape.escapeHtml)(n.label)}</a>`;
|
|
1049
|
+
}
|
|
1050
|
+
case "cite":
|
|
1051
|
+
return n.resolved ? `<a class="doc-cite" href="${(0, import_html_escape.escapeHtml)(href("ref-" + (n.keys[0] ?? ""), x))}">${(0, import_html_escape.escapeHtml)(n.label)}</a>` : `<span class="doc-cite doc-cite-missing">${(0, import_html_escape.escapeHtml)(n.label)}</span>`;
|
|
1052
|
+
case "noteRef":
|
|
1053
|
+
return `<sup class="doc-note-ref"><a href="${(0, import_html_escape.escapeHtml)(href(`note-${n.n}`, x))}" id="noteref-${n.n}">${n.n}</a></sup>`;
|
|
1054
|
+
default:
|
|
1055
|
+
return "";
|
|
1056
|
+
}
|
|
1057
|
+
}).join("");
|
|
1058
|
+
}
|
|
1059
|
+
var href = (id, x) => x.hrefFor ? x.hrefFor(id) : `#${id}`;
|
|
1060
|
+
function imageSrc(url, x) {
|
|
1061
|
+
if (x.srcFor) return x.srcFor(url);
|
|
1062
|
+
const resolved = x.images?.get(url);
|
|
1063
|
+
if (resolved) return resolved.dataUrl;
|
|
1064
|
+
return safeUrl(url);
|
|
1065
|
+
}
|
|
1066
|
+
function captionHtml(c, tag, x) {
|
|
1067
|
+
const text = renderInlineHtml(c.children, x);
|
|
1068
|
+
return `<${tag} class="doc-caption"><span class="doc-caption-label">${c.label}</span>${text ? `: ${text}` : ""}</${tag}>`;
|
|
1069
|
+
}
|
|
1070
|
+
function tocHtml(x) {
|
|
1071
|
+
const { tree } = x;
|
|
1072
|
+
const items = tree.toc.map(
|
|
1073
|
+
(e) => `<li class="toc-l${e.level}"><a href="${(0, import_html_escape.escapeHtml)(href(e.id, x))}"><span class="toc-text">${(0, import_html_escape.escapeHtml)(e.text)}</span><span class="toc-page"></span></a></li>`
|
|
1074
|
+
).join("");
|
|
1075
|
+
return `<nav class="doc-toc" role="doc-toc"><h2 class="doc-toc-title">${(0, import_html_escape.escapeHtml)(
|
|
1076
|
+
tree.settings.toc.title
|
|
1077
|
+
)}</h2><ol>${items}</ol></nav>`;
|
|
1078
|
+
}
|
|
1079
|
+
function renderBlocksHtml(blocks3, x) {
|
|
1080
|
+
return blocks3.map((b) => renderBlockHtml(b, x)).join("\n");
|
|
1081
|
+
}
|
|
1082
|
+
function renderBlockHtml(b, x) {
|
|
1083
|
+
switch (b.type) {
|
|
1084
|
+
case "heading": {
|
|
1085
|
+
const num = b.number ? `<span class="doc-heading-number">${b.number}</span> ` : "";
|
|
1086
|
+
return `<h${b.level} id="${(0, import_html_escape.escapeHtml)(b.id)}">${num}${renderInlineHtml(b.children, x)}</h${b.level}>`;
|
|
1087
|
+
}
|
|
1088
|
+
case "paragraph":
|
|
1089
|
+
return `<p>${renderInlineHtml(b.children, x)}</p>`;
|
|
1090
|
+
case "list": {
|
|
1091
|
+
const tag = b.ordered ? "ol" : "ul";
|
|
1092
|
+
const start = b.ordered && b.start !== 1 ? ` start="${b.start}"` : "";
|
|
1093
|
+
const task = b.items.some((i) => i.checked !== null);
|
|
1094
|
+
const items = b.items.map((i) => {
|
|
1095
|
+
const box = i.checked === null ? "" : `<span class="doc-task-box${i.checked ? " is-checked" : ""}" role="img" aria-label="${i.checked ? "done" : "not done"}"></span>`;
|
|
1096
|
+
const inner = i.children.length === 1 && i.children[0]?.type === "paragraph" ? renderInlineHtml(i.children[0].children, x) : renderBlocksHtml(i.children, x);
|
|
1097
|
+
return `<li${i.checked !== null ? ' class="doc-task"' : ""}>${box}${inner}</li>`;
|
|
1098
|
+
}).join("");
|
|
1099
|
+
return `<${tag}${start}${task ? ' class="doc-task-list"' : ""}>${items}</${tag}>`;
|
|
1100
|
+
}
|
|
1101
|
+
case "blockquote":
|
|
1102
|
+
return `<blockquote>${renderBlocksHtml(b.children, x)}</blockquote>`;
|
|
1103
|
+
case "code":
|
|
1104
|
+
return `<pre class="doc-code"${b.lang ? ` data-language="${(0, import_html_escape.escapeHtml)(b.lang)}"` : ""}><code>${(0, import_html_escape.escapeHtml)(
|
|
1105
|
+
b.value
|
|
1106
|
+
)}</code></pre>`;
|
|
1107
|
+
case "table": {
|
|
1108
|
+
const al = (i) => {
|
|
1109
|
+
const a = b.align[i];
|
|
1110
|
+
return a ? ` style="text-align:${a}"` : "";
|
|
1111
|
+
};
|
|
1112
|
+
const head = `<thead><tr>${b.header.map((c, i) => `<th${al(i)}>${renderInlineHtml(c, x)}</th>`).join("")}</tr></thead>`;
|
|
1113
|
+
const body = `<tbody>${b.rows.map((r) => `<tr>${r.map((c, i) => `<td${al(i)}>${renderInlineHtml(c, x)}</td>`).join("")}</tr>`).join("")}</tbody>`;
|
|
1114
|
+
const id = b.caption?.id ? ` id="tbl:${(0, import_html_escape.escapeHtml)(b.caption.id)}"` : "";
|
|
1115
|
+
return `<div class="doc-table-wrap"${id}><table>${b.caption ? captionHtml(b.caption, "caption", x) : ""}${head}${body}</table></div>`;
|
|
1116
|
+
}
|
|
1117
|
+
case "figure": {
|
|
1118
|
+
const src = imageSrc(b.url, x);
|
|
1119
|
+
const id = b.caption?.id ? ` id="fig:${(0, import_html_escape.escapeHtml)(b.caption.id)}"` : "";
|
|
1120
|
+
const img = src ? `<img src="${(0, import_html_escape.escapeHtml)(src)}" alt="${(0, import_html_escape.escapeHtml)(b.alt)}"${x.xhtml ? "/" : ""}>` : `<div class="doc-image-missing">Image unavailable: ${(0, import_html_escape.escapeHtml)(b.alt || b.url)}</div>`;
|
|
1121
|
+
return `<figure class="doc-figure"${id}>${img}${b.caption ? captionHtml(b.caption, "figcaption", x) : ""}</figure>`;
|
|
1122
|
+
}
|
|
1123
|
+
case "rule":
|
|
1124
|
+
return `<hr${x.xhtml ? "/" : ""}>`;
|
|
1125
|
+
case "pageBreak":
|
|
1126
|
+
return `<div class="${PAGE_BREAK_CLASS}" role="separator" aria-label="Page break"></div>`;
|
|
1127
|
+
case "toc":
|
|
1128
|
+
return tocHtml(x);
|
|
1129
|
+
case "notes":
|
|
1130
|
+
return `<ol class="doc-notes">${x.tree.notes.map(
|
|
1131
|
+
(n) => `<li id="note-${n.n}">${renderBlocksHtml(n.children, x)} <a class="doc-note-back" href="${(0, import_html_escape.escapeHtml)(
|
|
1132
|
+
href(`noteref-${n.n}`, x)
|
|
1133
|
+
)}" aria-label="Back to text">\u21A9</a></li>`
|
|
1134
|
+
).join("")}</ol>`;
|
|
1135
|
+
case "bibliography":
|
|
1136
|
+
return `<div class="doc-bibliography" role="doc-bibliography">${x.tree.bibliography.map((e) => `<p class="doc-ref" id="ref-${(0, import_html_escape.escapeHtml)(e.id)}">${renderInlineHtml(e.children, x)}</p>`).join("")}</div>`;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
function coverHtml(s, x) {
|
|
1140
|
+
const logo = s.logo ? imageSrc(s.logo, x) : null;
|
|
1141
|
+
return `<section class="doc-cover">${logo ? `<img class="doc-cover-logo" src="${(0, import_html_escape.escapeHtml)(logo)}" alt=""${x.xhtml ? "/" : ""}>` : ""}<h1 class="doc-cover-title">${(0, import_html_escape.escapeHtml)(
|
|
1142
|
+
s.title ?? ""
|
|
1143
|
+
)}</h1>${s.subtitle ? `<p class="doc-cover-subtitle">${(0, import_html_escape.escapeHtml)(s.subtitle)}</p>` : ""}${s.author ? `<p class="doc-cover-author">${(0, import_html_escape.escapeHtml)(s.author)}</p>` : ""}${s.date ? `<p class="doc-cover-date">${(0, import_html_escape.escapeHtml)(s.date)}</p>` : ""}</section>`;
|
|
1144
|
+
}
|
|
1145
|
+
var cssString = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, " ")}"`;
|
|
1146
|
+
function marginContent(template, s) {
|
|
1147
|
+
if (!template) return "none";
|
|
1148
|
+
return templateParts(template, s).map((p) => "field" in p ? `counter(${p.field})` : cssString(p.text)).join(" ");
|
|
1149
|
+
}
|
|
1150
|
+
function runningBoxes(t2, where, s) {
|
|
1151
|
+
if (!t2) return "";
|
|
1152
|
+
const box = (slot) => t2[slot] ? `@${where}-${slot}{content:${marginContent(t2[slot], s)};font:9pt/1.2 var(--doc-font);color:var(--matrx-print-muted,#6b7280);}` : "";
|
|
1153
|
+
return box("left") + box("center") + box("right");
|
|
1154
|
+
}
|
|
1155
|
+
var pt = (n) => `${Math.round(n * 100) / 100}pt`;
|
|
1156
|
+
function documentCss(s) {
|
|
1157
|
+
const { width, height, margins: m } = s.page;
|
|
1158
|
+
const portrait = `${pt(width)} ${pt(height)}`;
|
|
1159
|
+
const landscape = `${pt(height)} ${pt(width)}`;
|
|
1160
|
+
const base = s.page.orientation === "landscape" ? landscape : portrait;
|
|
1161
|
+
const blankBoxes = ["top", "bottom"].flatMap((w) => ["left", "center", "right"].map((sl) => `@${w}-${sl}{content:none;}`)).join("");
|
|
1162
|
+
return `
|
|
1163
|
+
:root{color-scheme:light;--doc-font:"Charter","Georgia","Cambria","Times New Roman",serif;--doc-sans:"Inter","Helvetica Neue","Arial",sans-serif;--doc-mono:"SFMono-Regular","Menlo","Consolas","Liberation Mono",monospace;}
|
|
1164
|
+
@page{size:${base};margin:${pt(m.top)} ${pt(m.right)} ${pt(m.bottom)} ${pt(m.left)};${runningBoxes(s.header, "top", s)}${runningBoxes(s.footer, "bottom", s)}}
|
|
1165
|
+
@page doc-portrait{size:${portrait};}
|
|
1166
|
+
@page doc-landscape{size:${landscape};}
|
|
1167
|
+
${s.differentFirstPage ? `@page:first{${blankBoxes}}` : ""}
|
|
1168
|
+
@page doc-cover{${blankBoxes}}
|
|
1169
|
+
html{background:var(--matrx-print-page,#ffffff);}
|
|
1170
|
+
body{margin:0;background:var(--matrx-print-page,#ffffff);color:var(--matrx-print-text,#1f2937);font:${pt(s.fontSize)}/1.5 var(--doc-font);-webkit-print-color-adjust:exact;print-color-adjust:exact;}
|
|
1171
|
+
.doc{max-width:${pt(width - m.left - m.right)};margin:0 auto;padding:48px 24px;}
|
|
1172
|
+
@media print{.doc{max-width:none;padding:0;}}
|
|
1173
|
+
.doc-section.doc-landscape{page:doc-landscape;}
|
|
1174
|
+
.doc-section.doc-portrait{page:doc-portrait;}
|
|
1175
|
+
@media screen{.doc-section.doc-landscape{max-width:none;}}
|
|
1176
|
+
.doc-section+.doc-section{break-before:page;}
|
|
1177
|
+
@media screen{.doc-section+.doc-section{border-top:1px dashed var(--matrx-print-rule,#d1d5db);margin-top:32px;padding-top:32px;}}
|
|
1178
|
+
.doc-cols-2{column-count:2;column-gap:0.3in;}
|
|
1179
|
+
.doc-cols-3{column-count:3;column-gap:0.25in;}
|
|
1180
|
+
.doc-cover{page:doc-cover;break-after:page;min-height:${pt(height - m.top - m.bottom)};display:flex;flex-direction:column;justify-content:center;align-items:flex-start;gap:10pt;}
|
|
1181
|
+
@media screen{.doc-cover{min-height:60vh;border-bottom:1px dashed var(--matrx-print-rule,#d1d5db);margin-bottom:32px;}}
|
|
1182
|
+
.doc-cover-logo{max-height:72pt;max-width:40%;margin-bottom:24pt;}
|
|
1183
|
+
.doc-cover-title{font:700 30pt/1.15 var(--doc-sans);margin:0;letter-spacing:-0.01em;}
|
|
1184
|
+
.doc-cover-subtitle{font:400 16pt/1.3 var(--doc-sans);color:var(--matrx-print-muted,#4b5563);margin:0;}
|
|
1185
|
+
.doc-cover-author,.doc-cover-date{font:12pt/1.4 var(--doc-sans);color:var(--matrx-print-muted,#4b5563);margin:0;}
|
|
1186
|
+
.doc-cover-author{margin-top:36pt;}
|
|
1187
|
+
p,li,dd{orphans:3;widows:3;}
|
|
1188
|
+
p{margin:0 0 0.7em;${s.hyphenate ? "hyphens:auto;-webkit-hyphens:auto;" : ""}}
|
|
1189
|
+
h1,h2,h3,h4,h5,h6{font-family:var(--doc-sans);color:var(--matrx-print-heading,#111827);line-height:1.25;break-after:avoid;page-break-after:avoid;break-inside:avoid;hyphens:manual;}
|
|
1190
|
+
h1{font-size:${pt(s.fontSize * 1.8)};margin:1.2em 0 0.5em;}
|
|
1191
|
+
h2{font-size:${pt(s.fontSize * 1.45)};margin:1.1em 0 0.45em;}
|
|
1192
|
+
h3{font-size:${pt(s.fontSize * 1.2)};margin:1em 0 0.4em;}
|
|
1193
|
+
h4,h5,h6{font-size:${pt(s.fontSize * 1.05)};margin:0.9em 0 0.35em;}
|
|
1194
|
+
.doc-heading-number{color:var(--matrx-print-muted,#6b7280);margin-right:0.35em;}
|
|
1195
|
+
a{color:var(--matrx-print-link,#1d4ed8);text-decoration:none;}
|
|
1196
|
+
code{font:0.88em var(--doc-mono);background:var(--matrx-print-code-bg,#f3f4f6);padding:0.05em 0.3em;border-radius:3px;}
|
|
1197
|
+
pre.doc-code{font:0.82em/1.45 var(--doc-mono);background:var(--matrx-print-code-bg,#f6f8fa);border:1px solid var(--matrx-print-rule,#e5e7eb);border-radius:4px;padding:8pt 10pt;white-space:pre-wrap;overflow-wrap:anywhere;break-inside:avoid;page-break-inside:avoid;}
|
|
1198
|
+
pre.doc-code code{background:none;padding:0;font:inherit;}
|
|
1199
|
+
blockquote{margin:0 0 0.8em;padding:0.1em 0 0.1em 1em;border-left:3px solid var(--matrx-print-rule,#d1d5db);color:var(--matrx-print-muted,#4b5563);break-inside:avoid;}
|
|
1200
|
+
hr{border:0;border-top:1px solid var(--matrx-print-rule,#d1d5db);margin:1.2em 0;}
|
|
1201
|
+
.doc-table-wrap{margin:0 0 1em;break-inside:avoid;page-break-inside:avoid;}
|
|
1202
|
+
table{border-collapse:collapse;width:100%;font-size:0.92em;}
|
|
1203
|
+
thead{display:table-header-group;}
|
|
1204
|
+
tr{break-inside:avoid;page-break-inside:avoid;}
|
|
1205
|
+
th,td{border-bottom:1px solid var(--matrx-print-rule,#e5e7eb);padding:4pt 6pt;text-align:left;vertical-align:top;}
|
|
1206
|
+
th{font-family:var(--doc-sans);font-weight:600;border-bottom:1.5px solid var(--matrx-print-heading,#111827);}
|
|
1207
|
+
table caption{caption-side:top;text-align:left;margin-bottom:4pt;}
|
|
1208
|
+
.doc-caption{font:0.9em/1.4 var(--doc-sans);color:var(--matrx-print-muted,#4b5563);break-after:avoid;}
|
|
1209
|
+
.doc-caption-label{font-weight:600;color:var(--matrx-print-heading,#111827);}
|
|
1210
|
+
.doc-figure{margin:0 0 1em;text-align:center;break-inside:avoid;page-break-inside:avoid;}
|
|
1211
|
+
.doc-figure img{max-width:100%;max-height:${pt((height - m.top - m.bottom) * 0.7)};}
|
|
1212
|
+
.doc-figure figcaption{margin-top:4pt;}
|
|
1213
|
+
.doc-image-missing{border:1px dashed var(--matrx-print-rule,#d1d5db);padding:16pt;color:var(--matrx-print-muted,#6b7280);}
|
|
1214
|
+
.doc-inline-img{max-height:1.2em;vertical-align:middle;}
|
|
1215
|
+
.doc-task-list{list-style:none;padding-left:0.2em;}
|
|
1216
|
+
.doc-task-box{display:inline-block;width:0.8em;height:0.8em;border:1.2px solid var(--matrx-print-heading,#374151);border-radius:2px;margin-right:0.45em;vertical-align:-0.05em;}
|
|
1217
|
+
.doc-task-box.is-checked{background:var(--matrx-print-heading,#374151);}
|
|
1218
|
+
.doc-xref-missing,.doc-cite-missing{color:var(--matrx-print-danger,#b91c1c);font-weight:600;}
|
|
1219
|
+
.doc-toc{break-after:auto;}
|
|
1220
|
+
.doc-toc-title{font-size:${pt(s.fontSize * 1.45)};}
|
|
1221
|
+
.doc-toc ol{list-style:none;padding:0;margin:0;}
|
|
1222
|
+
.doc-toc li{margin:0.2em 0;}
|
|
1223
|
+
.doc-toc a{display:flex;gap:0.5em;color:inherit;}
|
|
1224
|
+
.doc-toc .toc-text{flex:1;}
|
|
1225
|
+
.doc-toc a::after{content:target-counter(attr(href url),page);}
|
|
1226
|
+
${[2, 3, 4, 5, 6].map((l) => `.doc-toc .toc-l${l}{padding-left:${(l - 1) * 1.2}em;}`).join("")}
|
|
1227
|
+
.doc-toc .toc-l1{font-weight:600;}
|
|
1228
|
+
.doc-bibliography .doc-ref{padding-left:2em;text-indent:-2em;}
|
|
1229
|
+
.doc-notes{font-size:0.9em;}
|
|
1230
|
+
.doc-note-back{margin-left:0.3em;}
|
|
1231
|
+
.${PAGE_BREAK_CLASS}{break-after:page;page-break-after:always;height:0;}
|
|
1232
|
+
@media screen{.${PAGE_BREAK_CLASS}{height:auto;border-top:1px dashed var(--matrx-print-rule,#d1d5db);margin:24px 0;}}
|
|
1233
|
+
`.trim();
|
|
1234
|
+
}
|
|
1235
|
+
function documentToHtml(tree, options = {}) {
|
|
1236
|
+
const s = tree.settings;
|
|
1237
|
+
const x = { tree, images: options.images };
|
|
1238
|
+
const sections = tree.sections.map((sec) => {
|
|
1239
|
+
const cls = [
|
|
1240
|
+
"doc-section",
|
|
1241
|
+
sec.orientation === s.page.orientation ? "" : `doc-${sec.orientation}`,
|
|
1242
|
+
sec.columns > 1 ? `doc-cols-${sec.columns}` : ""
|
|
1243
|
+
].filter(Boolean).join(" ");
|
|
1244
|
+
return `<section class="${cls}">${renderBlocksHtml(sec.blocks, x)}</section>`;
|
|
1245
|
+
}).join("\n");
|
|
1246
|
+
const size = PAGE_SIZES[s.page.size].label;
|
|
1247
|
+
return `<!DOCTYPE html>
|
|
1248
|
+
<html lang="${(0, import_html_escape.escapeHtml)(s.lang)}">
|
|
1249
|
+
<head>
|
|
1250
|
+
<meta charset="utf-8">
|
|
1251
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1252
|
+
<meta name="color-scheme" content="light">
|
|
1253
|
+
<meta name="generator" content="@ai-matrx/print/document (${size}, ${s.page.orientation})">
|
|
1254
|
+
<title>${(0, import_html_escape.escapeHtml)(s.title ?? "Document")}</title>
|
|
1255
|
+
${s.author ? `<meta name="author" content="${(0, import_html_escape.escapeHtml)(s.author)}">` : ""}
|
|
1256
|
+
<style>${documentCss(s)}</style>
|
|
1257
|
+
</head>
|
|
1258
|
+
<body>
|
|
1259
|
+
<main class="doc">
|
|
1260
|
+
${s.cover ? coverHtml(s, x) : ""}
|
|
1261
|
+
${sections}
|
|
1262
|
+
</main>
|
|
1263
|
+
</body>
|
|
1264
|
+
</html>`;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// src/document/epub.ts
|
|
1268
|
+
var EPUB_CSS = `
|
|
1269
|
+
body{font-family:Georgia,"Times New Roman",serif;line-height:1.5;color:#1f2937;margin:0 5%;}
|
|
1270
|
+
h1,h2,h3,h4,h5,h6{font-family:"Helvetica Neue",Arial,sans-serif;line-height:1.25;page-break-after:avoid;break-after:avoid;color:#111827;}
|
|
1271
|
+
p{margin:0 0 0.7em;orphans:2;widows:2;hyphens:auto;-epub-hyphens:auto;}
|
|
1272
|
+
pre.doc-code{font-family:Menlo,Consolas,monospace;font-size:0.8em;white-space:pre-wrap;background:#f6f8fa;padding:0.6em;border:1px solid #e5e7eb;page-break-inside:avoid;}
|
|
1273
|
+
code{font-family:Menlo,Consolas,monospace;font-size:0.9em;}
|
|
1274
|
+
table{border-collapse:collapse;width:100%;font-size:0.9em;}
|
|
1275
|
+
th,td{border-bottom:1px solid #e5e7eb;padding:0.25em 0.4em;text-align:left;vertical-align:top;}
|
|
1276
|
+
th{border-bottom:1.5px solid #111827;}
|
|
1277
|
+
caption,figcaption,.doc-caption{font-size:0.9em;color:#4b5563;text-align:left;}
|
|
1278
|
+
.doc-caption-label{font-weight:bold;color:#111827;}
|
|
1279
|
+
figure{margin:1em 0;text-align:center;page-break-inside:avoid;}
|
|
1280
|
+
figure img{max-width:100%;}
|
|
1281
|
+
blockquote{margin:0 0 0.8em;padding-left:1em;border-left:3px solid #d1d5db;color:#4b5563;}
|
|
1282
|
+
.doc-task-list{list-style:none;padding-left:0.2em;}
|
|
1283
|
+
.doc-task-box{display:inline-block;width:0.8em;height:0.8em;border:1px solid #374151;margin-right:0.4em;}
|
|
1284
|
+
.doc-task-box.is-checked{background:#374151;}
|
|
1285
|
+
.doc-toc ol{list-style:none;padding-left:0;}
|
|
1286
|
+
.doc-bibliography .doc-ref{padding-left:2em;text-indent:-2em;}
|
|
1287
|
+
.doc-cover{text-align:center;margin-top:30%;}
|
|
1288
|
+
.doc-cover-logo{max-height:5em;}
|
|
1289
|
+
.doc-cover-title{font-size:2em;}
|
|
1290
|
+
.matrx-page-break{page-break-after:always;break-after:page;}
|
|
1291
|
+
`.trim();
|
|
1292
|
+
function extOf(mime) {
|
|
1293
|
+
return mime === "image/png" ? "png" : mime === "image/gif" ? "gif" : "jpg";
|
|
1294
|
+
}
|
|
1295
|
+
function xhtml(title, lang, body) {
|
|
1296
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
1297
|
+
<!DOCTYPE html>
|
|
1298
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" lang="${(0, import_html_escape2.escapeHtml)(lang)}" xml:lang="${(0, import_html_escape2.escapeHtml)(lang)}">
|
|
1299
|
+
<head><meta charset="utf-8"/><title>${(0, import_html_escape2.escapeHtml)(title)}</title><link rel="stylesheet" type="text/css" href="style.css"/></head>
|
|
1300
|
+
<body>
|
|
1301
|
+
${body}
|
|
1302
|
+
</body>
|
|
1303
|
+
</html>`;
|
|
1304
|
+
}
|
|
1305
|
+
function uuid() {
|
|
1306
|
+
const g = globalThis;
|
|
1307
|
+
if (g.crypto?.randomUUID) return g.crypto.randomUUID();
|
|
1308
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
|
|
1309
|
+
const r = Math.random() * 16 | 0;
|
|
1310
|
+
return (ch === "x" ? r : r & 3 | 8).toString(16);
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
async function documentToEpub(tree, options = {}) {
|
|
1314
|
+
const JSZipMod = await import("jszip");
|
|
1315
|
+
const JSZip = JSZipMod.default ?? JSZipMod;
|
|
1316
|
+
const s = tree.settings;
|
|
1317
|
+
const lang = s.lang || "en";
|
|
1318
|
+
const levels = [];
|
|
1319
|
+
for (const sec of tree.sections) for (const b of sec.blocks) if (b.type === "heading") levels.push(b.level);
|
|
1320
|
+
const top = Math.min(7, ...levels);
|
|
1321
|
+
const chapters = [];
|
|
1322
|
+
let current = null;
|
|
1323
|
+
for (const sec of tree.sections) {
|
|
1324
|
+
for (const b of sec.blocks) {
|
|
1325
|
+
if (b.type === "pageBreak") continue;
|
|
1326
|
+
if (b.type === "heading" && b.level === top && current && current.blocks.length) current = null;
|
|
1327
|
+
if (!current) {
|
|
1328
|
+
current = { file: `chapter-${chapters.length + 1}.xhtml`, title: b.type === "heading" ? b.plain : s.title ?? "Start", blocks: [] };
|
|
1329
|
+
chapters.push(current);
|
|
1330
|
+
}
|
|
1331
|
+
current.blocks.push(b);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
if (chapters.length === 0) chapters.push({ file: "chapter-1.xhtml", title: s.title ?? "Document", blocks: [] });
|
|
1335
|
+
const home = /* @__PURE__ */ new Map();
|
|
1336
|
+
const walk = (file, list) => {
|
|
1337
|
+
for (const b of list) {
|
|
1338
|
+
if (b.type === "heading") home.set(b.id, file);
|
|
1339
|
+
if (b.type === "figure" && b.caption?.id) home.set(`fig:${b.caption.id}`, file);
|
|
1340
|
+
if (b.type === "table" && b.caption?.id) home.set(`tbl:${b.caption.id}`, file);
|
|
1341
|
+
if (b.type === "bibliography") for (const e of tree.bibliography) home.set(`ref-${e.id}`, file);
|
|
1342
|
+
if (b.type === "notes") for (const n2 of tree.notes) home.set(`note-${n2.n}`, file);
|
|
1343
|
+
if (b.type === "list") for (const it of b.items) walk(file, it.children);
|
|
1344
|
+
if (b.type === "blockquote") walk(file, b.children);
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
for (const ch of chapters) walk(ch.file, ch.blocks);
|
|
1348
|
+
const imagePaths = /* @__PURE__ */ new Map();
|
|
1349
|
+
let imgN = 0;
|
|
1350
|
+
for (const [url, img] of options.images ?? /* @__PURE__ */ new Map()) {
|
|
1351
|
+
if (!img) continue;
|
|
1352
|
+
imgN++;
|
|
1353
|
+
imagePaths.set(url, { path: `images/img-${imgN}.${extOf(img.mime)}`, mime: img.mime, bytes: img.bytes });
|
|
1354
|
+
}
|
|
1355
|
+
const zip = new JSZip();
|
|
1356
|
+
zip.file("mimetype", "application/epub+zip", { compression: "STORE" });
|
|
1357
|
+
zip.file(
|
|
1358
|
+
"META-INF/container.xml",
|
|
1359
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
1360
|
+
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
|
|
1361
|
+
<rootfiles><rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/></rootfiles>
|
|
1362
|
+
</container>`
|
|
1363
|
+
);
|
|
1364
|
+
zip.file("OEBPS/style.css", EPUB_CSS);
|
|
1365
|
+
for (const img of imagePaths.values()) zip.file(`OEBPS/${img.path}`, img.bytes);
|
|
1366
|
+
const makeCtx = (file) => ({
|
|
1367
|
+
tree,
|
|
1368
|
+
xhtml: true,
|
|
1369
|
+
hrefFor: (id2) => {
|
|
1370
|
+
const target = home.get(id2) ?? file;
|
|
1371
|
+
return target === file ? `#${id2}` : `${target}#${id2}`;
|
|
1372
|
+
},
|
|
1373
|
+
srcFor: (url) => imagePaths.get(url)?.path ?? url
|
|
1374
|
+
});
|
|
1375
|
+
const spine = [];
|
|
1376
|
+
const manifest = [
|
|
1377
|
+
`<item id="nav" href="nav.xhtml" media-type="application/xhtml+xml" properties="nav"/>`,
|
|
1378
|
+
`<item id="css" href="style.css" media-type="text/css"/>`
|
|
1379
|
+
];
|
|
1380
|
+
if (s.cover) {
|
|
1381
|
+
zip.file("OEBPS/cover.xhtml", xhtml(s.title ?? "Cover", lang, coverHtml(s, makeCtx("cover.xhtml"))));
|
|
1382
|
+
manifest.push(`<item id="cover" href="cover.xhtml" media-type="application/xhtml+xml"/>`);
|
|
1383
|
+
spine.push(`<itemref idref="cover"/>`);
|
|
1384
|
+
}
|
|
1385
|
+
chapters.forEach((ch, i) => {
|
|
1386
|
+
zip.file(`OEBPS/${ch.file}`, xhtml(ch.title, lang, renderBlocksHtml(ch.blocks, makeCtx(ch.file))));
|
|
1387
|
+
manifest.push(`<item id="ch${i + 1}" href="${ch.file}" media-type="application/xhtml+xml"/>`);
|
|
1388
|
+
spine.push(`<itemref idref="ch${i + 1}"/>`);
|
|
1389
|
+
});
|
|
1390
|
+
let n = 0;
|
|
1391
|
+
for (const img of imagePaths.values()) {
|
|
1392
|
+
n++;
|
|
1393
|
+
manifest.push(
|
|
1394
|
+
`<item id="img${n}" href="${img.path}" media-type="${img.mime}"${s.logo && imagePaths.get(s.logo)?.path === img.path ? ' properties="cover-image"' : ""}/>`
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
const entries = tree.toc.length ? tree.toc : chapters.map((ch) => ({ id: "", level: 1, text: ch.title, file: ch.file }));
|
|
1398
|
+
const navItems = entries.map((e) => {
|
|
1399
|
+
const file = "file" in e ? e.file : home.get(e.id) ?? chapters[0].file;
|
|
1400
|
+
const hrefAttr = e.id ? `${file}#${e.id}` : file;
|
|
1401
|
+
return `<li class="toc-l${e.level}"><a href="${(0, import_html_escape2.escapeHtml)(hrefAttr)}">${(0, import_html_escape2.escapeHtml)(e.text)}</a></li>`;
|
|
1402
|
+
}).join("\n");
|
|
1403
|
+
zip.file(
|
|
1404
|
+
"OEBPS/nav.xhtml",
|
|
1405
|
+
xhtml(
|
|
1406
|
+
s.toc.title,
|
|
1407
|
+
lang,
|
|
1408
|
+
`<nav epub:type="toc" id="toc"><h1>${(0, import_html_escape2.escapeHtml)(s.toc.title)}</h1><ol>
|
|
1409
|
+
${navItems}
|
|
1410
|
+
</ol></nav>`
|
|
1411
|
+
)
|
|
1412
|
+
);
|
|
1413
|
+
const modified = (options.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
1414
|
+
const id = options.identifier ?? `urn:uuid:${uuid()}`;
|
|
1415
|
+
zip.file(
|
|
1416
|
+
"OEBPS/content.opf",
|
|
1417
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
1418
|
+
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="bookid" xml:lang="${(0, import_html_escape2.escapeHtml)(lang)}">
|
|
1419
|
+
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
1420
|
+
<dc:identifier id="bookid">${(0, import_html_escape2.escapeHtml)(id)}</dc:identifier>
|
|
1421
|
+
<dc:title>${(0, import_html_escape2.escapeHtml)(s.title ?? "Document")}</dc:title>
|
|
1422
|
+
<dc:language>${(0, import_html_escape2.escapeHtml)(lang)}</dc:language>
|
|
1423
|
+
${s.author ? `<dc:creator>${(0, import_html_escape2.escapeHtml)(s.author)}</dc:creator>` : ""}
|
|
1424
|
+
${s.date ? `<dc:date>${(0, import_html_escape2.escapeHtml)(s.date)}</dc:date>` : ""}
|
|
1425
|
+
<meta property="dcterms:modified">${modified}</meta>
|
|
1426
|
+
</metadata>
|
|
1427
|
+
<manifest>
|
|
1428
|
+
${manifest.join("\n")}
|
|
1429
|
+
</manifest>
|
|
1430
|
+
<spine>
|
|
1431
|
+
${spine.join("\n")}
|
|
1432
|
+
</spine>
|
|
1433
|
+
</package>`
|
|
1434
|
+
);
|
|
1435
|
+
return zip.generateAsync({ type: "uint8array", compression: "DEFLATE", mimeType: "application/epub+zip" });
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// src/document/pdf.ts
|
|
1439
|
+
var COLOR = {
|
|
1440
|
+
text: "#1f2937",
|
|
1441
|
+
heading: "#111827",
|
|
1442
|
+
muted: "#6b7280",
|
|
1443
|
+
link: "#1d4ed8",
|
|
1444
|
+
rule: "#d1d5db",
|
|
1445
|
+
codeBg: "#f6f8fa",
|
|
1446
|
+
danger: "#b91c1c"
|
|
1447
|
+
};
|
|
1448
|
+
var WIN_ANSI_EXTRA = new Set(
|
|
1449
|
+
"\u20AC\u201A\u0192\u201E\u2026\u2020\u2021\u02C6\u2030\u0160\u2039\u0152\u017D\u2018\u2019\u201C\u201D\u2022\u2013\u2014\u02DC\u2122\u0161\u203A\u0153\u017E\u0178"
|
|
1450
|
+
);
|
|
1451
|
+
var courierCanDraw = (s) => {
|
|
1452
|
+
for (const ch of s) {
|
|
1453
|
+
const c = ch.codePointAt(0);
|
|
1454
|
+
if (c >= 32 && c <= 126 || c >= 160 && c <= 255 || c === 9 || WIN_ANSI_EXTRA.has(ch)) continue;
|
|
1455
|
+
return false;
|
|
1456
|
+
}
|
|
1457
|
+
return true;
|
|
1458
|
+
};
|
|
1459
|
+
var NO_GLYPH = /[\p{Extended_Pictographic}\u{1F1E6}-\u{1F1FF}\u200D\uFE0F\u20E3]/gu;
|
|
1460
|
+
function drawable(text, c) {
|
|
1461
|
+
if (!NO_GLYPH.test(text)) return text;
|
|
1462
|
+
NO_GLYPH.lastIndex = 0;
|
|
1463
|
+
if (!c.glyphs.announced) {
|
|
1464
|
+
c.glyphs.announced = true;
|
|
1465
|
+
c.tree.notices.push({
|
|
1466
|
+
code: "glyph-substituted",
|
|
1467
|
+
message: "Emoji and pictographs were left out of the PDF: its fonts cannot draw them (Word, EPUB and HTML keep them).",
|
|
1468
|
+
remedy: "Use words or plain symbols instead of emoji if they must appear in the PDF."
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
return text.replace(NO_GLYPH, "").replace(/ {2,}/g, " ");
|
|
1472
|
+
}
|
|
1473
|
+
function runs2(nodes, c, st = {}) {
|
|
1474
|
+
const out = [];
|
|
1475
|
+
const base = (raw) => {
|
|
1476
|
+
const text = drawable(raw, c);
|
|
1477
|
+
const r = { text };
|
|
1478
|
+
if (st.bold) r.bold = true;
|
|
1479
|
+
if (st.italics) r.italics = true;
|
|
1480
|
+
if (st.strike) r.decoration = "lineThrough";
|
|
1481
|
+
if (st.link) {
|
|
1482
|
+
r.link = st.link;
|
|
1483
|
+
r.color = COLOR.link;
|
|
1484
|
+
}
|
|
1485
|
+
if (st.dest) {
|
|
1486
|
+
r.linkToDestination = st.dest;
|
|
1487
|
+
r.color = COLOR.link;
|
|
1488
|
+
}
|
|
1489
|
+
return r;
|
|
1490
|
+
};
|
|
1491
|
+
for (const n of nodes) {
|
|
1492
|
+
switch (n.type) {
|
|
1493
|
+
case "text":
|
|
1494
|
+
out.push(base(n.value));
|
|
1495
|
+
break;
|
|
1496
|
+
case "strong":
|
|
1497
|
+
out.push(...runs2(n.children, c, { ...st, bold: true }));
|
|
1498
|
+
break;
|
|
1499
|
+
case "emphasis":
|
|
1500
|
+
out.push(...runs2(n.children, c, { ...st, italics: true }));
|
|
1501
|
+
break;
|
|
1502
|
+
case "delete":
|
|
1503
|
+
out.push(...runs2(n.children, c, { ...st, strike: true }));
|
|
1504
|
+
break;
|
|
1505
|
+
case "code": {
|
|
1506
|
+
const r = base(n.value);
|
|
1507
|
+
if (courierCanDraw(n.value)) r.font = "Courier";
|
|
1508
|
+
r.fontSize = c.s.fontSize * 0.92;
|
|
1509
|
+
r.background = "#f3f4f6";
|
|
1510
|
+
out.push(r);
|
|
1511
|
+
break;
|
|
1512
|
+
}
|
|
1513
|
+
case "break":
|
|
1514
|
+
out.push(base("\n"));
|
|
1515
|
+
break;
|
|
1516
|
+
case "link":
|
|
1517
|
+
out.push(...runs2(n.children, c, /^(https?:|mailto:|tel:)/i.test(n.url) ? { ...st, link: n.url } : st));
|
|
1518
|
+
break;
|
|
1519
|
+
case "image":
|
|
1520
|
+
out.push(base(n.alt));
|
|
1521
|
+
break;
|
|
1522
|
+
case "xref": {
|
|
1523
|
+
if (!n.resolved) {
|
|
1524
|
+
out.push({ ...base(n.label), bold: true, color: COLOR.danger });
|
|
1525
|
+
break;
|
|
1526
|
+
}
|
|
1527
|
+
const id = n.target.startsWith("sec:") ? n.target.slice(4) : n.target;
|
|
1528
|
+
out.push({ ...base(n.label), linkToDestination: id, color: COLOR.link });
|
|
1529
|
+
break;
|
|
1530
|
+
}
|
|
1531
|
+
case "cite": {
|
|
1532
|
+
const r = base(n.label);
|
|
1533
|
+
if (n.resolved) {
|
|
1534
|
+
r.linkToDestination = `ref-${n.keys[0]}`;
|
|
1535
|
+
} else {
|
|
1536
|
+
r.bold = true;
|
|
1537
|
+
r.color = COLOR.danger;
|
|
1538
|
+
}
|
|
1539
|
+
out.push(r);
|
|
1540
|
+
break;
|
|
1541
|
+
}
|
|
1542
|
+
case "noteRef":
|
|
1543
|
+
out.push({ text: String(n.n), sup: true, linkToDestination: `note-${n.n}`, color: COLOR.link });
|
|
1544
|
+
break;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
return out;
|
|
1548
|
+
}
|
|
1549
|
+
var capNode = (cap, c, where) => ({
|
|
1550
|
+
text: [{ text: cap.label, bold: true, color: COLOR.heading }, ...cap.children.length ? [": ", ...runs2(cap.children, c)] : []],
|
|
1551
|
+
style: "caption",
|
|
1552
|
+
id: cap.id ? `${cap.kind}:${cap.id}` : void 0,
|
|
1553
|
+
// A caption above its table is never the last line on a page.
|
|
1554
|
+
...where === "above" ? { headlineLevel: 9 } : {},
|
|
1555
|
+
margin: where === "above" ? [0, 0, 0, 4] : [0, 4, 0, 0]
|
|
1556
|
+
});
|
|
1557
|
+
function estimateLines(text, c) {
|
|
1558
|
+
const charsPerLine = Math.max(20, c.width / (c.s.fontSize * 0.5));
|
|
1559
|
+
return Math.ceil(Math.max(1, text.length) / charsPerLine);
|
|
1560
|
+
}
|
|
1561
|
+
var plain = (nodes) => nodes.map((n) => "value" in n ? n.value : "label" in n ? n.label : "children" in n ? plain(n.children) : "").join("");
|
|
1562
|
+
function taskItem(checked, content, c) {
|
|
1563
|
+
const size = c.s.fontSize * 0.75;
|
|
1564
|
+
return {
|
|
1565
|
+
columns: [
|
|
1566
|
+
{
|
|
1567
|
+
width: size + 6,
|
|
1568
|
+
canvas: [
|
|
1569
|
+
{ type: "rect", x: 0, y: c.s.fontSize * 0.3, w: size, h: size, r: 1.5, lineWidth: 1, lineColor: COLOR.heading, ...checked ? { color: COLOR.heading } : {} }
|
|
1570
|
+
]
|
|
1571
|
+
},
|
|
1572
|
+
{ width: "*", stack: content }
|
|
1573
|
+
],
|
|
1574
|
+
columnGap: 2
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
function gluable(b, c) {
|
|
1578
|
+
if (!b) return false;
|
|
1579
|
+
if (b.type === "paragraph") return estimateLines(plain(b.children), c) <= 5;
|
|
1580
|
+
if (b.type === "figure" || b.type === "rule") return true;
|
|
1581
|
+
if (b.type === "list")
|
|
1582
|
+
return b.items.length <= 4 && b.items.every((i) => i.children.length === 1 && i.children[0]?.type === "paragraph");
|
|
1583
|
+
return false;
|
|
1584
|
+
}
|
|
1585
|
+
function blocks2(list, c) {
|
|
1586
|
+
const out = [];
|
|
1587
|
+
let breakNext = false;
|
|
1588
|
+
for (let i = 0; i < list.length; i++) {
|
|
1589
|
+
const b = list[i];
|
|
1590
|
+
let node = block2(b, c);
|
|
1591
|
+
if (node && b.type === "heading" && gluable(list[i + 1], c)) {
|
|
1592
|
+
const next = block2(list[i + 1], c);
|
|
1593
|
+
if (next) {
|
|
1594
|
+
node = { stack: [node, next], unbreakable: true };
|
|
1595
|
+
i++;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
if (node === null) {
|
|
1599
|
+
if (b.type === "pageBreak") breakNext = true;
|
|
1600
|
+
continue;
|
|
1601
|
+
}
|
|
1602
|
+
if (breakNext && typeof node === "object") {
|
|
1603
|
+
node.pageBreak = "before";
|
|
1604
|
+
breakNext = false;
|
|
1605
|
+
}
|
|
1606
|
+
out.push(node);
|
|
1607
|
+
}
|
|
1608
|
+
return out;
|
|
1609
|
+
}
|
|
1610
|
+
function block2(b, c) {
|
|
1611
|
+
switch (b.type) {
|
|
1612
|
+
case "heading": {
|
|
1613
|
+
const rel = Math.min(6, b.level - c.minLevel + 1);
|
|
1614
|
+
const node = {
|
|
1615
|
+
text: [...b.number ? [{ text: `${b.number} `, color: COLOR.muted }] : [], ...runs2(b.children, c)],
|
|
1616
|
+
style: `h${Math.min(6, b.level)}`,
|
|
1617
|
+
headlineLevel: b.level,
|
|
1618
|
+
id: b.id
|
|
1619
|
+
};
|
|
1620
|
+
if (c.tocIds.has(b.id)) {
|
|
1621
|
+
node.tocItem = true;
|
|
1622
|
+
node.tocMargin = [(rel - 1) * 14, rel === 1 ? 4 : 1, 0, 0];
|
|
1623
|
+
node.tocStyle = rel === 1 ? "tocL1" : "toc";
|
|
1624
|
+
}
|
|
1625
|
+
return node;
|
|
1626
|
+
}
|
|
1627
|
+
case "paragraph": {
|
|
1628
|
+
const text = runs2(b.children, c);
|
|
1629
|
+
const p = { text, style: "p" };
|
|
1630
|
+
return estimateLines(plain(b.children), c) <= 5 ? { stack: [p], unbreakable: true } : p;
|
|
1631
|
+
}
|
|
1632
|
+
case "list": {
|
|
1633
|
+
const items = b.items.map((it) => {
|
|
1634
|
+
const content = it.children.length === 1 && it.children[0]?.type === "paragraph" ? [{ text: runs2(it.children[0].children, c), margin: [0, 0, 0, 2] }] : blocks2(it.children, { ...c, width: c.width - 18 });
|
|
1635
|
+
if (it.checked !== null) return { ...taskItem(it.checked, content, c), listType: "none" };
|
|
1636
|
+
return content.length === 1 ? content[0] : { stack: content };
|
|
1637
|
+
});
|
|
1638
|
+
const node = b.ordered ? { ol: items, start: b.start } : { ul: items };
|
|
1639
|
+
node.margin = [0, 0, 0, 6];
|
|
1640
|
+
node.markerColor = COLOR.muted;
|
|
1641
|
+
return node;
|
|
1642
|
+
}
|
|
1643
|
+
case "blockquote":
|
|
1644
|
+
return {
|
|
1645
|
+
table: { widths: ["*"], body: [[{ stack: blocks2(b.children, { ...c, width: c.width - 16 }), color: COLOR.muted }]] },
|
|
1646
|
+
layout: {
|
|
1647
|
+
hLineWidth: () => 0,
|
|
1648
|
+
vLineWidth: (i) => i === 0 ? 3 : 0,
|
|
1649
|
+
vLineColor: () => COLOR.rule,
|
|
1650
|
+
paddingLeft: () => 10,
|
|
1651
|
+
paddingRight: () => 0,
|
|
1652
|
+
paddingTop: () => 2,
|
|
1653
|
+
paddingBottom: () => 2
|
|
1654
|
+
},
|
|
1655
|
+
margin: [0, 0, 0, 8]
|
|
1656
|
+
};
|
|
1657
|
+
case "code": {
|
|
1658
|
+
const lines = b.value.replace(/\n$/, "").split("\n");
|
|
1659
|
+
const size = c.s.fontSize * 0.8;
|
|
1660
|
+
const stack = lines.map((l) => {
|
|
1661
|
+
const t2 = drawable(l.replace(/\t/g, " "), c) || " ";
|
|
1662
|
+
return courierCanDraw(t2) ? { text: t2, font: "Courier", fontSize: size, preserveLeadingSpaces: true } : { text: t2, fontSize: size, preserveLeadingSpaces: true };
|
|
1663
|
+
});
|
|
1664
|
+
return {
|
|
1665
|
+
table: { widths: ["*"], body: [[{ stack, fillColor: COLOR.codeBg }]] },
|
|
1666
|
+
layout: {
|
|
1667
|
+
hLineWidth: () => 0.5,
|
|
1668
|
+
vLineWidth: () => 0.5,
|
|
1669
|
+
hLineColor: () => "#e5e7eb",
|
|
1670
|
+
vLineColor: () => "#e5e7eb",
|
|
1671
|
+
paddingLeft: () => 8,
|
|
1672
|
+
paddingRight: () => 8,
|
|
1673
|
+
paddingTop: () => 6,
|
|
1674
|
+
paddingBottom: () => 6
|
|
1675
|
+
},
|
|
1676
|
+
margin: [0, 0, 0, 8],
|
|
1677
|
+
// Keep a block that fits on one page together (30 lines fits even a
|
|
1678
|
+
// landscape page; a taller unbreakable block would be dropped).
|
|
1679
|
+
unbreakable: lines.length <= 30
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
case "table": {
|
|
1683
|
+
const n = Math.max(b.header.length, ...b.rows.map((r) => r.length), 1);
|
|
1684
|
+
const cell = (inl, i, head) => ({
|
|
1685
|
+
text: runs2(inl ?? [], c),
|
|
1686
|
+
bold: head,
|
|
1687
|
+
alignment: b.align[i] ?? "left",
|
|
1688
|
+
fontSize: c.s.fontSize * 0.92,
|
|
1689
|
+
color: head ? COLOR.heading : COLOR.text
|
|
1690
|
+
});
|
|
1691
|
+
const row = (r, head) => Array.from({ length: n }, (_, i) => cell(r[i], i, head));
|
|
1692
|
+
const table = {
|
|
1693
|
+
table: {
|
|
1694
|
+
headerRows: 1,
|
|
1695
|
+
dontBreakRows: true,
|
|
1696
|
+
keepWithHeaderRows: 1,
|
|
1697
|
+
widths: Array.from({ length: n }, () => "*"),
|
|
1698
|
+
body: [row(b.header, true), ...b.rows.map((r) => row(r, false))]
|
|
1699
|
+
},
|
|
1700
|
+
layout: {
|
|
1701
|
+
hLineWidth: (i) => i === 1 ? 1.2 : i === 0 ? 0 : 0.5,
|
|
1702
|
+
vLineWidth: () => 0,
|
|
1703
|
+
hLineColor: (i) => i === 1 ? COLOR.heading : "#e5e7eb",
|
|
1704
|
+
paddingLeft: () => 5,
|
|
1705
|
+
paddingRight: () => 5,
|
|
1706
|
+
paddingTop: () => 3,
|
|
1707
|
+
paddingBottom: () => 3
|
|
1708
|
+
}
|
|
1709
|
+
};
|
|
1710
|
+
const stack = [];
|
|
1711
|
+
if (b.caption) stack.push(capNode(b.caption, c, "above"));
|
|
1712
|
+
stack.push(table);
|
|
1713
|
+
return { stack, margin: [0, 2, 0, 10] };
|
|
1714
|
+
}
|
|
1715
|
+
case "figure": {
|
|
1716
|
+
const img = c.images?.get(b.url);
|
|
1717
|
+
const maxH = (c.s.page.height - c.s.page.margins.top - c.s.page.margins.bottom) * 0.6;
|
|
1718
|
+
const stack = [];
|
|
1719
|
+
if (img) {
|
|
1720
|
+
const scale = Math.min(1, c.width / img.width, maxH / img.height);
|
|
1721
|
+
stack.push({ image: img.dataUrl, width: img.width * scale, height: img.height * scale, alignment: "center" });
|
|
1722
|
+
} else {
|
|
1723
|
+
stack.push({ text: `[Image unavailable: ${b.alt || b.url}]`, italics: true, color: COLOR.muted, alignment: "center" });
|
|
1724
|
+
}
|
|
1725
|
+
if (b.caption) stack.push({ ...capNode(b.caption, c, "below"), alignment: "center" });
|
|
1726
|
+
return { stack, margin: [0, 4, 0, 10], unbreakable: true };
|
|
1727
|
+
}
|
|
1728
|
+
case "rule":
|
|
1729
|
+
return {
|
|
1730
|
+
canvas: [{ type: "line", x1: 0, y1: 0, x2: c.width, y2: 0, lineWidth: 0.75, lineColor: COLOR.rule }],
|
|
1731
|
+
margin: [0, 6, 0, 10]
|
|
1732
|
+
};
|
|
1733
|
+
case "pageBreak":
|
|
1734
|
+
return null;
|
|
1735
|
+
case "toc":
|
|
1736
|
+
return {
|
|
1737
|
+
toc: {
|
|
1738
|
+
title: { text: c.s.toc.title, style: "tocTitle" },
|
|
1739
|
+
numberStyle: { color: COLOR.muted }
|
|
1740
|
+
},
|
|
1741
|
+
margin: [0, 0, 0, 12]
|
|
1742
|
+
};
|
|
1743
|
+
case "notes":
|
|
1744
|
+
return {
|
|
1745
|
+
stack: c.tree.notes.map((n) => ({
|
|
1746
|
+
columns: [
|
|
1747
|
+
{ width: 18, text: `${n.n}.`, color: COLOR.muted, id: `note-${n.n}` },
|
|
1748
|
+
{ width: "*", stack: blocks2(n.children, { ...c, width: c.width - 18 }) }
|
|
1749
|
+
],
|
|
1750
|
+
fontSize: c.s.fontSize * 0.9
|
|
1751
|
+
}))
|
|
1752
|
+
};
|
|
1753
|
+
case "bibliography":
|
|
1754
|
+
return {
|
|
1755
|
+
stack: c.tree.bibliography.map((e) => ({
|
|
1756
|
+
text: runs2(e.children, c),
|
|
1757
|
+
id: `ref-${e.id}`,
|
|
1758
|
+
margin: [0, 0, 0, 6],
|
|
1759
|
+
leadingIndent: 0
|
|
1760
|
+
}))
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
function running(t2, s, page, pages, margins) {
|
|
1765
|
+
if (!t2) return null;
|
|
1766
|
+
const slot = (v, alignment) => ({ text: v ? fillTemplate(v, s, page, pages) : "", alignment, width: "*" });
|
|
1767
|
+
return {
|
|
1768
|
+
columns: [slot(t2.left, "left"), slot(t2.center, "center"), slot(t2.right, "right")],
|
|
1769
|
+
fontSize: 9,
|
|
1770
|
+
color: COLOR.muted,
|
|
1771
|
+
margin: [margins[0], 0, margins[1], 0]
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
function sectionContent(sec, c) {
|
|
1775
|
+
const landscape = sec.orientation === "landscape";
|
|
1776
|
+
const pageW = landscape ? c.s.page.height : c.s.page.width;
|
|
1777
|
+
const full = pageW - c.s.page.margins.left - c.s.page.margins.right;
|
|
1778
|
+
const gap = 18;
|
|
1779
|
+
const colW = (full - gap * (sec.columns - 1)) / sec.columns;
|
|
1780
|
+
const inner = blocks2(sec.blocks, { ...c, width: colW });
|
|
1781
|
+
if (sec.columns === 1) return inner;
|
|
1782
|
+
return [
|
|
1783
|
+
{
|
|
1784
|
+
columns: [
|
|
1785
|
+
{ width: colW, stack: inner },
|
|
1786
|
+
...Array.from({ length: sec.columns - 1 }, () => ({ width: colW, stack: [] }))
|
|
1787
|
+
],
|
|
1788
|
+
columnGap: gap,
|
|
1789
|
+
snakingColumns: true
|
|
1790
|
+
}
|
|
1791
|
+
];
|
|
1792
|
+
}
|
|
1793
|
+
function buildPdfDefinition(tree, images, pageCountSink) {
|
|
1794
|
+
const s = tree.settings;
|
|
1795
|
+
const m = s.page.margins;
|
|
1796
|
+
const headings = [];
|
|
1797
|
+
for (const sec of tree.sections) for (const b of sec.blocks) if (b.type === "heading") headings.push(b.level);
|
|
1798
|
+
const c = {
|
|
1799
|
+
glyphs: { announced: false },
|
|
1800
|
+
tree,
|
|
1801
|
+
images,
|
|
1802
|
+
s,
|
|
1803
|
+
width: s.page.width - m.left - m.right,
|
|
1804
|
+
tocIds: new Set(tree.toc.map((e) => e.id)),
|
|
1805
|
+
minLevel: Math.min(7, ...headings)
|
|
1806
|
+
};
|
|
1807
|
+
const content = [];
|
|
1808
|
+
if (s.cover) {
|
|
1809
|
+
const logo = s.logo ? images?.get(s.logo) : void 0;
|
|
1810
|
+
const pageH = (s.page.orientation === "landscape" ? s.page.width : s.page.height) - m.top - m.bottom;
|
|
1811
|
+
const cover = [];
|
|
1812
|
+
if (logo) {
|
|
1813
|
+
const scale = Math.min(1, 160 / logo.width, 72 / logo.height);
|
|
1814
|
+
cover.push({ image: logo.dataUrl, width: logo.width * scale, height: logo.height * scale, margin: [0, 0, 0, 24] });
|
|
1815
|
+
}
|
|
1816
|
+
cover.push({ text: s.title ?? "", style: "coverTitle" });
|
|
1817
|
+
if (s.subtitle) cover.push({ text: s.subtitle, style: "coverSubtitle" });
|
|
1818
|
+
if (s.author) cover.push({ text: s.author, style: "coverMeta", margin: [0, 36, 0, 0] });
|
|
1819
|
+
if (s.date) cover.push({ text: s.date, style: "coverMeta" });
|
|
1820
|
+
content.push({ stack: cover, margin: [0, pageH * 0.28, 0, 0] });
|
|
1821
|
+
}
|
|
1822
|
+
tree.sections.forEach((sec, idx) => {
|
|
1823
|
+
const nodes = sectionContent(sec, c);
|
|
1824
|
+
const first = nodes[0];
|
|
1825
|
+
if (first && typeof first === "object" && (idx > 0 || s.cover)) {
|
|
1826
|
+
first.pageBreak = "before";
|
|
1827
|
+
first.pageOrientation = sec.orientation;
|
|
1828
|
+
}
|
|
1829
|
+
content.push(...nodes);
|
|
1830
|
+
});
|
|
1831
|
+
const hs = s.fontSize;
|
|
1832
|
+
let pageCount = 0;
|
|
1833
|
+
const hidden = (page) => s.differentFirstPage && page === 1;
|
|
1834
|
+
return {
|
|
1835
|
+
info: { title: s.title ?? "Document", author: s.author, subject: s.subtitle, creator: "AI Matrx", producer: "@ai-matrx/print" },
|
|
1836
|
+
pageSize: { width: s.page.width, height: s.page.height },
|
|
1837
|
+
pageOrientation: s.page.orientation,
|
|
1838
|
+
pageMargins: [m.left, m.top, m.right, m.bottom],
|
|
1839
|
+
language: s.lang,
|
|
1840
|
+
header: (page, pages) => {
|
|
1841
|
+
pageCount = pages;
|
|
1842
|
+
pageCountSink?.(pageCount);
|
|
1843
|
+
if (hidden(page)) return null;
|
|
1844
|
+
const r = running(s.header, s, page, pages, [m.left, m.right]);
|
|
1845
|
+
return r ? { stack: [r], margin: [0, Math.max(14, m.top / 2 - 6), 0, 0] } : null;
|
|
1846
|
+
},
|
|
1847
|
+
footer: (page, pages) => {
|
|
1848
|
+
if (hidden(page)) return null;
|
|
1849
|
+
const r = running(s.footer, s, page, pages, [m.left, m.right]);
|
|
1850
|
+
return r ? { stack: [r], margin: [0, Math.max(10, m.bottom / 2 - 6), 0, 0] } : null;
|
|
1851
|
+
},
|
|
1852
|
+
// Keep-with-next: a heading never ends a page.
|
|
1853
|
+
// (A container stack reports every page its children touch, so only real
|
|
1854
|
+
// content nodes that START on the heading's page count as "following".)
|
|
1855
|
+
pageBreakBefore: (node, q) => {
|
|
1856
|
+
if (!node.headlineLevel) return false;
|
|
1857
|
+
const page = node.startPosition?.pageNumber;
|
|
1858
|
+
return !q.getFollowingNodesOnPage().some((n) => !n.stack && n.startPosition?.pageNumber === page);
|
|
1859
|
+
},
|
|
1860
|
+
content,
|
|
1861
|
+
defaultStyle: { font: "Roboto", fontSize: hs, lineHeight: 1.3, color: COLOR.text },
|
|
1862
|
+
styles: {
|
|
1863
|
+
p: { margin: [0, 0, 0, 7] },
|
|
1864
|
+
h1: { fontSize: hs * 1.8, bold: true, color: COLOR.heading, margin: [0, 14, 0, 6] },
|
|
1865
|
+
h2: { fontSize: hs * 1.45, bold: true, color: COLOR.heading, margin: [0, 12, 0, 5] },
|
|
1866
|
+
h3: { fontSize: hs * 1.2, bold: true, color: COLOR.heading, margin: [0, 10, 0, 4] },
|
|
1867
|
+
h4: { fontSize: hs * 1.05, bold: true, color: COLOR.heading, margin: [0, 8, 0, 3] },
|
|
1868
|
+
h5: { fontSize: hs, bold: true, color: COLOR.heading, margin: [0, 8, 0, 3] },
|
|
1869
|
+
h6: { fontSize: hs, bold: true, italics: true, color: COLOR.heading, margin: [0, 8, 0, 3] },
|
|
1870
|
+
caption: { fontSize: hs * 0.9, color: COLOR.muted },
|
|
1871
|
+
tocTitle: { fontSize: hs * 1.45, bold: true, color: COLOR.heading, margin: [0, 0, 0, 10] },
|
|
1872
|
+
toc: { fontSize: hs },
|
|
1873
|
+
tocL1: { fontSize: hs, bold: true },
|
|
1874
|
+
coverTitle: { fontSize: 30, bold: true, color: COLOR.heading, lineHeight: 1.1 },
|
|
1875
|
+
coverSubtitle: { fontSize: 16, color: "#4b5563", margin: [0, 10, 0, 0] },
|
|
1876
|
+
coverMeta: { fontSize: 12, color: "#4b5563", margin: [0, 4, 0, 0] }
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
var FONTS_READY = /* @__PURE__ */ Symbol.for("ai-matrx.print.document.pdfmake-fonts");
|
|
1881
|
+
async function loadPdfMake() {
|
|
1882
|
+
const mod = await import("pdfmake");
|
|
1883
|
+
const pdfMake = mod.default ?? mod;
|
|
1884
|
+
if (!pdfMake[FONTS_READY]) {
|
|
1885
|
+
const vfsMod = await import("pdfmake/build/vfs_fonts.js");
|
|
1886
|
+
const vfs = vfsMod.default ?? vfsMod;
|
|
1887
|
+
if (typeof pdfMake.addVirtualFileSystem === "function") {
|
|
1888
|
+
pdfMake.addVirtualFileSystem(vfs);
|
|
1889
|
+
const courier = await import("pdfmake/build/standard-fonts/Courier.js");
|
|
1890
|
+
pdfMake.addFontContainer?.(courier.default ?? courier);
|
|
1891
|
+
} else {
|
|
1892
|
+
const B = globalThis.Buffer;
|
|
1893
|
+
for (const [k, v] of Object.entries(vfs)) pdfMake.virtualfs?.writeFileSync(k, B.from(v, "base64"));
|
|
1894
|
+
}
|
|
1895
|
+
pdfMake.addFonts({
|
|
1896
|
+
Roboto: {
|
|
1897
|
+
normal: "Roboto-Regular.ttf",
|
|
1898
|
+
bold: "Roboto-Medium.ttf",
|
|
1899
|
+
italics: "Roboto-Italic.ttf",
|
|
1900
|
+
bolditalics: "Roboto-MediumItalic.ttf"
|
|
1901
|
+
},
|
|
1902
|
+
Courier: { normal: "Courier", bold: "Courier-Bold", italics: "Courier-Oblique", bolditalics: "Courier-BoldOblique" }
|
|
1903
|
+
});
|
|
1904
|
+
pdfMake.setUrlAccessPolicy?.(() => false);
|
|
1905
|
+
pdfMake.setLocalAccessPolicy?.((p) => /^Courier(-Bold|-Oblique|-BoldOblique)?$/.test(p));
|
|
1906
|
+
pdfMake[FONTS_READY] = true;
|
|
1907
|
+
}
|
|
1908
|
+
return pdfMake;
|
|
1909
|
+
}
|
|
1910
|
+
async function documentToPdf(tree, images) {
|
|
1911
|
+
const pdfMake = await loadPdfMake();
|
|
1912
|
+
let pageCount = 0;
|
|
1913
|
+
const def = buildPdfDefinition(tree, images, (n) => {
|
|
1914
|
+
pageCount = n;
|
|
1915
|
+
});
|
|
1916
|
+
const buf = await pdfMake.createPdf(def).getBuffer();
|
|
1917
|
+
return { bytes: buf instanceof Uint8Array ? new Uint8Array(buf) : new Uint8Array(buf), pageCount };
|
|
1918
|
+
}
|
|
1919
|
+
async function headingPages(tree, images) {
|
|
1920
|
+
const pdfMake = await loadPdfMake();
|
|
1921
|
+
const def = buildPdfDefinition({ ...tree, notices: [] }, images);
|
|
1922
|
+
await pdfMake.createPdf(def).getBuffer();
|
|
1923
|
+
const want = new Set(tree.toc.map((e) => e.id));
|
|
1924
|
+
const pages = /* @__PURE__ */ new Map();
|
|
1925
|
+
const walk = (n) => {
|
|
1926
|
+
if (Array.isArray(n)) {
|
|
1927
|
+
for (const x of n) walk(x);
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
if (!n || typeof n !== "object") return;
|
|
1931
|
+
const o = n;
|
|
1932
|
+
const pos = o.positions?.[0];
|
|
1933
|
+
if (typeof o.id === "string" && want.has(o.id) && pos?.pageNumber && !pages.has(o.id)) pages.set(o.id, pos.pageNumber);
|
|
1934
|
+
for (const key of ["stack", "columns", "ul", "ol", "content"]) if (key in o) walk(o[key]);
|
|
1935
|
+
const table = o.table;
|
|
1936
|
+
if (table?.body) walk(table.body);
|
|
1937
|
+
};
|
|
1938
|
+
walk(def.content);
|
|
1939
|
+
return pages;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// src/document/tree.ts
|
|
1943
|
+
var import_mdast_util_from_markdown = require("mdast-util-from-markdown");
|
|
1944
|
+
var import_mdast_util_gfm = require("mdast-util-gfm");
|
|
1945
|
+
var import_micromark_extension_gfm = require("micromark-extension-gfm");
|
|
1946
|
+
|
|
1947
|
+
// src/markdown.ts
|
|
1948
|
+
var import_html_escape3 = require("@ai-matrx/kit/html-escape");
|
|
1949
|
+
function removeThinkingContent(content) {
|
|
1950
|
+
if (!content) return "";
|
|
1951
|
+
let result = content.replace(/\n*<thinking>[\s\S]*?<\/thinking>\n*/gi, "\n").replace(/\n*<think>[\s\S]*?<\/think>\n*/gi, "\n").replace(/\n*<reasoning>[\s\S]*?<\/reasoning>\n*/gi, "\n");
|
|
1952
|
+
result = result.replace(/\n{3,}/g, "\n\n");
|
|
1953
|
+
return result.trim();
|
|
1954
|
+
}
|
|
1955
|
+
var PAGE_BREAK_HTML = `<div class="${PAGE_BREAK_CLASS}" role="separator" aria-label="${PAGE_BREAK_LABEL}" data-label="${PAGE_BREAK_LABEL}"></div>`;
|
|
1956
|
+
var LINK_URL = String.raw`((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?`;
|
|
1957
|
+
var IMAGE_RE = new RegExp(String.raw`!\[([^\]]*)\]\(` + LINK_URL + String.raw`\)`, "g");
|
|
1958
|
+
var LINK_RE = new RegExp(String.raw`\[([^\]]+)\]\(` + LINK_URL + String.raw`\)`, "g");
|
|
1959
|
+
|
|
1960
|
+
// src/document/citations.ts
|
|
1961
|
+
var t = (value) => ({ type: "text", value });
|
|
1962
|
+
var em = (value) => ({ type: "emphasis", children: [t(value)] });
|
|
1963
|
+
function parseCitationGroup(inner) {
|
|
1964
|
+
const parts = inner.split(";").map((p) => p.trim());
|
|
1965
|
+
if (parts.length === 0) return null;
|
|
1966
|
+
const refs = [];
|
|
1967
|
+
for (const part of parts) {
|
|
1968
|
+
const m = /^(-?)@([A-Za-z0-9_][\w:.#$%&+?<>~/-]*?)(?:\s*,\s*(.+))?$/.exec(part);
|
|
1969
|
+
if (!m) return null;
|
|
1970
|
+
if (/^(fig|tbl|sec|eq|lst):/i.test(m[2])) return null;
|
|
1971
|
+
refs.push({ key: m[2], locator: m[3]?.trim(), suppressAuthor: m[1] === "-" });
|
|
1972
|
+
}
|
|
1973
|
+
return refs;
|
|
1974
|
+
}
|
|
1975
|
+
function year(item) {
|
|
1976
|
+
const dp = item.issued?.["date-parts"]?.[0]?.[0];
|
|
1977
|
+
if (dp !== void 0 && dp !== null && String(dp).trim()) return String(dp);
|
|
1978
|
+
return item.issued?.literal ?? "n.d.";
|
|
1979
|
+
}
|
|
1980
|
+
var family = (n) => n.literal ?? n.family ?? n.given ?? "";
|
|
1981
|
+
function initials(given) {
|
|
1982
|
+
if (!given) return "";
|
|
1983
|
+
return given.split(/[\s]+/).filter(Boolean).map(
|
|
1984
|
+
(g) => g.split("-").map((p) => `${p[0]?.toUpperCase() ?? ""}.`).join("-")
|
|
1985
|
+
).join(" ");
|
|
1986
|
+
}
|
|
1987
|
+
function authorsOf(item) {
|
|
1988
|
+
return item.author?.length ? item.author : item.editor ?? [];
|
|
1989
|
+
}
|
|
1990
|
+
function shortAuthors(item, style, narrative = false) {
|
|
1991
|
+
const a = authorsOf(item);
|
|
1992
|
+
if (a.length === 0) return item.title ? `"${item.title}"` : item.id;
|
|
1993
|
+
const and = style === "apa" && !narrative ? "&" : "and";
|
|
1994
|
+
if (a.length === 1) return family(a[0]);
|
|
1995
|
+
if (a.length === 2) return `${family(a[0])} ${and} ${family(a[1])}`;
|
|
1996
|
+
if (style === "chicago" && a.length === 3)
|
|
1997
|
+
return `${family(a[0])}, ${family(a[1])}, and ${family(a[2])}`;
|
|
1998
|
+
return `${family(a[0])} et al.`;
|
|
1999
|
+
}
|
|
2000
|
+
function mlaLocator(locator) {
|
|
2001
|
+
return locator.replace(/^(?:pp?\.|pages?)\s*/i, "");
|
|
2002
|
+
}
|
|
2003
|
+
function formatCitation(refs, lookup, style, narrative = false) {
|
|
2004
|
+
let resolved = true;
|
|
2005
|
+
const pieces = refs.map((r) => {
|
|
2006
|
+
const item = lookup(r.key);
|
|
2007
|
+
if (!item) {
|
|
2008
|
+
resolved = false;
|
|
2009
|
+
return `${r.key}?`;
|
|
2010
|
+
}
|
|
2011
|
+
const who = shortAuthors(item, style);
|
|
2012
|
+
const when = year(item);
|
|
2013
|
+
const loc = r.locator;
|
|
2014
|
+
if (style === "mla") {
|
|
2015
|
+
const l = loc ? mlaLocator(loc) : "";
|
|
2016
|
+
if (r.suppressAuthor || narrative) return l;
|
|
2017
|
+
return l ? `${who} ${l}` : who;
|
|
2018
|
+
}
|
|
2019
|
+
if (style === "chicago") {
|
|
2020
|
+
const l = loc ? mlaLocator(loc) : "";
|
|
2021
|
+
const core2 = r.suppressAuthor || narrative ? when : `${who} ${when}`;
|
|
2022
|
+
return l ? `${core2}, ${l}` : core2;
|
|
2023
|
+
}
|
|
2024
|
+
const core = r.suppressAuthor || narrative ? when : `${who}, ${when}`;
|
|
2025
|
+
return loc ? `${core}, ${loc}` : core;
|
|
2026
|
+
});
|
|
2027
|
+
if (narrative && refs.length === 1) {
|
|
2028
|
+
const item = lookup(refs[0].key);
|
|
2029
|
+
if (item) {
|
|
2030
|
+
const inner = pieces[0];
|
|
2031
|
+
const who = shortAuthors(item, style, true);
|
|
2032
|
+
return { label: inner ? `${who} (${inner})` : who, resolved };
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
const body = pieces.filter(Boolean).join("; ");
|
|
2036
|
+
return { label: body ? `(${body})` : "", resolved };
|
|
2037
|
+
}
|
|
2038
|
+
function apaNames(names) {
|
|
2039
|
+
const fmt = (n) => n.literal ?? [n.family, initials(n.given)].filter(Boolean).join(", ");
|
|
2040
|
+
if (names.length === 1) return fmt(names[0]);
|
|
2041
|
+
if (names.length <= 20) {
|
|
2042
|
+
return `${names.slice(0, -1).map(fmt).join(", ")}, & ${fmt(names[names.length - 1])}`;
|
|
2043
|
+
}
|
|
2044
|
+
return `${names.slice(0, 19).map(fmt).join(", ")}, \u2026 ${fmt(names[names.length - 1])}`;
|
|
2045
|
+
}
|
|
2046
|
+
function invertedFirst(names, and, etAlAt) {
|
|
2047
|
+
const first = (n) => n.literal ?? [n.family, n.given].filter(Boolean).join(", ");
|
|
2048
|
+
const rest = (n) => n.literal ?? [n.given, n.family].filter(Boolean).join(" ");
|
|
2049
|
+
if (names.length === 1) return first(names[0]);
|
|
2050
|
+
if (names.length >= etAlAt) return `${first(names[0])}, et al.`;
|
|
2051
|
+
if (names.length === 2) return `${first(names[0])}, ${and} ${rest(names[1])}`;
|
|
2052
|
+
return `${first(names[0])}, ${names.slice(1, -1).map(rest).join(", ")}, ${and} ${rest(names[names.length - 1])}`;
|
|
2053
|
+
}
|
|
2054
|
+
var endDot = (s) => /[.?!]$/.test(s) ? s : `${s}.`;
|
|
2055
|
+
var isArticle = (i) => /article|chapter|paper-conference|entry/.test(i.type ?? "") || Boolean(i["container-title"] && (i.volume || i.issue || i.page));
|
|
2056
|
+
var isWeb = (i) => /webpage|post|post-weblog/.test(i.type ?? "") || !i.publisher && !i["container-title"] && Boolean(i.URL);
|
|
2057
|
+
var link = (i) => i.DOI ? `https://doi.org/${i.DOI.replace(/^https?:\/\/doi\.org\//, "")}` : i.URL;
|
|
2058
|
+
function formatApa(i) {
|
|
2059
|
+
const out = [];
|
|
2060
|
+
const names = authorsOf(i);
|
|
2061
|
+
const y = year(i);
|
|
2062
|
+
if (names.length) out.push(t(`${endDot(apaNames(names))} (${y}). `));
|
|
2063
|
+
if (isArticle(i)) {
|
|
2064
|
+
if (i.title) out.push(t(`${endDot(i.title)} `));
|
|
2065
|
+
if (!names.length) out.push(t(`(${y}). `));
|
|
2066
|
+
if (i["container-title"]) {
|
|
2067
|
+
out.push(em(i["container-title"]));
|
|
2068
|
+
if (i.volume) {
|
|
2069
|
+
out.push(t(", "), em(String(i.volume)));
|
|
2070
|
+
}
|
|
2071
|
+
if (i.issue) out.push(t(`(${i.issue})`));
|
|
2072
|
+
if (i.page) out.push(t(`, ${i.page}`));
|
|
2073
|
+
out.push(t(". "));
|
|
2074
|
+
}
|
|
2075
|
+
} else {
|
|
2076
|
+
if (i.title) out.push(em(i.title));
|
|
2077
|
+
if (i.edition) out.push(t(` (${i.edition} ed.)`));
|
|
2078
|
+
out.push(t(". "));
|
|
2079
|
+
if (!names.length) out.push(t(`(${y}). `));
|
|
2080
|
+
if (isWeb(i) && i["container-title"]) out.push(t(`${endDot(i["container-title"])} `));
|
|
2081
|
+
if (i.publisher) out.push(t(`${endDot(i.publisher)} `));
|
|
2082
|
+
}
|
|
2083
|
+
const l = link(i);
|
|
2084
|
+
if (l) out.push({ type: "link", url: l, children: [t(l)] });
|
|
2085
|
+
return trimTail(out);
|
|
2086
|
+
}
|
|
2087
|
+
function formatMla(i) {
|
|
2088
|
+
const out = [];
|
|
2089
|
+
const names = authorsOf(i);
|
|
2090
|
+
if (names.length) out.push(t(`${endDot(invertedFirst(names, "and", 3))} `));
|
|
2091
|
+
const y = year(i);
|
|
2092
|
+
if (isArticle(i) || isWeb(i)) {
|
|
2093
|
+
if (i.title) out.push(t(`\u201C${endDot(i.title)}\u201D `));
|
|
2094
|
+
if (i["container-title"]) out.push(em(i["container-title"]));
|
|
2095
|
+
const tail = [];
|
|
2096
|
+
if (i.volume) tail.push(`vol. ${i.volume}`);
|
|
2097
|
+
if (i.issue) tail.push(`no. ${i.issue}`);
|
|
2098
|
+
if (i.publisher && isWeb(i)) tail.push(i.publisher);
|
|
2099
|
+
if (y !== "n.d.") tail.push(y);
|
|
2100
|
+
if (i.page) tail.push(`${/[-–]/.test(i.page) ? "pp." : "p."} ${i.page}`);
|
|
2101
|
+
if (tail.length) out.push(t(`${i["container-title"] ? ", " : ""}${tail.join(", ")}`));
|
|
2102
|
+
const l = link(i);
|
|
2103
|
+
if (l) out.push(t(", "), { type: "link", url: l, children: [t(l.replace(/^https?:\/\//, ""))] });
|
|
2104
|
+
out.push(t("."));
|
|
2105
|
+
} else {
|
|
2106
|
+
if (i.title) out.push(em(i.title), t(". "));
|
|
2107
|
+
const tail = [i.publisher, y !== "n.d." ? y : void 0].filter(Boolean);
|
|
2108
|
+
if (tail.length) out.push(t(`${tail.join(", ")}.`));
|
|
2109
|
+
}
|
|
2110
|
+
return trimTail(out);
|
|
2111
|
+
}
|
|
2112
|
+
function formatChicago(i) {
|
|
2113
|
+
const out = [];
|
|
2114
|
+
const names = authorsOf(i);
|
|
2115
|
+
if (names.length) out.push(t(`${endDot(invertedFirst(names, "and", 11))} `));
|
|
2116
|
+
out.push(t(`${year(i)}. `));
|
|
2117
|
+
if (isArticle(i) || isWeb(i)) {
|
|
2118
|
+
if (i.title) out.push(t(`\u201C${endDot(i.title)}\u201D `));
|
|
2119
|
+
if (i["container-title"]) {
|
|
2120
|
+
out.push(em(i["container-title"]));
|
|
2121
|
+
if (i.volume) out.push(t(` ${i.volume}`));
|
|
2122
|
+
if (i.issue) out.push(t(` (${i.issue})`));
|
|
2123
|
+
if (i.page) out.push(t(`: ${i.page}`));
|
|
2124
|
+
out.push(t(". "));
|
|
2125
|
+
}
|
|
2126
|
+
} else {
|
|
2127
|
+
if (i.title) out.push(em(i.title), t(". "));
|
|
2128
|
+
const pub = [i["publisher-place"], i.publisher].filter(Boolean).join(": ");
|
|
2129
|
+
if (pub) out.push(t(`${endDot(pub)} `));
|
|
2130
|
+
}
|
|
2131
|
+
const l = link(i);
|
|
2132
|
+
if (l) out.push({ type: "link", url: l, children: [t(l)] }, t("."));
|
|
2133
|
+
return trimTail(out);
|
|
2134
|
+
}
|
|
2135
|
+
function trimTail(out) {
|
|
2136
|
+
const last = out[out.length - 1];
|
|
2137
|
+
if (last?.type === "text") last.value = last.value.replace(/\s+$/, "");
|
|
2138
|
+
return out;
|
|
2139
|
+
}
|
|
2140
|
+
function formatBibliography(items, style) {
|
|
2141
|
+
const sortKey = (i) => {
|
|
2142
|
+
const a = authorsOf(i)[0];
|
|
2143
|
+
return `${(a ? family(a) : i.title ?? i.id).toLowerCase()}\0${year(i)}\0${(i.title ?? "").toLowerCase()}`;
|
|
2144
|
+
};
|
|
2145
|
+
const fmt = style === "mla" ? formatMla : style === "chicago" ? formatChicago : formatApa;
|
|
2146
|
+
return [...items].sort((a, b) => sortKey(a).localeCompare(sortKey(b))).map((i) => ({ id: i.id, children: fmt(i) }));
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
// src/document/tree.ts
|
|
2150
|
+
var MARK = "matrx:";
|
|
2151
|
+
var FENCE_RE = /^\s*(`{3,}|~{3,})/;
|
|
2152
|
+
var TABLE_CAPTION_RE = /^\s{0,3}(?:Table:|:)\s+\S/;
|
|
2153
|
+
function prepass(body) {
|
|
2154
|
+
const lines = body.split(/\r?\n/);
|
|
2155
|
+
const out = [];
|
|
2156
|
+
let fence = null;
|
|
2157
|
+
const isTableLine = (l) => l !== void 0 && /^\s{0,3}\|/.test(l);
|
|
2158
|
+
const prevNonBlank = (i) => {
|
|
2159
|
+
for (let j = i - 1; j >= 0; j--) if (lines[j].trim()) return lines[j];
|
|
2160
|
+
return void 0;
|
|
2161
|
+
};
|
|
2162
|
+
const nextNonBlank = (i) => {
|
|
2163
|
+
for (let j = i + 1; j < lines.length; j++) if (lines[j].trim()) return lines[j];
|
|
2164
|
+
return void 0;
|
|
2165
|
+
};
|
|
2166
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2167
|
+
const line = lines[i];
|
|
2168
|
+
const fm = FENCE_RE.exec(line);
|
|
2169
|
+
if (fm) {
|
|
2170
|
+
const marker = fm[1];
|
|
2171
|
+
if (fence === null) fence = marker;
|
|
2172
|
+
else if (marker[0] === fence[0] && marker.length >= fence.length) fence = null;
|
|
2173
|
+
out.push(line);
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
2176
|
+
if (fence !== null) {
|
|
2177
|
+
out.push(line);
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
let directive = null;
|
|
2181
|
+
const section = parseSectionBreakLine(line);
|
|
2182
|
+
if (section) directive = `<!--${MARK}section ${JSON.stringify(section)}-->`;
|
|
2183
|
+
else if (isPageBreakLine(line)) directive = `<!--${MARK}pagebreak-->`;
|
|
2184
|
+
else if (isTocLine(line)) directive = `<!--${MARK}toc-->`;
|
|
2185
|
+
else if (isBibliographyLine(line)) directive = `<!--${MARK}bibliography-->`;
|
|
2186
|
+
if (directive) {
|
|
2187
|
+
out.push("", directive, "");
|
|
2188
|
+
continue;
|
|
2189
|
+
}
|
|
2190
|
+
if (TABLE_CAPTION_RE.test(line) && (isTableLine(prevNonBlank(i)) || isTableLine(nextNonBlank(i)))) {
|
|
2191
|
+
out.push("", line.trim().replace(/^:\s+/, "Table: "), "");
|
|
2192
|
+
continue;
|
|
2193
|
+
}
|
|
2194
|
+
out.push(line);
|
|
2195
|
+
}
|
|
2196
|
+
return out.join("\n");
|
|
2197
|
+
}
|
|
2198
|
+
var slugify = (s) => s.toLowerCase().normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-") || "section";
|
|
2199
|
+
function plainText(inlines2) {
|
|
2200
|
+
return inlines2.map((n) => {
|
|
2201
|
+
switch (n.type) {
|
|
2202
|
+
case "text":
|
|
2203
|
+
case "code":
|
|
2204
|
+
return n.value;
|
|
2205
|
+
case "strong":
|
|
2206
|
+
case "emphasis":
|
|
2207
|
+
case "delete":
|
|
2208
|
+
case "link":
|
|
2209
|
+
return plainText(n.children);
|
|
2210
|
+
case "xref":
|
|
2211
|
+
case "cite":
|
|
2212
|
+
return n.label;
|
|
2213
|
+
case "image":
|
|
2214
|
+
return n.alt;
|
|
2215
|
+
case "break":
|
|
2216
|
+
return " ";
|
|
2217
|
+
default:
|
|
2218
|
+
return "";
|
|
2219
|
+
}
|
|
2220
|
+
}).join("");
|
|
2221
|
+
}
|
|
2222
|
+
function uniqueId(ctx, base) {
|
|
2223
|
+
let id = base;
|
|
2224
|
+
for (let n = 2; ctx.ids.has(id); n++) id = `${base}-${n}`;
|
|
2225
|
+
ctx.ids.add(id);
|
|
2226
|
+
return id;
|
|
2227
|
+
}
|
|
2228
|
+
var CITE_GROUP_RE = /\[(-?@[^[\]]+)\]/g;
|
|
2229
|
+
var BARE_RE = /(^|[^\w@.\\])@((?:fig|tbl|sec|eq):[\w-]+|[A-Za-z0-9_](?:[\w:.#$%&+?<>~/-]*\w)?)/g;
|
|
2230
|
+
function scanText(value, ctx) {
|
|
2231
|
+
const out = [];
|
|
2232
|
+
const pushText = (s) => {
|
|
2233
|
+
if (!s) return;
|
|
2234
|
+
const last2 = out[out.length - 1];
|
|
2235
|
+
if (last2?.type === "text") last2.value += s;
|
|
2236
|
+
else out.push({ type: "text", value: s });
|
|
2237
|
+
};
|
|
2238
|
+
const scanBare = (s) => {
|
|
2239
|
+
let last2 = 0;
|
|
2240
|
+
for (const m of s.matchAll(BARE_RE)) {
|
|
2241
|
+
const key = m[2];
|
|
2242
|
+
const start = m.index + m[1].length;
|
|
2243
|
+
const xm = /^(fig|tbl|sec|eq):(.+)$/.exec(key);
|
|
2244
|
+
if (xm) {
|
|
2245
|
+
pushText(s.slice(last2, start));
|
|
2246
|
+
out.push(makeXref(`${xm[1]}:${xm[2]}`, ctx));
|
|
2247
|
+
last2 = start + key.length + 1;
|
|
2248
|
+
} else if (ctx.refs.has(key)) {
|
|
2249
|
+
pushText(s.slice(last2, start));
|
|
2250
|
+
ctx.cited.add(key);
|
|
2251
|
+
const { label, resolved } = formatCitation(
|
|
2252
|
+
[{ key, suppressAuthor: false }],
|
|
2253
|
+
(k) => ctx.refs.get(k),
|
|
2254
|
+
ctx.settings.citationStyle,
|
|
2255
|
+
true
|
|
2256
|
+
);
|
|
2257
|
+
out.push({ type: "cite", keys: [key], label, resolved });
|
|
2258
|
+
last2 = start + key.length + 1;
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
pushText(s.slice(last2));
|
|
2262
|
+
};
|
|
2263
|
+
let last = 0;
|
|
2264
|
+
for (const m of value.matchAll(CITE_GROUP_RE)) {
|
|
2265
|
+
const inner = m[1];
|
|
2266
|
+
const refs = parseCitationGroup(inner);
|
|
2267
|
+
const single = /^@((?:fig|tbl|sec|eq):[\w-]+)$/.exec(inner.trim());
|
|
2268
|
+
if (!refs && !single) continue;
|
|
2269
|
+
scanBare(value.slice(last, m.index));
|
|
2270
|
+
if (single) out.push(makeXref(single[1], ctx));
|
|
2271
|
+
else if (refs) {
|
|
2272
|
+
const { label, resolved } = formatCitation(refs, (k) => ctx.refs.get(k), ctx.settings.citationStyle);
|
|
2273
|
+
for (const r of refs) if (ctx.refs.has(r.key)) ctx.cited.add(r.key);
|
|
2274
|
+
if (!resolved) {
|
|
2275
|
+
const missing = refs.filter((r) => !ctx.refs.has(r.key)).map((r) => r.key);
|
|
2276
|
+
ctx.notify({
|
|
2277
|
+
code: "citation-unresolved",
|
|
2278
|
+
message: `No reference was found for ${missing.map((k) => `@${k}`).join(", ")}.`,
|
|
2279
|
+
remedy: "Add it under `references:` in the settings at the top (id, title, author, issued), or fix the key's spelling."
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2282
|
+
out.push({ type: "cite", keys: refs.map((r) => r.key), label, resolved });
|
|
2283
|
+
}
|
|
2284
|
+
last = m.index + m[0].length;
|
|
2285
|
+
}
|
|
2286
|
+
scanBare(value.slice(last));
|
|
2287
|
+
return out;
|
|
2288
|
+
}
|
|
2289
|
+
function makeXref(target, ctx) {
|
|
2290
|
+
const x = { type: "xref", target, label: target, resolved: false };
|
|
2291
|
+
ctx.xrefs.push(x);
|
|
2292
|
+
return x;
|
|
2293
|
+
}
|
|
2294
|
+
function inlines(nodes, ctx) {
|
|
2295
|
+
const out = [];
|
|
2296
|
+
for (const n of nodes) {
|
|
2297
|
+
switch (n.type) {
|
|
2298
|
+
case "text":
|
|
2299
|
+
out.push(...scanText(n.value, ctx));
|
|
2300
|
+
break;
|
|
2301
|
+
case "strong":
|
|
2302
|
+
out.push({ type: "strong", children: inlines(n.children, ctx) });
|
|
2303
|
+
break;
|
|
2304
|
+
case "emphasis":
|
|
2305
|
+
out.push({ type: "emphasis", children: inlines(n.children, ctx) });
|
|
2306
|
+
break;
|
|
2307
|
+
case "delete":
|
|
2308
|
+
out.push({ type: "delete", children: inlines(n.children, ctx) });
|
|
2309
|
+
break;
|
|
2310
|
+
case "inlineCode":
|
|
2311
|
+
out.push({ type: "code", value: n.value });
|
|
2312
|
+
break;
|
|
2313
|
+
case "break":
|
|
2314
|
+
out.push({ type: "break" });
|
|
2315
|
+
break;
|
|
2316
|
+
case "link":
|
|
2317
|
+
out.push({ type: "link", url: n.url, children: inlines(n.children, ctx) });
|
|
2318
|
+
break;
|
|
2319
|
+
case "image":
|
|
2320
|
+
out.push({ type: "image", url: n.url, alt: n.alt ?? "" });
|
|
2321
|
+
break;
|
|
2322
|
+
case "linkReference": {
|
|
2323
|
+
const def = ctx.defs.get(n.identifier);
|
|
2324
|
+
const kids = inlines(n.children, ctx);
|
|
2325
|
+
if (def) out.push({ type: "link", url: def.url, children: kids });
|
|
2326
|
+
else out.push({ type: "text", value: "[" }, ...kids, { type: "text", value: "]" });
|
|
2327
|
+
break;
|
|
2328
|
+
}
|
|
2329
|
+
case "imageReference": {
|
|
2330
|
+
const def = ctx.defs.get(n.identifier);
|
|
2331
|
+
if (def) out.push({ type: "image", url: def.url, alt: n.alt ?? "" });
|
|
2332
|
+
break;
|
|
2333
|
+
}
|
|
2334
|
+
case "footnoteReference": {
|
|
2335
|
+
let num = ctx.noteOrder.get(n.identifier);
|
|
2336
|
+
if (num === void 0) {
|
|
2337
|
+
num = ctx.noteOrder.size + 1;
|
|
2338
|
+
ctx.noteOrder.set(n.identifier, num);
|
|
2339
|
+
}
|
|
2340
|
+
out.push({ type: "noteRef", n: num });
|
|
2341
|
+
break;
|
|
2342
|
+
}
|
|
2343
|
+
case "html":
|
|
2344
|
+
if (/^<br\s*\/?>$/i.test(n.value.trim())) out.push({ type: "break" });
|
|
2345
|
+
else if (!/^<!--/.test(n.value)) announceHtml(ctx);
|
|
2346
|
+
break;
|
|
2347
|
+
default:
|
|
2348
|
+
break;
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
return out;
|
|
2352
|
+
}
|
|
2353
|
+
function announceHtml(ctx) {
|
|
2354
|
+
if (ctx.htmlAnnounced) return;
|
|
2355
|
+
ctx.htmlAnnounced = true;
|
|
2356
|
+
ctx.notify({
|
|
2357
|
+
code: "html-stripped",
|
|
2358
|
+
message: "Raw HTML tags in the document were left out of the printed/exported file (their text is kept).",
|
|
2359
|
+
remedy: "Use markdown formatting instead of HTML tags; a page break is the line <!-- pagebreak -->."
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
function directiveOf(node) {
|
|
2363
|
+
if (node.type !== "html") return null;
|
|
2364
|
+
const m = /^<!--matrx:(section|pagebreak|toc|bibliography)\s*(.*?)-->$/.exec(node.value.trim());
|
|
2365
|
+
if (!m) return null;
|
|
2366
|
+
if (m[1] === "section") {
|
|
2367
|
+
try {
|
|
2368
|
+
return { type: "section", value: JSON.parse(m[2] || "{}") };
|
|
2369
|
+
} catch {
|
|
2370
|
+
return { type: "section", value: {} };
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
return { type: m[1] };
|
|
2374
|
+
}
|
|
2375
|
+
var ATTR_RE = /\s*\{([^{}]*)\}\s*$/;
|
|
2376
|
+
function takeAttributes(kids) {
|
|
2377
|
+
const last = kids[kids.length - 1];
|
|
2378
|
+
if (last?.type !== "text") return { unnumbered: false };
|
|
2379
|
+
const m = ATTR_RE.exec(last.value);
|
|
2380
|
+
if (!m) return { unnumbered: false };
|
|
2381
|
+
const attrs = m[1].trim().split(/\s+/);
|
|
2382
|
+
if (!attrs.every((a) => /^(#[\w:.-]+|\.[\w-]+|-|[\w-]+=\S+)$/.test(a))) return { unnumbered: false };
|
|
2383
|
+
last.value = last.value.slice(0, m.index).replace(/\s+$/, "");
|
|
2384
|
+
if (!last.value) kids.pop();
|
|
2385
|
+
const id = attrs.find((a) => a.startsWith("#"))?.slice(1);
|
|
2386
|
+
const unnumbered = attrs.includes("-") || attrs.includes(".unnumbered");
|
|
2387
|
+
return { id, unnumbered };
|
|
2388
|
+
}
|
|
2389
|
+
function figureOf(node, ctx) {
|
|
2390
|
+
if (node.type !== "paragraph") return null;
|
|
2391
|
+
const kids = node.children.filter((c) => !(c.type === "text" && !c.value.trim()));
|
|
2392
|
+
const img = kids[0];
|
|
2393
|
+
if (!img || img.type !== "image" && img.type !== "imageReference") return null;
|
|
2394
|
+
let url;
|
|
2395
|
+
let alt = img.alt ?? "";
|
|
2396
|
+
if (img.type === "image") {
|
|
2397
|
+
url = img.url;
|
|
2398
|
+
if (!alt && img.title) alt = img.title;
|
|
2399
|
+
} else {
|
|
2400
|
+
const def = ctx.defs.get(img.identifier);
|
|
2401
|
+
if (!def) return null;
|
|
2402
|
+
url = def.url;
|
|
2403
|
+
}
|
|
2404
|
+
let id;
|
|
2405
|
+
if (kids.length === 2) {
|
|
2406
|
+
const tail = kids[1];
|
|
2407
|
+
const m = tail?.type === "text" ? /^\s*\{#fig:([\w-]+)[^}]*\}\s*$/.exec(tail.value) : null;
|
|
2408
|
+
if (!m) return null;
|
|
2409
|
+
id = m[1];
|
|
2410
|
+
} else if (kids.length > 2) return null;
|
|
2411
|
+
const figure = { type: "figure", url, alt };
|
|
2412
|
+
if (alt.trim() || id) {
|
|
2413
|
+
const number = ++ctx.figN;
|
|
2414
|
+
const children = alt.trim() ? scanText(alt, ctx) : [];
|
|
2415
|
+
const caption = { kind: "fig", id, number, label: `Figure ${number}`, children };
|
|
2416
|
+
if (id) uniqueId(ctx, `fig:${id}`);
|
|
2417
|
+
figure.caption = caption;
|
|
2418
|
+
ctx.figures.push(caption);
|
|
2419
|
+
}
|
|
2420
|
+
return figure;
|
|
2421
|
+
}
|
|
2422
|
+
function tableCaptionText(node) {
|
|
2423
|
+
if (!node || node.type !== "paragraph") return null;
|
|
2424
|
+
const first = node.children[0];
|
|
2425
|
+
if (first?.type !== "text" || !/^Table:\s+/.test(first.value)) return null;
|
|
2426
|
+
const rest = [{ ...first, value: first.value.replace(/^Table:\s+/, "") }, ...node.children.slice(1)];
|
|
2427
|
+
return rest;
|
|
2428
|
+
}
|
|
2429
|
+
function convertBlocks(nodes, ctx, out, allowDirectives = false) {
|
|
2430
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
2431
|
+
const node = nodes[i];
|
|
2432
|
+
const block3 = convertBlock(nodes, i, ctx, allowDirectives);
|
|
2433
|
+
if (block3 === "skip-next") {
|
|
2434
|
+
i++;
|
|
2435
|
+
continue;
|
|
2436
|
+
}
|
|
2437
|
+
if (block3) out.push(block3);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
function convertTable(node, captionNodes, ctx) {
|
|
2441
|
+
const rows = node.children.map((row) => row.children.map((cell) => inlines(cell.children, ctx)));
|
|
2442
|
+
const table = {
|
|
2443
|
+
type: "table",
|
|
2444
|
+
align: (node.align ?? []).map((a) => a ?? null),
|
|
2445
|
+
header: rows[0] ?? [],
|
|
2446
|
+
rows: rows.slice(1)
|
|
2447
|
+
};
|
|
2448
|
+
if (captionNodes) {
|
|
2449
|
+
const kids = inlines(captionNodes, ctx);
|
|
2450
|
+
const { id } = takeAttributes(kids);
|
|
2451
|
+
const tid = id?.replace(/^tbl:/, "");
|
|
2452
|
+
const number = ++ctx.tblN;
|
|
2453
|
+
const caption = { kind: "tbl", id: tid, number, label: `Table ${number}`, children: kids };
|
|
2454
|
+
if (tid) uniqueId(ctx, `tbl:${tid}`);
|
|
2455
|
+
table.caption = caption;
|
|
2456
|
+
ctx.tables.push(caption);
|
|
2457
|
+
}
|
|
2458
|
+
return table;
|
|
2459
|
+
}
|
|
2460
|
+
function convertBlock(nodes, i, ctx, allowDirectives) {
|
|
2461
|
+
const node = nodes[i];
|
|
2462
|
+
switch (node.type) {
|
|
2463
|
+
case "heading": {
|
|
2464
|
+
const children = inlines(node.children, ctx);
|
|
2465
|
+
const { id, unnumbered } = takeAttributes(children);
|
|
2466
|
+
const plain2 = plainText(children).trim();
|
|
2467
|
+
const hid = uniqueId(ctx, id ?? slugify(plain2));
|
|
2468
|
+
const h = {
|
|
2469
|
+
type: "heading",
|
|
2470
|
+
level: node.depth,
|
|
2471
|
+
id: hid,
|
|
2472
|
+
plain: plain2,
|
|
2473
|
+
children,
|
|
2474
|
+
numbered: !unnumbered
|
|
2475
|
+
};
|
|
2476
|
+
ctx.headingIndex.set(hid, h);
|
|
2477
|
+
return h;
|
|
2478
|
+
}
|
|
2479
|
+
case "paragraph": {
|
|
2480
|
+
const fig = figureOf(node, ctx);
|
|
2481
|
+
if (fig) return fig;
|
|
2482
|
+
const cap = tableCaptionText(node);
|
|
2483
|
+
if (cap) {
|
|
2484
|
+
const next = nodes[i + 1];
|
|
2485
|
+
const prev = nodes[i - 1];
|
|
2486
|
+
if (next?.type === "table") {
|
|
2487
|
+
const t2 = convertTable(next, cap, ctx);
|
|
2488
|
+
nodes[i + 1] = { type: "html", value: "<!--matrx:consumed-->" };
|
|
2489
|
+
return t2;
|
|
2490
|
+
}
|
|
2491
|
+
if (prev?.type === "table") return null;
|
|
2492
|
+
}
|
|
2493
|
+
return { type: "paragraph", children: inlines(node.children, ctx) };
|
|
2494
|
+
}
|
|
2495
|
+
case "table": {
|
|
2496
|
+
const next = nodes[i + 1];
|
|
2497
|
+
const cap = tableCaptionText(next);
|
|
2498
|
+
if (cap) {
|
|
2499
|
+
const t2 = convertTable(node, cap, ctx);
|
|
2500
|
+
nodes[i + 1] = { type: "html", value: "<!--matrx:consumed-->" };
|
|
2501
|
+
return t2;
|
|
2502
|
+
}
|
|
2503
|
+
return convertTable(node, null, ctx);
|
|
2504
|
+
}
|
|
2505
|
+
case "list": {
|
|
2506
|
+
const items = node.children.map((li) => {
|
|
2507
|
+
const children = [];
|
|
2508
|
+
convertBlocks(li.children, ctx, children);
|
|
2509
|
+
return { checked: li.checked ?? null, children };
|
|
2510
|
+
});
|
|
2511
|
+
return { type: "list", ordered: Boolean(node.ordered), start: node.start ?? 1, items };
|
|
2512
|
+
}
|
|
2513
|
+
case "blockquote": {
|
|
2514
|
+
const children = [];
|
|
2515
|
+
convertBlocks(node.children, ctx, children);
|
|
2516
|
+
return { type: "blockquote", children };
|
|
2517
|
+
}
|
|
2518
|
+
case "code":
|
|
2519
|
+
return { type: "code", lang: node.lang ?? "", value: node.value };
|
|
2520
|
+
case "thematicBreak":
|
|
2521
|
+
return { type: "rule" };
|
|
2522
|
+
case "html": {
|
|
2523
|
+
if (/^<!--matrx:consumed-->$/.test(node.value)) return null;
|
|
2524
|
+
if (/^\s*<!--[\s\S]*-->\s*$/.test(node.value)) return null;
|
|
2525
|
+
if (/^\s*<br\s*\/?>\s*$/i.test(node.value)) return null;
|
|
2526
|
+
const text = node.value.replace(/<(script|style)\b[\s\S]*?<\/\1>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
2527
|
+
if (/<(?!\/?(?:artifact|flashcards)\b)[a-z]/i.test(node.value)) announceHtml(ctx);
|
|
2528
|
+
return text ? { type: "paragraph", children: [{ type: "text", value: text }] } : null;
|
|
2529
|
+
}
|
|
2530
|
+
case "math":
|
|
2531
|
+
return { type: "code", lang: "math", value: node.value };
|
|
2532
|
+
default:
|
|
2533
|
+
void allowDirectives;
|
|
2534
|
+
return null;
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
function resolveXrefs(ctx) {
|
|
2538
|
+
const figs = new Map(ctx.figures.filter((c) => c.id).map((c) => [c.id, c]));
|
|
2539
|
+
const tbls = new Map(ctx.tables.filter((c) => c.id).map((c) => [c.id, c]));
|
|
2540
|
+
for (const x of ctx.xrefs) {
|
|
2541
|
+
const [kind, id] = x.target.split(/:(.*)/s);
|
|
2542
|
+
let label;
|
|
2543
|
+
if (kind === "fig") label = figs.get(id)?.label;
|
|
2544
|
+
else if (kind === "tbl") label = tbls.get(id)?.label;
|
|
2545
|
+
else if (kind === "sec") {
|
|
2546
|
+
const h = ctx.headingIndex.get(`sec:${id}`) ?? ctx.headingIndex.get(id);
|
|
2547
|
+
if (h) {
|
|
2548
|
+
label = h.number ? `Section ${h.number}` : `\u201C${h.plain}\u201D`;
|
|
2549
|
+
x.target = `sec:${h.id}`;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
if (label) {
|
|
2553
|
+
x.label = label;
|
|
2554
|
+
x.resolved = true;
|
|
2555
|
+
} else {
|
|
2556
|
+
x.label = "??";
|
|
2557
|
+
ctx.notify({
|
|
2558
|
+
code: "xref-unresolved",
|
|
2559
|
+
message: `The cross-reference @${x.target} points at nothing in this document.`,
|
|
2560
|
+
remedy: kind === "sec" ? `Give the heading an id: \`## Title {#sec:${id}}\`.` : kind === "fig" ? `Label the image: \`{#fig:${id}}\`.` : `Caption the table: a line \`Table: Caption {#tbl:${id}}\` right before or after it.`
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
function parseDocument(source, options = {}) {
|
|
2566
|
+
const notices = [];
|
|
2567
|
+
const notify = (n) => {
|
|
2568
|
+
notices.push(n);
|
|
2569
|
+
options.onNotice?.(n);
|
|
2570
|
+
};
|
|
2571
|
+
const sink = [];
|
|
2572
|
+
const fm = splitFrontmatter(source ?? "", sink);
|
|
2573
|
+
const settings = resolveSettings(fm.data, sink, options);
|
|
2574
|
+
for (const n of sink) notify(n);
|
|
2575
|
+
const body = removeThinkingContent(fm.body);
|
|
2576
|
+
const root = (0, import_mdast_util_from_markdown.fromMarkdown)(prepass(body), {
|
|
2577
|
+
extensions: [(0, import_micromark_extension_gfm.gfm)()],
|
|
2578
|
+
mdastExtensions: [(0, import_mdast_util_gfm.gfmFromMarkdown)()]
|
|
2579
|
+
});
|
|
2580
|
+
const ctx = {
|
|
2581
|
+
settings,
|
|
2582
|
+
notices,
|
|
2583
|
+
notify,
|
|
2584
|
+
defs: /* @__PURE__ */ new Map(),
|
|
2585
|
+
footnoteDefs: /* @__PURE__ */ new Map(),
|
|
2586
|
+
noteOrder: /* @__PURE__ */ new Map(),
|
|
2587
|
+
refs: new Map(settings.references.map((r) => [r.id, r])),
|
|
2588
|
+
cited: /* @__PURE__ */ new Set(),
|
|
2589
|
+
htmlAnnounced: false,
|
|
2590
|
+
ids: /* @__PURE__ */ new Set(),
|
|
2591
|
+
figN: 0,
|
|
2592
|
+
tblN: 0,
|
|
2593
|
+
figures: [],
|
|
2594
|
+
tables: [],
|
|
2595
|
+
xrefs: [],
|
|
2596
|
+
headingIndex: /* @__PURE__ */ new Map()
|
|
2597
|
+
};
|
|
2598
|
+
const visit = (n) => {
|
|
2599
|
+
if (n.type === "definition") ctx.defs.set(n.identifier, { url: n.url, title: n.title });
|
|
2600
|
+
if (n.type === "footnoteDefinition") ctx.footnoteDefs.set(n.identifier, n.children);
|
|
2601
|
+
if ("children" in n) for (const c of n.children) visit(c);
|
|
2602
|
+
};
|
|
2603
|
+
visit(root);
|
|
2604
|
+
const sections = [
|
|
2605
|
+
{ orientation: settings.page.orientation, columns: settings.columns, blocks: [] }
|
|
2606
|
+
];
|
|
2607
|
+
let hasTocBlock = false;
|
|
2608
|
+
let hasBibBlock = false;
|
|
2609
|
+
const top = root.children.filter((n) => n.type !== "definition" && n.type !== "footnoteDefinition");
|
|
2610
|
+
for (let i = 0; i < top.length; i++) {
|
|
2611
|
+
const node = top[i];
|
|
2612
|
+
const d = directiveOf(node);
|
|
2613
|
+
const current = sections[sections.length - 1];
|
|
2614
|
+
if (d?.type === "section") {
|
|
2615
|
+
sections.push({
|
|
2616
|
+
orientation: d.value.orientation ?? settings.page.orientation,
|
|
2617
|
+
columns: d.value.columns ?? settings.columns,
|
|
2618
|
+
blocks: []
|
|
2619
|
+
});
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
if (d?.type === "pagebreak") {
|
|
2623
|
+
current.blocks.push({ type: "pageBreak" });
|
|
2624
|
+
continue;
|
|
2625
|
+
}
|
|
2626
|
+
if (d?.type === "toc") {
|
|
2627
|
+
if (!hasTocBlock) current.blocks.push({ type: "toc" });
|
|
2628
|
+
hasTocBlock = true;
|
|
2629
|
+
continue;
|
|
2630
|
+
}
|
|
2631
|
+
if (d?.type === "bibliography") {
|
|
2632
|
+
current.blocks.push({ type: "bibliography" });
|
|
2633
|
+
hasBibBlock = true;
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
const block3 = convertBlock(top, i, ctx, true);
|
|
2637
|
+
if (block3 && block3 !== "skip-next") current.blocks.push(block3);
|
|
2638
|
+
}
|
|
2639
|
+
const nonEmpty = sections.filter((s, idx) => s.blocks.length > 0 || idx === 0);
|
|
2640
|
+
sections.length = 0;
|
|
2641
|
+
sections.push(...nonEmpty);
|
|
2642
|
+
for (const s of sections) {
|
|
2643
|
+
while (s.blocks[0]?.type === "pageBreak") s.blocks.shift();
|
|
2644
|
+
while (s.blocks[s.blocks.length - 1]?.type === "pageBreak") s.blocks.pop();
|
|
2645
|
+
}
|
|
2646
|
+
const allHeadings = [];
|
|
2647
|
+
const collect = (blocks3) => {
|
|
2648
|
+
for (const b of blocks3) if (b.type === "heading") allHeadings.push(b);
|
|
2649
|
+
};
|
|
2650
|
+
for (const s of sections) collect(s.blocks);
|
|
2651
|
+
const minLevel = Math.min(7, ...allHeadings.map((h) => h.level));
|
|
2652
|
+
if (settings.numberSections) {
|
|
2653
|
+
const counters = [0, 0, 0, 0, 0, 0];
|
|
2654
|
+
for (const h of allHeadings) {
|
|
2655
|
+
if (!h.numbered) continue;
|
|
2656
|
+
const depth = h.level - minLevel;
|
|
2657
|
+
counters[depth] = (counters[depth] ?? 0) + 1;
|
|
2658
|
+
for (let d = depth + 1; d < 6; d++) counters[d] = 0;
|
|
2659
|
+
h.number = counters.slice(0, depth + 1).map((c) => c || 1).join(".");
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
const notes = [];
|
|
2663
|
+
for (const [identifier, n] of [...ctx.noteOrder.entries()].sort((a, b) => a[1] - b[1])) {
|
|
2664
|
+
const children = [];
|
|
2665
|
+
convertBlocks(ctx.footnoteDefs.get(identifier) ?? [], ctx, children);
|
|
2666
|
+
notes.push({ n, children });
|
|
2667
|
+
}
|
|
2668
|
+
const last = sections[sections.length - 1];
|
|
2669
|
+
const backLevel = minLevel <= 6 ? minLevel : 1;
|
|
2670
|
+
const backHeading = (plain2, id) => {
|
|
2671
|
+
const h = {
|
|
2672
|
+
type: "heading",
|
|
2673
|
+
level: backLevel,
|
|
2674
|
+
id: uniqueId(ctx, id),
|
|
2675
|
+
plain: plain2,
|
|
2676
|
+
children: [{ type: "text", value: plain2 }],
|
|
2677
|
+
numbered: false
|
|
2678
|
+
};
|
|
2679
|
+
allHeadings.push(h);
|
|
2680
|
+
return h;
|
|
2681
|
+
};
|
|
2682
|
+
if (notes.length) last.blocks.push(backHeading("Notes", "notes"), { type: "notes" });
|
|
2683
|
+
const citedItems = [...ctx.cited].map((k) => ctx.refs.get(k)).filter((x) => Boolean(x));
|
|
2684
|
+
const bibliography = formatBibliography(citedItems, settings.citationStyle);
|
|
2685
|
+
if (bibliography.length || hasBibBlock) {
|
|
2686
|
+
const heading = backHeading(settings.bibliographyTitle, "references");
|
|
2687
|
+
if (hasBibBlock) {
|
|
2688
|
+
for (const s of sections) {
|
|
2689
|
+
const idx = s.blocks.findIndex((b) => b.type === "bibliography");
|
|
2690
|
+
if (idx >= 0) {
|
|
2691
|
+
s.blocks.splice(idx, 0, heading);
|
|
2692
|
+
break;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
} else {
|
|
2696
|
+
last.blocks.push(heading, { type: "bibliography" });
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
if (settings.toc.enabled && !hasTocBlock) {
|
|
2700
|
+
sections[0].blocks.unshift({ type: "toc" }, { type: "pageBreak" });
|
|
2701
|
+
}
|
|
2702
|
+
const order = [];
|
|
2703
|
+
for (const s of sections) for (const b of s.blocks) if (b.type === "heading") order.push(b);
|
|
2704
|
+
const toc = order.filter((h) => h.level - minLevel + 1 <= settings.toc.depth).map((h) => ({ id: h.id, level: h.level - minLevel + 1, text: h.number ? `${h.number} ${h.plain}` : h.plain }));
|
|
2705
|
+
resolveXrefs(ctx);
|
|
2706
|
+
if (!sections.some((s) => s.blocks.length) && !settings.cover) {
|
|
2707
|
+
notify({
|
|
2708
|
+
code: "empty-document",
|
|
2709
|
+
message: "The document has no content to print or export.",
|
|
2710
|
+
remedy: "Write something below the settings block (or remove the --- lines if they are not settings)."
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
return {
|
|
2714
|
+
settings,
|
|
2715
|
+
body,
|
|
2716
|
+
sections,
|
|
2717
|
+
toc,
|
|
2718
|
+
figures: ctx.figures,
|
|
2719
|
+
tables: ctx.tables,
|
|
2720
|
+
bibliography,
|
|
2721
|
+
notes,
|
|
2722
|
+
notices
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
// src/document.ts
|
|
2727
|
+
var DOCUMENT_FORMATS = [
|
|
2728
|
+
{ format: "pdf", label: "PDF", extension: "pdf", mime: "application/pdf" },
|
|
2729
|
+
{ format: "docx", label: "Word (.docx)", extension: "docx", mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
|
|
2730
|
+
{ format: "epub", label: "EPUB", extension: "epub", mime: "application/epub+zip" },
|
|
2731
|
+
{ format: "html", label: "Web page (.html)", extension: "html", mime: "text/html" },
|
|
2732
|
+
{ format: "markdown", label: "Markdown", extension: "md", mime: "text/markdown" }
|
|
2733
|
+
];
|
|
2734
|
+
function sniff(bytes) {
|
|
2735
|
+
const b = bytes;
|
|
2736
|
+
if (b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
|
|
2737
|
+
const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
|
|
2738
|
+
return { mime: "image/png", width: dv.getUint32(16), height: dv.getUint32(20) };
|
|
2739
|
+
}
|
|
2740
|
+
if (b[0] === 71 && b[1] === 73 && b[2] === 70) {
|
|
2741
|
+
return { mime: "image/gif", width: b[6] | b[7] << 8, height: b[8] | b[9] << 8 };
|
|
2742
|
+
}
|
|
2743
|
+
if (b[0] === 255 && b[1] === 216) {
|
|
2744
|
+
let i = 2;
|
|
2745
|
+
while (i + 9 < b.length) {
|
|
2746
|
+
if (b[i] !== 255) {
|
|
2747
|
+
i++;
|
|
2748
|
+
continue;
|
|
2749
|
+
}
|
|
2750
|
+
const marker = b[i + 1];
|
|
2751
|
+
const len = b[i + 2] << 8 | b[i + 3];
|
|
2752
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
2753
|
+
return { mime: "image/jpeg", height: b[i + 5] << 8 | b[i + 6], width: b[i + 7] << 8 | b[i + 8] };
|
|
2754
|
+
}
|
|
2755
|
+
i += 2 + len;
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
return null;
|
|
2759
|
+
}
|
|
2760
|
+
function toBase64(bytes) {
|
|
2761
|
+
const g = globalThis;
|
|
2762
|
+
if (g.Buffer) return g.Buffer.from(bytes).toString("base64");
|
|
2763
|
+
let s = "";
|
|
2764
|
+
for (let i = 0; i < bytes.length; i += 32768) s += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
2765
|
+
return btoa(s);
|
|
2766
|
+
}
|
|
2767
|
+
function fromBase64(b64) {
|
|
2768
|
+
const g = globalThis;
|
|
2769
|
+
if (g.Buffer) return new Uint8Array(g.Buffer.from(b64, "base64"));
|
|
2770
|
+
const bin = atob(b64);
|
|
2771
|
+
const out = new Uint8Array(bin.length);
|
|
2772
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
2773
|
+
return out;
|
|
2774
|
+
}
|
|
2775
|
+
var defaultImageLoader = async (url) => {
|
|
2776
|
+
const data = /^data:image\/[\w.+-]+;base64,(.*)$/i.exec(url);
|
|
2777
|
+
if (data) return fromBase64(data[1]);
|
|
2778
|
+
if (!/^https?:\/\//i.test(url) || typeof fetch !== "function") return null;
|
|
2779
|
+
const res = await fetch(url, { credentials: "omit" });
|
|
2780
|
+
if (!res.ok) return null;
|
|
2781
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
2782
|
+
};
|
|
2783
|
+
function imageUrls(tree) {
|
|
2784
|
+
const urls = /* @__PURE__ */ new Set();
|
|
2785
|
+
const walk = (blocks3) => {
|
|
2786
|
+
for (const b of blocks3) {
|
|
2787
|
+
if (b.type === "figure") urls.add(b.url);
|
|
2788
|
+
if (b.type === "list") for (const it of b.items) walk(it.children);
|
|
2789
|
+
if (b.type === "blockquote") walk(b.children);
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
for (const s of tree.sections) walk(s.blocks);
|
|
2793
|
+
if (tree.settings.cover && tree.settings.logo) urls.add(tree.settings.logo);
|
|
2794
|
+
return [...urls];
|
|
2795
|
+
}
|
|
2796
|
+
async function resolveDocumentImages(tree, loader = defaultImageLoader, onNotice) {
|
|
2797
|
+
const map = /* @__PURE__ */ new Map();
|
|
2798
|
+
await Promise.all(
|
|
2799
|
+
imageUrls(tree).map(async (url) => {
|
|
2800
|
+
let bytes = null;
|
|
2801
|
+
try {
|
|
2802
|
+
bytes = await loader(url);
|
|
2803
|
+
} catch {
|
|
2804
|
+
bytes = null;
|
|
2805
|
+
}
|
|
2806
|
+
const info = bytes ? sniff(bytes) : null;
|
|
2807
|
+
if (!bytes || !info || !info.width || !info.height) {
|
|
2808
|
+
map.set(url, null);
|
|
2809
|
+
const n = {
|
|
2810
|
+
code: "image-unavailable",
|
|
2811
|
+
message: `An image could not be included: ${url.length > 90 ? `${url.slice(0, 87)}\u2026` : url}`,
|
|
2812
|
+
remedy: bytes ? "Use a PNG, JPEG or GIF image." : "Use an image stored in AI Matrx (or any public https link that allows downloads); private or expired links cannot be printed."
|
|
2813
|
+
};
|
|
2814
|
+
tree.notices.push(n);
|
|
2815
|
+
onNotice?.(n);
|
|
2816
|
+
return;
|
|
2817
|
+
}
|
|
2818
|
+
map.set(url, { ...info, bytes, dataUrl: `data:${info.mime};base64,${toBase64(bytes)}` });
|
|
2819
|
+
})
|
|
2820
|
+
);
|
|
2821
|
+
return map;
|
|
2822
|
+
}
|
|
2823
|
+
var safeName = (s) => s.replace(/[\\/:*?"<>|\u0000-\u001f]+/g, " ").replace(/\s+/g, " ").trim().slice(0, 120) || "document";
|
|
2824
|
+
async function exportDocument(source, format, options = {}) {
|
|
2825
|
+
const tree = parseDocument(source, options);
|
|
2826
|
+
const meta = DOCUMENT_FORMATS.find((f) => f.format === format);
|
|
2827
|
+
if (!meta) throw new Error(`Unknown document format "${format}". Use one of: ${DOCUMENT_FORMATS.map((f) => f.format).join(", ")}.`);
|
|
2828
|
+
const fileName = `${safeName(options.fileName ?? tree.settings.title ?? "document")}.${meta.extension}`;
|
|
2829
|
+
const enc = new TextEncoder();
|
|
2830
|
+
let bytes;
|
|
2831
|
+
let pageCount;
|
|
2832
|
+
if (format === "markdown") {
|
|
2833
|
+
bytes = enc.encode(source);
|
|
2834
|
+
} else {
|
|
2835
|
+
const images = await resolveDocumentImages(tree, options.imageLoader, options.onNotice);
|
|
2836
|
+
if (format === "html") bytes = enc.encode(documentToHtml(tree, { images }));
|
|
2837
|
+
else if (format === "pdf") {
|
|
2838
|
+
const r = await documentToPdf(tree, images);
|
|
2839
|
+
bytes = r.bytes;
|
|
2840
|
+
pageCount = r.pageCount;
|
|
2841
|
+
} else if (format === "docx") {
|
|
2842
|
+
const pages = tree.toc.length ? await headingPages(tree, images) : void 0;
|
|
2843
|
+
bytes = await documentToDocx(tree, { images, headingPages: pages });
|
|
2844
|
+
} else bytes = await documentToEpub(tree, { images });
|
|
2845
|
+
}
|
|
2846
|
+
return { format, bytes, mime: meta.mime, fileName, pageCount, tree, notices: tree.notices };
|
|
2847
|
+
}
|
|
2848
|
+
function downloadDocumentExport(exp) {
|
|
2849
|
+
const blob = new Blob([exp.bytes], { type: exp.mime });
|
|
2850
|
+
const url = URL.createObjectURL(blob);
|
|
2851
|
+
const a = document.createElement("a");
|
|
2852
|
+
a.href = url;
|
|
2853
|
+
a.download = exp.fileName;
|
|
2854
|
+
a.rel = "noopener";
|
|
2855
|
+
document.body.appendChild(a);
|
|
2856
|
+
a.click();
|
|
2857
|
+
a.remove();
|
|
2858
|
+
setTimeout(() => URL.revokeObjectURL(url), 3e4);
|
|
2859
|
+
}
|
|
2860
|
+
//# sourceMappingURL=document.cjs.map
|