@masumdev/markforge 0.3.0 → 0.4.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/dist/index.js CHANGED
@@ -39,6 +39,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
39
39
  var src_exports = {};
40
40
  __export(src_exports, {
41
41
  DEFAULT_CONFIG: () => DEFAULT_CONFIG,
42
+ KATEX_INLINE_CSS: () => KATEX_INLINE_CSS,
42
43
  MARKFORGE_VERSION: () => MARKFORGE_VERSION,
43
44
  Orientation: () => Orientation,
44
45
  OutputFormat: () => OutputFormat,
@@ -51,6 +52,7 @@ __export(src_exports, {
51
52
  THEME_DEFAULT: () => THEME_DEFAULT,
52
53
  Theme: () => Theme,
53
54
  WatermarkPosition: () => WatermarkPosition,
55
+ applyHeadingNumbering: () => applyHeadingNumbering,
54
56
  buildDocxDocument: () => buildDocxDocument,
55
57
  buildHtmlDocument: () => buildHtmlDocument,
56
58
  buildPdfDocument: () => buildPdfDocument,
@@ -67,19 +69,29 @@ __export(src_exports, {
67
69
  inlineHtmlImages: () => inlineHtmlImages,
68
70
  loadConfig: () => loadConfig,
69
71
  markforge: () => compileMarkdown,
72
+ normalizeBackCover: () => normalizeBackCover,
73
+ normalizeCoverPage: () => normalizeCoverPage,
70
74
  normalizeHeaderFooter: () => normalizeHeaderFooter,
71
75
  normalizeHeaderFooterSlot: () => normalizeHeaderFooterSlot,
76
+ normalizeNumberHeadings: () => normalizeNumberHeadings,
77
+ normalizeSecurity: () => normalizeSecurity,
72
78
  normalizeSignatures: () => normalizeSignatures,
73
79
  normalizeWatermark: () => normalizeWatermark,
74
80
  parseInlineSpans: () => parseInlineSpans,
75
81
  parseMarginToTwip: () => parseMarginToTwip2,
82
+ parseMarkdown: () => parseMarkdownDocument,
76
83
  parseMarkdownDocument: () => parseMarkdownDocument,
84
+ renderBackCoverHtml: () => renderBackCoverHtml,
85
+ renderCoverPageHtml: () => renderCoverPageHtml,
77
86
  renderInlinesToHtml: () => renderInlinesToHtml,
87
+ renderMathToHtml: () => renderMathToHtml,
78
88
  renderMermaidToPng: () => renderMermaidToPng,
89
+ renderNodesToHtml: () => renderNodesToHtml,
79
90
  replaceDocumentTokens: () => replaceDocumentTokens,
80
91
  resolveDocumentConfig: () => resolveDocumentConfig,
81
92
  resolveImage: () => resolveImage,
82
93
  slugify: () => slugify,
94
+ startPreviewServer: () => startPreviewServer,
83
95
  tokenizeCodeLine: () => tokenizeCodeLine
84
96
  });
85
97
  module.exports = __toCommonJS(src_exports);
@@ -120,6 +132,26 @@ function parseInlineSpans(text) {
120
132
  remaining = remaining.slice(imgMatch[0].length);
121
133
  continue;
122
134
  }
135
+ const fnMatch = remaining.match(/^\[\^([\w-]+)\]/);
136
+ if (fnMatch) {
137
+ const fnId = fnMatch[1];
138
+ spans.push({
139
+ type: "footnoteRef",
140
+ content: fnId,
141
+ footnoteId: fnId
142
+ });
143
+ remaining = remaining.slice(fnMatch[0].length);
144
+ continue;
145
+ }
146
+ const mathMatch = remaining.match(/^\$([^$\n]+?)\$/);
147
+ if (mathMatch && !mathMatch[1].startsWith("$")) {
148
+ spans.push({
149
+ type: "mathInline",
150
+ content: mathMatch[1]
151
+ });
152
+ remaining = remaining.slice(mathMatch[0].length);
153
+ continue;
154
+ }
123
155
  const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/);
124
156
  if (linkMatch) {
125
157
  spans.push({
@@ -219,7 +251,7 @@ function parseInlineSpans(text) {
219
251
  remaining = remaining.slice(fullTag.length);
220
252
  continue;
221
253
  }
222
- const nextSpecial = remaining.search(/[\*\_\[\!`~<]/);
254
+ const nextSpecial = remaining.search(/[\*\_\[\!`~<\$]/);
223
255
  if (nextSpecial === -1) {
224
256
  spans.push({
225
257
  type: "text",
@@ -240,11 +272,51 @@ function parseInlineSpans(text) {
240
272
  remaining = remaining.slice(nextSpecial);
241
273
  }
242
274
  }
243
- return spans;
275
+ const merged = [];
276
+ for (const s of spans) {
277
+ const prev = merged[merged.length - 1];
278
+ if (prev && prev.type === "text" && s.type === "text") {
279
+ prev.content += s.content;
280
+ } else {
281
+ merged.push(s);
282
+ }
283
+ }
284
+ return merged;
244
285
  }
245
286
  function slugify(text) {
246
287
  return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
247
288
  }
289
+ function applyHeadingNumbering(nodes, tocEntries, options) {
290
+ const depth = options.depth ?? 3;
291
+ const skipH1 = options.skipH1 ?? false;
292
+ const prefix = options.prefix ?? "";
293
+ const counters = [0, 0, 0, 0, 0, 0];
294
+ for (const node of nodes) {
295
+ if (node.type === "heading" && node.level) {
296
+ if (node._numbered) continue;
297
+ const lvl = node.level;
298
+ if (lvl > depth) continue;
299
+ if (lvl === 1 && skipH1) continue;
300
+ const idx = lvl - 1;
301
+ counters[idx]++;
302
+ for (let c = idx + 1; c < counters.length; c++) {
303
+ counters[c] = 0;
304
+ }
305
+ const startIdx = skipH1 ? 1 : 0;
306
+ const parts = counters.slice(startIdx, idx + 1).filter((n) => n > 0);
307
+ const numberStr = parts.join(".") + ".";
308
+ const fullPrefix = prefix ? `${prefix} ${numberStr} ` : `${numberStr} `;
309
+ const originalText = node.text || "";
310
+ node.text = fullPrefix + originalText;
311
+ node.inlines = parseInlineSpans(node.text);
312
+ node._numbered = true;
313
+ const toc = tocEntries.find((t) => t.id === node.id);
314
+ if (toc) {
315
+ toc.text = node.text;
316
+ }
317
+ }
318
+ }
319
+ }
248
320
  function parseMarkdownDocument(rawMarkdown) {
249
321
  const { data: frontmatter, content } = (0, import_gray_matter.default)(rawMarkdown);
250
322
  const metadata = frontmatter || {};
@@ -256,6 +328,7 @@ function parseMarkdownDocument(rawMarkdown) {
256
328
  const lines = cleanContent.split(/\r?\n/);
257
329
  const nodes = [];
258
330
  const tocEntries = [];
331
+ const footnoteDefs = [];
259
332
  let i = 0;
260
333
  while (i < lines.length) {
261
334
  const line = lines[i];
@@ -284,6 +357,29 @@ function parseMarkdownDocument(rawMarkdown) {
284
357
  i++;
285
358
  continue;
286
359
  }
360
+ if (line.trim().startsWith("$$")) {
361
+ const mathLines = [];
362
+ const singleLine = line.trim().match(/^\$\$(.+)\$\$$/);
363
+ if (singleLine) {
364
+ nodes.push({
365
+ type: "mathBlock",
366
+ text: singleLine[1].trim()
367
+ });
368
+ i++;
369
+ continue;
370
+ }
371
+ i++;
372
+ while (i < lines.length && !lines[i].trim().startsWith("$$")) {
373
+ mathLines.push(lines[i]);
374
+ i++;
375
+ }
376
+ if (i < lines.length) i++;
377
+ nodes.push({
378
+ type: "mathBlock",
379
+ text: mathLines.join("\n").trim()
380
+ });
381
+ continue;
382
+ }
287
383
  const codeBlockMatch = line.match(/^```(\w+)?/);
288
384
  if (codeBlockMatch) {
289
385
  const language = (codeBlockMatch[1] || "text").trim().toLowerCase();
@@ -310,6 +406,100 @@ function parseMarkdownDocument(rawMarkdown) {
310
406
  }
311
407
  continue;
312
408
  }
409
+ const colsMatch = line.trim().match(/^:::columns(?:\s+\[?([\w\s=.-]+)\]?)?$/i);
410
+ if (colsMatch) {
411
+ const attrStr = colsMatch[1] || "";
412
+ let colsCount = 2;
413
+ let colGap = "1.5rem";
414
+ if (attrStr) {
415
+ const numMatch = attrStr.trim().match(/^(\d+)$/);
416
+ const cMatch = attrStr.match(/cols=(\d+)/i) || attrStr.match(/columns=(\d+)/i);
417
+ const gMatch = attrStr.match(/gap=([^\s]+)/i);
418
+ if (numMatch) colsCount = parseInt(numMatch[1], 10);
419
+ else if (cMatch) colsCount = parseInt(cMatch[1], 10);
420
+ if (gMatch) colGap = gMatch[1];
421
+ }
422
+ const columnNodes = [];
423
+ let currentColumnLines = [];
424
+ let inColBlock = false;
425
+ i++;
426
+ while (i < lines.length) {
427
+ const curLine = lines[i];
428
+ const trimmed = curLine.trim();
429
+ if (/^:::col(?:umn)?$/i.test(trimmed)) {
430
+ if (currentColumnLines.length > 0) {
431
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
432
+ columnNodes.push({
433
+ type: "column",
434
+ children: subDoc.nodes
435
+ });
436
+ currentColumnLines = [];
437
+ }
438
+ inColBlock = true;
439
+ i++;
440
+ } else if (trimmed === ":::") {
441
+ if (inColBlock) {
442
+ if (currentColumnLines.length > 0) {
443
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
444
+ columnNodes.push({
445
+ type: "column",
446
+ children: subDoc.nodes
447
+ });
448
+ currentColumnLines = [];
449
+ }
450
+ inColBlock = false;
451
+ i++;
452
+ } else {
453
+ if (currentColumnLines.length > 0) {
454
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
455
+ columnNodes.push({
456
+ type: "column",
457
+ children: subDoc.nodes
458
+ });
459
+ currentColumnLines = [];
460
+ }
461
+ i++;
462
+ break;
463
+ }
464
+ } else {
465
+ currentColumnLines.push(curLine);
466
+ i++;
467
+ }
468
+ }
469
+ if (currentColumnLines.length > 0) {
470
+ const subDoc = parseMarkdownDocument(currentColumnLines.join("\n"));
471
+ columnNodes.push({
472
+ type: "column",
473
+ children: subDoc.nodes
474
+ });
475
+ }
476
+ nodes.push({
477
+ type: "columns",
478
+ columnsCount: columnNodes.length > 0 ? columnNodes.length : colsCount,
479
+ columnGap: colGap,
480
+ children: columnNodes
481
+ });
482
+ continue;
483
+ }
484
+ const fnDefMatch = line.match(/^\[\^([\w-]+)\]:\s+(.+)$/);
485
+ if (fnDefMatch) {
486
+ const fnId = fnDefMatch[1];
487
+ const fnText = fnDefMatch[2].trim();
488
+ const inlines = parseInlineSpans(fnText);
489
+ footnoteDefs.push({
490
+ id: fnId,
491
+ text: fnText,
492
+ inlines
493
+ });
494
+ nodes.push({
495
+ type: "footnoteDef",
496
+ footnoteId: fnId,
497
+ text: fnText,
498
+ inlines
499
+ });
500
+ i++;
501
+ continue;
502
+ }
313
503
  const calloutMatch = line.match(/^>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/i);
314
504
  if (calloutMatch) {
315
505
  const calloutType = calloutMatch[1].toUpperCase();
@@ -433,7 +623,7 @@ function parseMarkdownDocument(rawMarkdown) {
433
623
  continue;
434
624
  }
435
625
  const paraLines = [];
436
- while (i < lines.length && lines[i].trim() && !lines[i].match(/^#{1,6}\s+/) && !lines[i].startsWith("```") && !lines[i].startsWith(">") && !lines[i].trim().startsWith("|") && !lines[i].match(/^(\s*)([-*+]|\d+\.)\s+/)) {
626
+ while (i < lines.length && lines[i].trim() && !lines[i].match(/^#{1,6}\s+/) && !lines[i].startsWith("```") && !lines[i].startsWith("$$") && !lines[i].startsWith(">") && !lines[i].trim().startsWith("|") && !lines[i].match(/^:::columns/) && !lines[i].match(/^\[\^[\w-]+\]:\s+/) && !lines[i].match(/^(\s*)([-*+]|\d+\.)\s+/)) {
437
627
  paraLines.push(lines[i]);
438
628
  i++;
439
629
  }
@@ -444,12 +634,19 @@ function parseMarkdownDocument(rawMarkdown) {
444
634
  inlines: parseInlineSpans(paraText)
445
635
  });
446
636
  }
637
+ if (metadata.numberHeadings) {
638
+ const opts = typeof metadata.numberHeadings === "boolean" ? { enabled: metadata.numberHeadings } : metadata.numberHeadings;
639
+ if (opts.enabled !== false) {
640
+ applyHeadingNumbering(nodes, tocEntries, opts);
641
+ }
642
+ }
447
643
  return {
448
644
  metadata,
449
645
  content: cleanContent,
450
646
  nodes,
451
647
  tocEntries,
452
- inlinedStyles
648
+ inlinedStyles,
649
+ footnoteDefs
453
650
  };
454
651
  }
455
652
 
@@ -515,9 +712,14 @@ async function resolveImage(src, baseDir = process.cwd()) {
515
712
  memoryImageCache.set(cacheKey, resolved2);
516
713
  return resolved2;
517
714
  }
518
- const localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
715
+ let localPath = path.isAbsolute(src) ? src : path.resolve(baseDir, src);
519
716
  if (!fs.existsSync(localPath)) {
520
- return null;
717
+ const cwdPath = path.resolve(process.cwd(), src);
718
+ if (fs.existsSync(cwdPath)) {
719
+ localPath = cwdPath;
720
+ } else {
721
+ return null;
722
+ }
521
723
  }
522
724
  const buffer = fs.readFileSync(localPath);
523
725
  const mimeType = getMimeType(localPath);
@@ -922,6 +1124,8 @@ var path4 = __toESM(require("path"));
922
1124
  var os = __toESM(require("os"));
923
1125
  var import_node_url2 = require("url");
924
1126
  var import_node_child_process = require("child_process");
1127
+ var import_pdf_lib = require("pdf-lib");
1128
+ var import_pdf_encrypt = require("@pdfsmaller/pdf-encrypt");
925
1129
 
926
1130
  // src/core/html/htmlBuilder.ts
927
1131
  var fs3 = __toESM(require("fs"));
@@ -993,7 +1197,7 @@ var THEME_COMPONENTS = `
993
1197
  .document-meta { font-size: 0.9rem; color: var(--mf-text-muted); display: flex; gap: 1.5rem; flex-wrap: wrap; }
994
1198
 
995
1199
  /* Table of Contents */
996
- .table-of-contents { background: var(--mf-card-bg); border: 1px solid var(--mf-border); border-radius: 8px; padding: 1.5rem 2rem; margin: 2rem 0; }
1200
+ .table-of-contents { background: var(--mf-card-bg); border: 1px solid var(--mf-border); border-radius: 8px; padding: 1.5rem 2rem; margin: 2rem 0; page-break-after: always; break-after: page; }
997
1201
  .table-of-contents h2 { font-size: 1rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--mf-text-muted); margin: 0 0 1rem 0; }
998
1202
  .table-of-contents ul { list-style: none; padding: 0; margin: 0; }
999
1203
  .table-of-contents li { padding: 0.25rem 0; }
@@ -1066,11 +1270,13 @@ var THEME_CORPORATE = `
1066
1270
  }
1067
1271
  body { background-color: var(--mf-bg); color: var(--mf-text); font-family: var(--mf-font-family); font-size: 15px; line-height: 1.65; margin: 0; padding: 2.5rem; }
1068
1272
  .document-container { max-width: 860px; margin: 0 auto; position: relative; z-index: 1; }
1069
- h1, h2, h3, h4, h5, h6 { color: var(--mf-text); font-weight: 700; margin-top: 1.8rem; margin-bottom: 0.8rem; line-height: 1.25; }
1070
- h1 { font-size: 2.2rem; border-bottom: 2px solid var(--mf-primary); padding-bottom: 0.5rem; }
1273
+ h1, h2, h3, h4, h5, h6 { color: var(--mf-primary-dark); font-weight: 700; margin-top: 1.8rem; margin-bottom: 0.8rem; line-height: 1.25; }
1274
+ h1 { font-size: 2.2rem; color: var(--mf-primary-dark); border-bottom: 2.5px solid var(--mf-primary); padding-bottom: 0.5rem; }
1071
1275
  h2 { font-size: 1.6rem; color: var(--mf-primary-dark); border-bottom: 1px solid #CCFBF1; padding-bottom: 0.4rem; }
1072
- h3 { font-size: 1.3rem; }
1073
- h4 { font-size: 1.1rem; }
1276
+ h3 { font-size: 1.3rem; color: var(--mf-primary-dark); }
1277
+ h4 { font-size: 1.1rem; color: var(--mf-primary-dark); }
1278
+ h5 { font-size: 1.0rem; color: var(--mf-primary-dark); }
1279
+ h6 { font-size: 0.9rem; color: var(--mf-primary-dark); }
1074
1280
  p { margin: 0.8rem 0; }
1075
1281
  `;
1076
1282
  var THEME_DEFAULT = THEME_CORPORATE;
@@ -1112,11 +1318,13 @@ function generateThemeCss(theme) {
1112
1318
  }
1113
1319
  body { background-color: var(--mf-bg); color: var(--mf-text); font-family: var(--mf-font-family); font-size: 15px; line-height: 1.65; margin: 0; padding: 2.5rem; }
1114
1320
  .document-container { max-width: 860px; margin: 0 auto; position: relative; z-index: 1; }
1115
- h1, h2, h3, h4, h5, h6 { color: var(--mf-text); font-weight: 700; margin-top: 1.8rem; margin-bottom: 0.8rem; line-height: 1.25; }
1116
- h1 { font-size: 2.2rem; border-bottom: 2px solid var(--mf-primary); padding-bottom: 0.5rem; }
1321
+ h1, h2, h3, h4, h5, h6 { color: var(--mf-primary-dark); font-weight: 700; margin-top: 1.8rem; margin-bottom: 0.8rem; line-height: 1.25; }
1322
+ h1 { font-size: 2.2rem; color: var(--mf-primary-dark); border-bottom: 2.5px solid var(--mf-primary); padding-bottom: 0.5rem; }
1117
1323
  h2 { font-size: 1.6rem; color: var(--mf-primary-dark); border-bottom: 1px solid var(--mf-border); padding-bottom: 0.4rem; }
1118
- h3 { font-size: 1.3rem; }
1119
- h4 { font-size: 1.1rem; }
1324
+ h3 { font-size: 1.3rem; color: var(--mf-primary-dark); }
1325
+ h4 { font-size: 1.1rem; color: var(--mf-primary-dark); }
1326
+ h5 { font-size: 1.0rem; color: var(--mf-primary-dark); }
1327
+ h6 { font-size: 0.9rem; color: var(--mf-primary-dark); }
1120
1328
  p { margin: 0.8rem 0; }
1121
1329
  ${theme.customCss || ""}
1122
1330
  `;
@@ -1164,6 +1372,12 @@ var DEFAULT_CONFIG = {
1164
1372
  },
1165
1373
  toc: false,
1166
1374
  watermark: void 0,
1375
+ signatures: void 0,
1376
+ coverPage: void 0,
1377
+ backCover: void 0,
1378
+ numberHeadings: void 0,
1379
+ security: void 0,
1380
+ math: true,
1167
1381
  embedImages: true,
1168
1382
  metadata: void 0,
1169
1383
  watch: false,
@@ -1319,15 +1533,45 @@ function formatMarginCss(margin, defaultCss = "2.5cm") {
1319
1533
  if (/^[0-9.]+$/.test(str)) return `${str}pt`;
1320
1534
  return str;
1321
1535
  }
1322
- function replaceDocumentTokens(template = "", meta) {
1323
- return template.replace(/\{title\}/gi, meta.title || "").replace(/\{subtitle\}/gi, meta.subtitle || "").replace(/\{author\}/gi, meta.author || "").replace(/\{version\}/gi, meta.version || "").replace(/\{date\}/gi, meta.date || "").replace(/\{company\}/gi, meta.company || "");
1536
+ function replaceDocumentTokens(template = "", meta = {}) {
1537
+ if (!template) return "";
1538
+ const currentYear = meta.year ? String(meta.year) : (/* @__PURE__ */ new Date()).getFullYear().toString();
1539
+ const tokenMap = {
1540
+ title: meta.title ? String(meta.title) : "",
1541
+ subtitle: meta.subtitle ? String(meta.subtitle) : "",
1542
+ author: meta.author ? Array.isArray(meta.author) ? meta.author.join(", ") : String(meta.author) : "",
1543
+ version: meta.version ? String(meta.version) : "",
1544
+ date: meta.date ? String(meta.date) : "",
1545
+ company: meta.company ? String(meta.company) : "",
1546
+ year: currentYear
1547
+ };
1548
+ if (meta.metadata && typeof meta.metadata === "object") {
1549
+ for (const [key, val] of Object.entries(meta.metadata)) {
1550
+ if (val !== void 0 && val !== null) {
1551
+ tokenMap[key.toLowerCase()] = String(val);
1552
+ }
1553
+ }
1554
+ }
1555
+ for (const [key, val] of Object.entries(meta)) {
1556
+ if (val !== void 0 && val !== null && typeof val !== "object") {
1557
+ tokenMap[key.toLowerCase()] = String(val);
1558
+ }
1559
+ }
1560
+ return template.replace(/\{([a-zA-Z0-9_\-]+)\}/gi, (match, tokenKey) => {
1561
+ const lowerKey = tokenKey.toLowerCase();
1562
+ if (lowerKey in tokenMap) {
1563
+ return tokenMap[lowerKey];
1564
+ }
1565
+ return match;
1566
+ });
1324
1567
  }
1325
- function normalizeWatermark(rawWatermark) {
1568
+ function normalizeWatermark(rawWatermark, tokens) {
1326
1569
  if (!rawWatermark) {
1327
1570
  return void 0;
1328
1571
  }
1329
1572
  if (typeof rawWatermark === "string") {
1330
- const text = rawWatermark.trim();
1573
+ let text = rawWatermark.trim();
1574
+ if (tokens) text = replaceDocumentTokens(text, tokens);
1331
1575
  if (!text) return void 0;
1332
1576
  return {
1333
1577
  text,
@@ -1340,8 +1584,10 @@ function normalizeWatermark(rawWatermark) {
1340
1584
  }
1341
1585
  if (typeof rawWatermark === "object") {
1342
1586
  if (!rawWatermark.text || !rawWatermark.text.trim()) return void 0;
1587
+ let text = rawWatermark.text.trim();
1588
+ if (tokens) text = replaceDocumentTokens(text, tokens);
1343
1589
  return {
1344
- text: rawWatermark.text.trim(),
1590
+ text,
1345
1591
  color: rawWatermark.color || "#94a3b8",
1346
1592
  opacity: typeof rawWatermark.opacity === "number" ? rawWatermark.opacity : 0.08,
1347
1593
  fontSize: rawWatermark.fontSize || 54,
@@ -1457,6 +1703,174 @@ function normalizeSignatures(raw, meta = {}) {
1457
1703
  spacingBeforeTwip
1458
1704
  };
1459
1705
  }
1706
+ function normalizeCoverPage(rawCover, tokenCtx = {}) {
1707
+ if (!rawCover) return void 0;
1708
+ const cfg = typeof rawCover === "object" ? rawCover : {};
1709
+ if (cfg.enabled === false) return void 0;
1710
+ const preset = cfg.preset || "modern";
1711
+ const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : tokenCtx.title ? String(tokenCtx.title) : "Document Title";
1712
+ const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : tokenCtx.subtitle ? String(tokenCtx.subtitle) : void 0;
1713
+ const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author ? String(tokenCtx.author) : void 0;
1714
+ const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company ? String(tokenCtx.company) : void 0;
1715
+ const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version ? String(tokenCtx.version) : void 0;
1716
+ let dateStr;
1717
+ if (typeof cfg.date === "string") {
1718
+ dateStr = replaceDocumentTokens(cfg.date, tokenCtx);
1719
+ } else if (cfg.date === true) {
1720
+ dateStr = tokenCtx.date ? String(tokenCtx.date) : (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1721
+ } else {
1722
+ dateStr = tokenCtx.date ? String(tokenCtx.date) : void 0;
1723
+ }
1724
+ const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
1725
+ const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
1726
+ const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
1727
+ const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
1728
+ const logoWidth = cfg.logoWidth;
1729
+ const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1730
+ const backgroundColor = typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1731
+ const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
1732
+ const titleColor = typeof cfg.titleColor === "string" ? cfg.titleColor : void 0;
1733
+ const subtitleColor = typeof cfg.subtitleColor === "string" ? cfg.subtitleColor : void 0;
1734
+ const accentColor = typeof cfg.accentColor === "string" ? cfg.accentColor : void 0;
1735
+ const footerText = cfg.footerText ? replaceDocumentTokens(String(cfg.footerText), tokenCtx) : void 0;
1736
+ const address = cfg.address ? replaceDocumentTokens(String(cfg.address), tokenCtx) : void 0;
1737
+ const email = cfg.email ? replaceDocumentTokens(String(cfg.email), tokenCtx) : void 0;
1738
+ const phone = cfg.phone ? replaceDocumentTokens(String(cfg.phone), tokenCtx) : void 0;
1739
+ const website = cfg.website ? replaceDocumentTokens(String(cfg.website), tokenCtx) : void 0;
1740
+ let socialMap;
1741
+ if (cfg.social && typeof cfg.social === "object") {
1742
+ socialMap = {};
1743
+ for (const [k, v] of Object.entries(cfg.social)) {
1744
+ if (typeof v === "string") {
1745
+ socialMap[k] = replaceDocumentTokens(v, tokenCtx);
1746
+ }
1747
+ }
1748
+ }
1749
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
1750
+ const copyright = cfg.copyright ? replaceDocumentTokens(String(cfg.copyright), { ...tokenCtx, year: currentYear }) : void 0;
1751
+ return {
1752
+ enabled: true,
1753
+ preset,
1754
+ title,
1755
+ subtitle,
1756
+ author,
1757
+ company,
1758
+ version,
1759
+ date: dateStr,
1760
+ badge,
1761
+ badgeColor,
1762
+ badgeTextColor,
1763
+ logo,
1764
+ logoWidth,
1765
+ bgGradient,
1766
+ backgroundColor,
1767
+ textColor,
1768
+ titleColor,
1769
+ subtitleColor,
1770
+ accentColor,
1771
+ footerText,
1772
+ address,
1773
+ email,
1774
+ phone,
1775
+ website,
1776
+ social: socialMap,
1777
+ copyright
1778
+ };
1779
+ }
1780
+ function normalizeBackCover(rawBack, tokenCtx = {}) {
1781
+ if (!rawBack) return void 0;
1782
+ const cfg = typeof rawBack === "object" ? rawBack : {};
1783
+ if (cfg.enabled === false) return void 0;
1784
+ const preset = cfg.preset || "modern";
1785
+ const title = cfg.title ? replaceDocumentTokens(String(cfg.title), tokenCtx) : "Thank You";
1786
+ const subtitle = cfg.subtitle ? replaceDocumentTokens(String(cfg.subtitle), tokenCtx) : void 0;
1787
+ const author = Array.isArray(cfg.author) ? cfg.author.join(", ") : cfg.author ? replaceDocumentTokens(String(cfg.author), tokenCtx) : tokenCtx.author ? String(tokenCtx.author) : void 0;
1788
+ const company = cfg.company ? replaceDocumentTokens(String(cfg.company), tokenCtx) : tokenCtx.company ? String(tokenCtx.company) : void 0;
1789
+ const version = cfg.version ? replaceDocumentTokens(String(cfg.version), tokenCtx) : tokenCtx.version ? String(tokenCtx.version) : void 0;
1790
+ const date = typeof cfg.date === "string" ? replaceDocumentTokens(cfg.date, tokenCtx) : tokenCtx.date ? String(tokenCtx.date) : void 0;
1791
+ const address = cfg.address ? replaceDocumentTokens(String(cfg.address), tokenCtx) : void 0;
1792
+ const email = cfg.email ? replaceDocumentTokens(String(cfg.email), tokenCtx) : void 0;
1793
+ const phone = cfg.phone ? replaceDocumentTokens(String(cfg.phone), tokenCtx) : void 0;
1794
+ const website = cfg.website ? replaceDocumentTokens(String(cfg.website), tokenCtx) : void 0;
1795
+ let socialMap;
1796
+ if (cfg.social && typeof cfg.social === "object") {
1797
+ socialMap = {};
1798
+ for (const [k, v] of Object.entries(cfg.social)) {
1799
+ if (typeof v === "string") {
1800
+ socialMap[k] = replaceDocumentTokens(v, tokenCtx);
1801
+ }
1802
+ }
1803
+ }
1804
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
1805
+ const copyright = cfg.copyright ? replaceDocumentTokens(String(cfg.copyright), { ...tokenCtx, year: currentYear }) : company ? `Copyright (c) ${currentYear} ${company}. All Rights Reserved.` : void 0;
1806
+ const footerText = cfg.footerText ? replaceDocumentTokens(String(cfg.footerText), tokenCtx) : void 0;
1807
+ const badge = cfg.badge ? replaceDocumentTokens(String(cfg.badge), tokenCtx) : void 0;
1808
+ const badgeColor = typeof cfg.badgeColor === "string" ? cfg.badgeColor : void 0;
1809
+ const badgeTextColor = typeof cfg.badgeTextColor === "string" ? cfg.badgeTextColor : void 0;
1810
+ const logo = typeof cfg.logo === "string" ? cfg.logo : void 0;
1811
+ const logoWidth = cfg.logoWidth;
1812
+ const bgGradient = typeof cfg.bgGradient === "string" ? cfg.bgGradient : typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1813
+ const backgroundColor = typeof cfg.backgroundColor === "string" ? cfg.backgroundColor : void 0;
1814
+ const textColor = typeof cfg.textColor === "string" ? cfg.textColor : void 0;
1815
+ const titleColor = typeof cfg.titleColor === "string" ? cfg.titleColor : void 0;
1816
+ const subtitleColor = typeof cfg.subtitleColor === "string" ? cfg.subtitleColor : void 0;
1817
+ const accentColor = typeof cfg.accentColor === "string" ? cfg.accentColor : void 0;
1818
+ return {
1819
+ enabled: true,
1820
+ preset,
1821
+ title,
1822
+ subtitle,
1823
+ author,
1824
+ company,
1825
+ version,
1826
+ date,
1827
+ address,
1828
+ email,
1829
+ phone,
1830
+ website,
1831
+ social: socialMap,
1832
+ copyright,
1833
+ footerText,
1834
+ badge,
1835
+ badgeColor,
1836
+ badgeTextColor,
1837
+ logo,
1838
+ logoWidth,
1839
+ bgGradient,
1840
+ backgroundColor,
1841
+ textColor,
1842
+ titleColor,
1843
+ subtitleColor,
1844
+ accentColor
1845
+ };
1846
+ }
1847
+ function normalizeNumberHeadings(raw) {
1848
+ if (raw === void 0 || raw === false) return void 0;
1849
+ if (raw === true) {
1850
+ return { enabled: true, depth: 3, skipH1: false, prefix: "" };
1851
+ }
1852
+ if (typeof raw === "object") {
1853
+ const obj = raw;
1854
+ if (obj.enabled === false) return void 0;
1855
+ return {
1856
+ enabled: true,
1857
+ depth: obj.depth ?? 3,
1858
+ skipH1: obj.skipH1 ?? false,
1859
+ prefix: obj.prefix ?? ""
1860
+ };
1861
+ }
1862
+ return void 0;
1863
+ }
1864
+ function normalizeSecurity(raw) {
1865
+ if (!raw) return void 0;
1866
+ const sec = raw;
1867
+ if (!sec.userPassword && !sec.ownerPassword && !sec.permissions) return void 0;
1868
+ return {
1869
+ userPassword: sec.userPassword,
1870
+ ownerPassword: sec.ownerPassword,
1871
+ permissions: sec.permissions
1872
+ };
1873
+ }
1460
1874
  function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1461
1875
  const configMeta = userConfig.metadata || {};
1462
1876
  const mergedMeta = { ...configMeta, ...frontmatter };
@@ -1467,7 +1881,15 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1467
1881
  const version = mergedMeta.version || void 0;
1468
1882
  const company = mergedMeta.company || void 0;
1469
1883
  const lang = mergedMeta.lang || "en";
1470
- const tokenContext = { title, subtitle, author, version, date, company };
1884
+ const tokenContext = {
1885
+ ...mergedMeta,
1886
+ title,
1887
+ subtitle,
1888
+ author,
1889
+ version,
1890
+ date,
1891
+ company
1892
+ };
1471
1893
  const theme = mergedMeta.theme || userConfig.theme || DEFAULT_CONFIG.theme;
1472
1894
  const orientation = mergedMeta.orientation || userConfig.orientation || DEFAULT_CONFIG.orientation;
1473
1895
  const paperSize = mergedMeta.paperSize || userConfig.paperSize || DEFAULT_CONFIG.paperSize;
@@ -1496,9 +1918,18 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1496
1918
  const footer = normalizeHeaderFooter(rawFooter, tokenContext);
1497
1919
  const toc = typeof mergedMeta.toc === "boolean" ? mergedMeta.toc : typeof userConfig.toc === "boolean" ? userConfig.toc : DEFAULT_CONFIG.toc;
1498
1920
  const rawWatermark = mergedMeta.watermark !== void 0 ? mergedMeta.watermark : userConfig.watermark !== void 0 ? userConfig.watermark : DEFAULT_CONFIG.watermark;
1499
- const watermark = normalizeWatermark(rawWatermark);
1921
+ const watermark = normalizeWatermark(rawWatermark, tokenContext);
1500
1922
  const rawSignatures = mergedMeta.signatures || userConfig.signatures;
1501
1923
  const signatures = normalizeSignatures(rawSignatures, tokenContext);
1924
+ const rawCover = mergedMeta.coverPage !== void 0 ? mergedMeta.coverPage : userConfig.coverPage;
1925
+ const coverPage = normalizeCoverPage(rawCover, tokenContext);
1926
+ const rawBack = mergedMeta.backCover !== void 0 ? mergedMeta.backCover : userConfig.backCover;
1927
+ const backCover = normalizeBackCover(rawBack, tokenContext);
1928
+ const rawNumberHeadings = mergedMeta.numberHeadings !== void 0 ? mergedMeta.numberHeadings : userConfig.numberHeadings;
1929
+ const numberHeadings = normalizeNumberHeadings(rawNumberHeadings);
1930
+ const rawSecurity = mergedMeta.security || userConfig.security;
1931
+ const security = normalizeSecurity(rawSecurity);
1932
+ const math = mergedMeta.math !== false && userConfig.math !== false;
1502
1933
  const cssList = [];
1503
1934
  const addCss = (item) => {
1504
1935
  if (!item) return;
@@ -1528,6 +1959,11 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1528
1959
  toc,
1529
1960
  signatures,
1530
1961
  watermark,
1962
+ coverPage,
1963
+ backCover,
1964
+ numberHeadings,
1965
+ security,
1966
+ math,
1531
1967
  css: cssList,
1532
1968
  embedImages,
1533
1969
  bundleHtml,
@@ -1535,11 +1971,50 @@ function resolveDocumentConfig(frontmatter = {}, userConfig = {}) {
1535
1971
  };
1536
1972
  }
1537
1973
 
1974
+ // src/core/math/mathRenderer.ts
1975
+ var import_katex = __toESM(require("katex"));
1976
+ function renderMathToHtml(latex, displayMode = false) {
1977
+ try {
1978
+ return import_katex.default.renderToString(latex.trim(), {
1979
+ displayMode,
1980
+ throwOnError: false,
1981
+ output: "htmlAndMathml",
1982
+ strict: false
1983
+ });
1984
+ } catch {
1985
+ return `<span class="katex-fallback">${latex}</span>`;
1986
+ }
1987
+ }
1988
+ var KATEX_INLINE_CSS = `
1989
+ .katex { font: normal 1.21em KaTeX_Main, Times New Roman, serif; line-height: 1.2; text-indent: 0; text-rendering: auto; border-color: currentColor; }
1990
+ .katex * { -ms-high-contrast-adjust: none !important; }
1991
+ .katex .katex-html { display: inline-block; }
1992
+ .katex .katex-mathml { clip: rect(1px, 1px, 1px, 1px); border: 0; height: 1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
1993
+ .katex-display { display: block; margin: 1em 0; text-align: center; }
1994
+ .katex-display > .katex { display: inline-block; text-align: initial; }
1995
+ .katex .base { position: relative; white-space: nowrap; width: min-content; }
1996
+ .katex .strut { display: inline-block; }
1997
+ .katex .mord { display: inline-block; }
1998
+ .katex .mbin { display: inline-block; }
1999
+ .katex .mrel { display: inline-block; }
2000
+ .katex .mopen { display: inline-block; }
2001
+ .katex .mclose { display: inline-block; }
2002
+ .katex .mpunct { display: inline-block; }
2003
+ .katex .minner { display: inline-block; }
2004
+ .katex .mop { display: inline-block; }
2005
+ .katex .frac-line { width: 100%; border-bottom-style: solid; }
2006
+ .katex .vlist-t { display: inline-table; table-layout: fixed; }
2007
+ .katex .vlist-r { display: table-row; }
2008
+ .katex .vlist { display: table-cell; vertical-align: bottom; position: relative; }
2009
+ .katex .msupsub { text-align: left; }
2010
+ .katex .sqrt > .root { margin-left: 0.27777778em; margin-right: -0.55555556em; }
2011
+ `;
2012
+
1538
2013
  // src/core/html/htmlBuilder.ts
1539
2014
  function escapeHtml(str) {
1540
2015
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1541
2016
  }
1542
- async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
2017
+ async function renderInlinesToHtml(spans = [], baseDir = process.cwd(), tokens) {
1543
2018
  let result = "";
1544
2019
  for (const span of spans) {
1545
2020
  if (span.type === "image" && span.url) {
@@ -1553,23 +2028,23 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1553
2028
  continue;
1554
2029
  }
1555
2030
  if (span.type === "link" && span.url) {
1556
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
2031
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1557
2032
  const title = span.title ? ` title="${escapeHtml(span.title)}"` : "";
1558
2033
  result += `<a href="${escapeHtml(span.url)}"${title}>${inner}</a>`;
1559
2034
  continue;
1560
2035
  }
1561
2036
  if (span.type === "bold") {
1562
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
2037
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1563
2038
  result += `<strong>${inner}</strong>`;
1564
2039
  continue;
1565
2040
  }
1566
2041
  if (span.type === "italic") {
1567
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
2042
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1568
2043
  result += `<em>${inner}</em>`;
1569
2044
  continue;
1570
2045
  }
1571
2046
  if (span.type === "strikethrough") {
1572
- const inner = span.children ? await renderInlinesToHtml(span.children, baseDir) : escapeHtml(span.content);
2047
+ const inner = span.children ? await renderInlinesToHtml(span.children, baseDir, tokens) : escapeHtml(tokens ? replaceDocumentTokens(span.content, tokens) : span.content);
1573
2048
  result += `<del>${inner}</del>`;
1574
2049
  continue;
1575
2050
  }
@@ -1577,83 +2052,57 @@ async function renderInlinesToHtml(spans = [], baseDir = process.cwd()) {
1577
2052
  result += `<code>${escapeHtml(span.content)}</code>`;
1578
2053
  continue;
1579
2054
  }
2055
+ if (span.type === "mathInline") {
2056
+ result += renderMathToHtml(span.content, false);
2057
+ continue;
2058
+ }
2059
+ if (span.type === "footnoteRef") {
2060
+ const id = escapeHtml(span.footnoteId || span.content);
2061
+ result += `<sup><a href="#fn-${id}" id="fnref-${id}" class="markforge-fnref">[${escapeHtml(span.content)}]</a></sup>`;
2062
+ continue;
2063
+ }
1580
2064
  if (span.type === "htmlInline") {
1581
2065
  result += span.content;
1582
2066
  continue;
1583
2067
  }
1584
- result += escapeHtml(span.content);
2068
+ const content = tokens ? replaceDocumentTokens(span.content, tokens) : span.content;
2069
+ result += escapeHtml(content);
1585
2070
  }
1586
2071
  return result;
1587
2072
  }
1588
- async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
1589
- const resolved = resolveDocumentConfig(doc.metadata, config);
1590
- const baseThemeCss = generateThemeCss(resolved.theme);
1591
- let customCss = "";
1592
- for (const cssPath of resolved.css) {
1593
- const fullCssPath = path3.isAbsolute(cssPath) ? cssPath : path3.resolve(baseDir, cssPath);
1594
- if (fs3.existsSync(fullCssPath)) {
1595
- customCss += `
1596
- /* Custom CSS: ${cssPath} */
1597
- ` + fs3.readFileSync(fullCssPath, "utf-8");
1598
- }
1599
- }
1600
- const inlinedCss = doc.inlinedStyles.join("\n");
2073
+ async function renderNodesToHtml(nodes, resolved, baseDir = process.cwd(), tokens) {
1601
2074
  let bodyHtml = "";
1602
- if (resolved.title) {
1603
- bodyHtml += ` <header class="document-header">
1604
- `;
1605
- bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
1606
- `;
1607
- if (resolved.subtitle) {
1608
- bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
1609
- `;
1610
- }
1611
- if (resolved.author || resolved.date || resolved.version) {
1612
- bodyHtml += ` <div class="document-meta">
1613
- `;
1614
- if (resolved.author) {
1615
- bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
1616
- `;
1617
- }
1618
- if (resolved.version) {
1619
- bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
1620
- `;
1621
- }
1622
- if (resolved.date) {
1623
- bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
1624
- `;
1625
- }
1626
- bodyHtml += ` </div>
1627
- `;
1628
- }
1629
- bodyHtml += ` </header>
1630
- `;
1631
- }
1632
- if (resolved.toc && doc.tocEntries.length > 0) {
1633
- bodyHtml += ` <nav class="table-of-contents">
1634
- `;
1635
- bodyHtml += ` <h2>Table of Contents</h2>
1636
- <ul>
1637
- `;
1638
- for (const entry of doc.tocEntries) {
1639
- const indent = " ".repeat(entry.level);
1640
- bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
1641
- `;
1642
- }
1643
- bodyHtml += ` </ul>
1644
- </nav>
1645
- `;
1646
- }
1647
- for (const node of doc.nodes) {
2075
+ const tokenCtx = tokens || resolved;
2076
+ for (const node of nodes) {
1648
2077
  if (node.type === "heading") {
1649
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
2078
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1650
2079
  bodyHtml += ` <h${node.level} id="${node.id}">${inner}</h${node.level}>
1651
2080
  `;
1652
2081
  continue;
1653
2082
  }
1654
2083
  if (node.type === "paragraph") {
1655
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
2084
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1656
2085
  bodyHtml += ` <p>${inner}</p>
2086
+ `;
2087
+ continue;
2088
+ }
2089
+ if (node.type === "mathBlock") {
2090
+ bodyHtml += ` <div class="math-block">${renderMathToHtml(node.text || "", true)}</div>
2091
+ `;
2092
+ continue;
2093
+ }
2094
+ if (node.type === "columns") {
2095
+ const cols = node.columnsCount || 2;
2096
+ const gap = node.columnGap || "1.5rem";
2097
+ let colChildrenHtml = "";
2098
+ for (const col of node.children || []) {
2099
+ const colInner = await renderNodesToHtml(col.children || [], resolved, baseDir, tokenCtx);
2100
+ colChildrenHtml += ` <div class="markforge-col">
2101
+ ${colInner} </div>
2102
+ `;
2103
+ }
2104
+ bodyHtml += ` <div class="markforge-columns" style="--cols: ${cols}; --col-gap: ${gap};">
2105
+ ${colChildrenHtml} </div>
1657
2106
  `;
1658
2107
  continue;
1659
2108
  }
@@ -1673,7 +2122,7 @@ ${escapeHtml(node.text || "")}
1673
2122
  continue;
1674
2123
  }
1675
2124
  if (node.type === "callout") {
1676
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
2125
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1677
2126
  const CALLOUT_STYLES = {
1678
2127
  NOTE: { bg: "#ECFDFD", border: "#33CDCF", titleColor: "#009DA0" },
1679
2128
  TIP: { bg: "#ecfdf5", border: "#10b981", titleColor: "#10b981" },
@@ -1693,7 +2142,7 @@ ${escapeHtml(node.text || "")}
1693
2142
  continue;
1694
2143
  }
1695
2144
  if (node.type === "blockquote") {
1696
- const inner = await renderInlinesToHtml(node.inlines, baseDir);
2145
+ const inner = await renderInlinesToHtml(node.inlines, baseDir, tokenCtx);
1697
2146
  bodyHtml += ` <blockquote>${inner}</blockquote>
1698
2147
  `;
1699
2148
  continue;
@@ -1707,7 +2156,7 @@ ${escapeHtml(node.text || "")}
1707
2156
  for (const cell of row.children || []) {
1708
2157
  const tag = cell.isHeader ? "th" : "td";
1709
2158
  const align = cell.align ? ` align="${cell.align}"` : "";
1710
- const inner = await renderInlinesToHtml(cell.inlines, baseDir);
2159
+ const inner = await renderInlinesToHtml(cell.inlines, baseDir, tokenCtx);
1711
2160
  bodyHtml += ` <${tag}${align}>${inner}</${tag}>
1712
2161
  `;
1713
2162
  }
@@ -1723,7 +2172,7 @@ ${escapeHtml(node.text || "")}
1723
2172
  bodyHtml += ` <${tag}>
1724
2173
  `;
1725
2174
  for (const item of node.children) {
1726
- const inner = await renderInlinesToHtml(item.inlines, baseDir);
2175
+ const inner = await renderInlinesToHtml(item.inlines, baseDir, tokenCtx);
1727
2176
  bodyHtml += ` <li>${inner}</li>
1728
2177
  `;
1729
2178
  }
@@ -1742,70 +2191,508 @@ ${escapeHtml(node.text || "")}
1742
2191
  continue;
1743
2192
  }
1744
2193
  }
1745
- let watermarkCss = "";
1746
- let watermarkHtml = "";
1747
- if (resolved.watermark) {
1748
- const wm = resolved.watermark;
1749
- watermarkCss = `
1750
- .document-watermark {
1751
- position: fixed;
1752
- top: 0;
1753
- left: 0;
1754
- width: 100vw;
1755
- height: 100vh;
1756
- pointer-events: none;
1757
- z-index: 0;
1758
- user-select: none;
1759
- -webkit-user-select: none;
2194
+ return bodyHtml;
2195
+ }
2196
+ async function renderCoverPageHtml(cover, baseDir = process.cwd()) {
2197
+ let logoHtml = "";
2198
+ if (cover.logo) {
2199
+ const resolvedLogo = await resolveImage(cover.logo, baseDir);
2200
+ const src = resolvedLogo ? resolvedLogo.dataUri : cover.logo;
2201
+ const widthStyle = cover.logoWidth ? `max-width: ${typeof cover.logoWidth === "number" ? cover.logoWidth + "px" : cover.logoWidth}; max-height: 80px; width: auto; height: auto;` : "max-height: 60px; max-width: 180px; width: auto; height: auto;";
2202
+ logoHtml = `<div class="cover-logo"><img src="${src}" alt="Logo" style="${widthStyle} object-fit: contain;" /></div>`;
1760
2203
  }
1761
- .document-container {
2204
+ const badgeHtml = cover.badge ? `<div class="cover-badge" style="${cover.badgeColor ? `background-color: ${cover.badgeColor};` : ""}${cover.badgeTextColor ? `color: ${cover.badgeTextColor};` : ""}">${escapeHtml(cover.badge)}</div>` : "";
2205
+ const titleHtml = `<h1 class="cover-title">${escapeHtml(cover.title)}</h1>`;
2206
+ const subtitleHtml = cover.subtitle ? `<div class="cover-subtitle">${escapeHtml(cover.subtitle)}</div>` : "";
2207
+ const metaItems = [];
2208
+ if (cover.company) metaItems.push(`<div class="cover-meta-item"><span class="cover-meta-label">Organization:</span> <span class="cover-meta-value">${escapeHtml(cover.company)}</span></div>`);
2209
+ if (cover.author) metaItems.push(`<div class="cover-meta-item"><span class="cover-meta-label">Author:</span> <span class="cover-meta-value">${escapeHtml(cover.author)}</span></div>`);
2210
+ if (cover.version) metaItems.push(`<div class="cover-meta-item"><span class="cover-meta-label">Version:</span> <span class="cover-meta-value">${escapeHtml(cover.version)}</span></div>`);
2211
+ if (cover.date) metaItems.push(`<div class="cover-meta-item"><span class="cover-meta-label">Date:</span> <span class="cover-meta-value">${escapeHtml(cover.date)}</span></div>`);
2212
+ const metaHtml = metaItems.length > 0 ? `<div class="cover-meta">${metaItems.join("\n")}</div>` : "";
2213
+ const footerHtml = cover.footerText ? `<div class="cover-footer-text">${escapeHtml(cover.footerText)}</div>` : "";
2214
+ const css = `
2215
+ .markforge-cover {
2216
+ min-height: 100vh;
2217
+ box-sizing: border-box;
2218
+ display: flex;
2219
+ flex-direction: column;
2220
+ justify-content: space-between;
2221
+ padding: 4rem 3.5rem;
2222
+ page-break-after: always;
2223
+ break-after: page;
1762
2224
  position: relative;
1763
- z-index: 1;
2225
+ z-index: 2;
2226
+ background: ${cover.bgGradient || "#FFFFFF"};
2227
+ -webkit-print-color-adjust: exact;
2228
+ print-color-adjust: exact;
2229
+ color: ${cover.textColor || "#0F172A"};
2230
+ }
2231
+ .markforge-cover.cover-modern {
2232
+ border-top: 8px solid #0D998D;
2233
+ }
2234
+ .markforge-cover.cover-corporate-split {
2235
+ border-left: 12px solid #0D998D;
2236
+ }
2237
+ .markforge-cover.cover-card {
2238
+ background: #F8FAFC;
2239
+ }
2240
+ .cover-top {
2241
+ display: flex;
2242
+ justify-content: space-between;
2243
+ align-items: flex-start;
2244
+ width: 100%;
2245
+ }
2246
+ .cover-badge {
2247
+ display: inline-block;
2248
+ padding: 0.35rem 0.85rem;
2249
+ font-size: 0.78rem;
2250
+ font-weight: 700;
2251
+ letter-spacing: 0.08em;
2252
+ text-transform: uppercase;
2253
+ background-color: #ECFDFD;
2254
+ color: #0D998D;
2255
+ border-radius: 4px;
2256
+ border: 1px solid #33CDCF;
2257
+ }
2258
+ .cover-body {
2259
+ margin: auto 0;
2260
+ }
2261
+ .cover-title {
2262
+ font-size: 2.8rem;
2263
+ font-weight: 800;
2264
+ line-height: 1.15;
2265
+ margin: 0 0 1rem 0;
2266
+ color: inherit;
2267
+ }
2268
+ .cover-subtitle {
2269
+ font-size: 1.35rem;
2270
+ font-weight: 400;
2271
+ color: #64748B;
2272
+ margin: 0 0 2rem 0;
2273
+ line-height: 1.4;
2274
+ }
2275
+ .cover-meta {
2276
+ display: flex;
2277
+ flex-direction: column;
2278
+ gap: 0.5rem;
2279
+ border-top: 1.5px solid #E2E8F0;
2280
+ padding-top: 1.5rem;
2281
+ max-width: 480px;
2282
+ }
2283
+ .cover-meta-item {
2284
+ font-size: 0.92rem;
2285
+ display: flex;
2286
+ gap: 0.75rem;
2287
+ }
2288
+ .cover-meta-label {
2289
+ font-weight: 600;
2290
+ color: #64748B;
2291
+ min-width: 110px;
2292
+ }
2293
+ .cover-meta-value {
2294
+ font-weight: 500;
2295
+ color: #0F172A;
2296
+ }
2297
+ .cover-bottom {
2298
+ display: flex;
2299
+ justify-content: space-between;
2300
+ align-items: flex-end;
2301
+ font-size: 0.82rem;
2302
+ color: #94A3B8;
1764
2303
  }
1765
2304
  @media print {
1766
- .document-watermark {
1767
- position: fixed;
1768
- top: 0;
1769
- left: 0;
1770
- width: 100vw;
2305
+ .markforge-cover {
2306
+ page-break-after: always;
2307
+ break-after: page;
1771
2308
  height: 100vh;
2309
+ min-height: 100vh;
2310
+ max-height: 100vh;
2311
+ box-sizing: border-box;
2312
+ overflow: hidden;
2313
+ margin: 0;
1772
2314
  -webkit-print-color-adjust: exact;
1773
2315
  print-color-adjust: exact;
1774
2316
  }
1775
2317
  }
1776
2318
  `;
1777
- watermarkHtml = ` <div id="markforge-watermark" class="document-watermark" aria-hidden="true"></div>
1778
- <script>
1779
- (function() {
1780
- try {
1781
- var canvas = document.createElement('canvas');
1782
- var dpr = 3;
1783
- var width = 800;
1784
- var height = 1100;
1785
- canvas.width = Math.round(width * dpr);
1786
- canvas.height = Math.round(height * dpr);
1787
- var ctx = canvas.getContext('2d');
1788
- if (ctx) {
1789
- ctx.scale(dpr, dpr);
1790
- ctx.translate(width / 2, height / 2);
1791
- ctx.rotate((${wm.rotate} * Math.PI) / 180);
1792
- ctx.textAlign = 'center';
1793
- ctx.textBaseline = 'middle';
1794
- ctx.font = '900 ${wm.fontSize * 1.5}px system-ui, -apple-system, sans-serif';
1795
- ctx.fillStyle = '${wm.color}';
1796
- ctx.globalAlpha = ${wm.opacity};
1797
- try { ctx.letterSpacing = '0.15em'; } catch(e) {}
1798
- ctx.fillText(${JSON.stringify(wm.text.toUpperCase())}, 0, 0);
1799
- var dataUrl = canvas.toDataURL('image/png');
1800
- var wmEl = document.getElementById('markforge-watermark');
1801
- if (wmEl) {
1802
- wmEl.style.backgroundImage = 'url("' + dataUrl + '")';
1803
- wmEl.style.backgroundRepeat = 'no-repeat';
1804
- wmEl.style.backgroundPosition = 'center center';
1805
- wmEl.style.backgroundSize = 'contain';
1806
- }
1807
- }
1808
- } catch(err) {}
2319
+ const html = ` <section class="markforge-cover cover-${cover.preset}">
2320
+ <div class="cover-top">
2321
+ ${logoHtml}
2322
+ ${badgeHtml}
2323
+ </div>
2324
+ <div class="cover-body">
2325
+ ${titleHtml}
2326
+ ${subtitleHtml}
2327
+ ${metaHtml}
2328
+ </div>
2329
+ <div class="cover-bottom">
2330
+ ${footerHtml}
2331
+ </div>
2332
+ </section>
2333
+ `;
2334
+ return { html, css };
2335
+ }
2336
+ async function renderBackCoverHtml(backCover, baseDir = process.cwd()) {
2337
+ let logoHtml = "";
2338
+ if (backCover.logo) {
2339
+ const resolved = await resolveImage(backCover.logo, baseDir);
2340
+ const src = resolved ? resolved.dataUri : backCover.logo;
2341
+ const widthStyle = backCover.logoWidth ? `style="width: ${typeof backCover.logoWidth === "number" ? `${backCover.logoWidth}px` : backCover.logoWidth}; max-width: 100%;"` : `style="max-width: 160px; height: auto;"`;
2342
+ logoHtml = `<div class="back-logo"><img src="${src}" alt="Brand Logo" ${widthStyle} /></div>`;
2343
+ }
2344
+ const badgeHtml = backCover.badge ? `<div class="back-badge" style="${backCover.badgeColor ? `background-color: ${backCover.badgeColor};` : ""}${backCover.badgeTextColor ? `color: ${backCover.badgeTextColor};` : ""}">${escapeHtml(backCover.badge)}</div>` : "";
2345
+ const titleHtml = `<h1 class="back-title">${escapeHtml(backCover.title)}</h1>`;
2346
+ const subtitleHtml = backCover.subtitle ? `<div class="back-subtitle">${escapeHtml(backCover.subtitle)}</div>` : "";
2347
+ const contactItems = [];
2348
+ if (backCover.company) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Organization:</span> <span class="back-contact-value">${escapeHtml(backCover.company)}</span></div>`);
2349
+ if (backCover.address) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Address:</span> <span class="back-contact-value">${escapeHtml(backCover.address)}</span></div>`);
2350
+ if (backCover.email) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Email:</span> <a href="mailto:${escapeHtml(backCover.email)}" class="back-contact-link">${escapeHtml(backCover.email)}</a></div>`);
2351
+ if (backCover.phone) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Phone:</span> <span class="back-contact-value">${escapeHtml(backCover.phone)}</span></div>`);
2352
+ if (backCover.website) contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">Website:</span> <a href="${escapeHtml(backCover.website)}" target="_blank" class="back-contact-link">${escapeHtml(backCover.website)}</a></div>`);
2353
+ if (backCover.social) {
2354
+ for (const [network, url] of Object.entries(backCover.social)) {
2355
+ if (url) {
2356
+ contactItems.push(`<div class="back-contact-item"><span class="back-contact-label">${escapeHtml(network.toUpperCase())}:</span> <a href="${escapeHtml(url)}" target="_blank" class="back-contact-link">${escapeHtml(url)}</a></div>`);
2357
+ }
2358
+ }
2359
+ }
2360
+ const contactHtml = contactItems.length > 0 ? `<div class="back-contact-grid">${contactItems.join("\n")}</div>` : "";
2361
+ const copyrightHtml = backCover.copyright ? `<div class="back-copyright">${escapeHtml(backCover.copyright)}</div>` : "";
2362
+ const isDark = backCover.preset === "corporate";
2363
+ const css = `
2364
+ .markforge-back-cover {
2365
+ min-height: 100vh;
2366
+ box-sizing: border-box;
2367
+ display: flex;
2368
+ flex-direction: column;
2369
+ justify-content: space-between;
2370
+ padding: 4rem 3.5rem;
2371
+ page-break-before: always;
2372
+ break-before: page;
2373
+ position: relative;
2374
+ z-index: 2;
2375
+ background: ${backCover.bgGradient || (isDark ? "#0F172A" : "#FFFFFF")};
2376
+ color: ${backCover.textColor || (isDark ? "#F8FAFC" : "#0F172A")};
2377
+ }
2378
+ .markforge-back-cover.back-modern {
2379
+ border-bottom: 8px solid #0D998D;
2380
+ }
2381
+ .markforge-back-cover.back-corporate {
2382
+ border-left: 12px solid #33CDCF;
2383
+ }
2384
+ .markforge-back-cover.back-card {
2385
+ background: #F8FAFC;
2386
+ }
2387
+ .back-top {
2388
+ display: flex;
2389
+ justify-content: space-between;
2390
+ align-items: flex-start;
2391
+ width: 100%;
2392
+ }
2393
+ .back-badge {
2394
+ display: inline-block;
2395
+ padding: 0.35rem 0.85rem;
2396
+ font-size: 0.78rem;
2397
+ font-weight: 700;
2398
+ letter-spacing: 0.08em;
2399
+ text-transform: uppercase;
2400
+ background-color: #ECFDFD;
2401
+ color: #0D998D;
2402
+ border-radius: 4px;
2403
+ border: 1px solid #33CDCF;
2404
+ }
2405
+ .back-body {
2406
+ margin: auto 0;
2407
+ }
2408
+ .back-title {
2409
+ font-size: 2.6rem;
2410
+ font-weight: 800;
2411
+ line-height: 1.15;
2412
+ margin: 0 0 0.75rem 0;
2413
+ color: inherit;
2414
+ }
2415
+ .back-subtitle {
2416
+ font-size: 1.25rem;
2417
+ font-weight: 400;
2418
+ color: ${isDark ? "#94A3B8" : "#64748B"};
2419
+ margin: 0 0 2rem 0;
2420
+ line-height: 1.4;
2421
+ }
2422
+ .back-contact-grid {
2423
+ display: flex;
2424
+ flex-direction: column;
2425
+ gap: 0.6rem;
2426
+ border-top: 1.5px solid ${isDark ? "#334155" : "#E2E8F0"};
2427
+ padding-top: 1.5rem;
2428
+ max-width: 540px;
2429
+ }
2430
+ .back-contact-item {
2431
+ font-size: 0.92rem;
2432
+ display: flex;
2433
+ gap: 0.75rem;
2434
+ }
2435
+ .back-contact-label {
2436
+ font-weight: 600;
2437
+ color: ${isDark ? "#94A3B8" : "#64748B"};
2438
+ min-width: 110px;
2439
+ }
2440
+ .back-contact-link {
2441
+ color: #0D998D;
2442
+ text-decoration: none;
2443
+ font-weight: 600;
2444
+ }
2445
+ .back-contact-link:hover {
2446
+ text-decoration: underline;
2447
+ }
2448
+ .back-copyright {
2449
+ font-size: 0.82rem;
2450
+ color: ${isDark ? "#64748B" : "#94A3B8"};
2451
+ border-top: 1px solid ${isDark ? "#1E293B" : "#F1F5F9"};
2452
+ padding-top: 1rem;
2453
+ margin-top: 2rem;
2454
+ }
2455
+ @media print {
2456
+ .markforge-back-cover {
2457
+ page: back-cover-page;
2458
+ page-break-before: always;
2459
+ break-before: page;
2460
+ page-break-after: avoid;
2461
+ break-after: avoid;
2462
+ min-height: 100vh;
2463
+ height: 100vh;
2464
+ max-height: 100vh;
2465
+ margin: 0;
2466
+ box-sizing: border-box;
2467
+ overflow: hidden;
2468
+ -webkit-print-color-adjust: exact;
2469
+ print-color-adjust: exact;
2470
+ }
2471
+ }
2472
+ `;
2473
+ const html = ` <section class="markforge-back-cover back-${backCover.preset}">
2474
+ <div class="back-top">
2475
+ ${logoHtml}
2476
+ ${badgeHtml}
2477
+ </div>
2478
+ <div class="back-body">
2479
+ ${titleHtml}
2480
+ ${subtitleHtml}
2481
+ ${contactHtml}
2482
+ </div>
2483
+ ${copyrightHtml}
2484
+ </section>
2485
+ `;
2486
+ return { html, css };
2487
+ }
2488
+ async function buildHtmlDocument(doc, config, baseDir = process.cwd()) {
2489
+ var _a, _b;
2490
+ const resolved = resolveDocumentConfig(doc.metadata, config);
2491
+ const baseThemeCss = generateThemeCss(resolved.theme);
2492
+ let customCss = "";
2493
+ for (const cssPath of resolved.css) {
2494
+ const fullCssPath = path3.isAbsolute(cssPath) ? cssPath : path3.resolve(baseDir, cssPath);
2495
+ if (fs3.existsSync(fullCssPath)) {
2496
+ customCss += `
2497
+ /* Custom CSS: ${cssPath} */
2498
+ ` + fs3.readFileSync(fullCssPath, "utf-8");
2499
+ }
2500
+ }
2501
+ const inlinedCss = doc.inlinedStyles.join("\n");
2502
+ const extraCss = `
2503
+ .markforge-columns {
2504
+ display: grid;
2505
+ grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
2506
+ gap: var(--col-gap, 1.5rem);
2507
+ margin: 1.5rem 0;
2508
+ }
2509
+ .markforge-col {
2510
+ min-width: 0;
2511
+ }
2512
+ .markforge-fnref {
2513
+ text-decoration: none;
2514
+ font-size: 0.8em;
2515
+ vertical-align: super;
2516
+ color: #0D998D;
2517
+ font-weight: 700;
2518
+ }
2519
+ .markforge-footnotes {
2520
+ margin-top: 3rem;
2521
+ padding-top: 1rem;
2522
+ font-size: 0.88rem;
2523
+ color: #64748B;
2524
+ }
2525
+ .markforge-footnotes hr {
2526
+ border: 0;
2527
+ border-top: 1px solid #E2E8F0;
2528
+ margin-bottom: 1rem;
2529
+ }
2530
+ .markforge-fn-return {
2531
+ text-decoration: none;
2532
+ color: #0D998D;
2533
+ }
2534
+ .math-block {
2535
+ margin: 1.5rem 0;
2536
+ text-align: center;
2537
+ overflow-x: auto;
2538
+ }
2539
+ `;
2540
+ let coverHtml = "";
2541
+ let coverCss = "";
2542
+ if (resolved.coverPage && resolved.coverPage.enabled) {
2543
+ const coverRes = await renderCoverPageHtml(resolved.coverPage, baseDir);
2544
+ coverHtml = coverRes.html;
2545
+ coverCss = coverRes.css;
2546
+ }
2547
+ let backHtml = "";
2548
+ let backCss = "";
2549
+ if (resolved.backCover && resolved.backCover.enabled) {
2550
+ const backRes = await renderBackCoverHtml(resolved.backCover, baseDir);
2551
+ backHtml = backRes.html;
2552
+ backCss = backRes.css;
2553
+ }
2554
+ let bodyHtml = "";
2555
+ if (resolved.title && !((_a = resolved.coverPage) == null ? void 0 : _a.enabled)) {
2556
+ bodyHtml += ` <header class="document-header">
2557
+ `;
2558
+ bodyHtml += ` <h1 class="document-title">${escapeHtml(resolved.title)}</h1>
2559
+ `;
2560
+ if (resolved.subtitle) {
2561
+ bodyHtml += ` <div class="document-subtitle">${escapeHtml(resolved.subtitle)}</div>
2562
+ `;
2563
+ }
2564
+ if (resolved.author || resolved.date || resolved.version) {
2565
+ bodyHtml += ` <div class="document-meta">
2566
+ `;
2567
+ if (resolved.author) {
2568
+ bodyHtml += ` <span>Author: ${escapeHtml(resolved.author)}</span>
2569
+ `;
2570
+ }
2571
+ if (resolved.version) {
2572
+ bodyHtml += ` <span>Version: ${escapeHtml(resolved.version)}</span>
2573
+ `;
2574
+ }
2575
+ if (resolved.date) {
2576
+ bodyHtml += ` <span>Date: ${escapeHtml(resolved.date)}</span>
2577
+ `;
2578
+ }
2579
+ bodyHtml += ` </div>
2580
+ `;
2581
+ }
2582
+ bodyHtml += ` </header>
2583
+ `;
2584
+ }
2585
+ if (((_b = resolved.numberHeadings) == null ? void 0 : _b.enabled) !== false && resolved.numberHeadings) {
2586
+ applyHeadingNumbering(doc.nodes, doc.tocEntries, resolved.numberHeadings);
2587
+ }
2588
+ if (resolved.toc && doc.tocEntries.length > 0) {
2589
+ bodyHtml += ` <nav class="table-of-contents">
2590
+ `;
2591
+ bodyHtml += ` <h2>Table of Contents</h2>
2592
+ <ul>
2593
+ `;
2594
+ for (const entry of doc.tocEntries) {
2595
+ const indent = " ".repeat(entry.level);
2596
+ bodyHtml += ` ${indent}<li><a href="#${entry.id}">${escapeHtml(entry.text)}</a></li>
2597
+ `;
2598
+ }
2599
+ bodyHtml += ` </ul>
2600
+ </nav>
2601
+ `;
2602
+ }
2603
+ const mergedTokens = {
2604
+ ...config.metadata,
2605
+ ...doc.metadata,
2606
+ ...resolved,
2607
+ title: resolved.title,
2608
+ subtitle: resolved.subtitle,
2609
+ author: resolved.author,
2610
+ version: resolved.version,
2611
+ date: resolved.date,
2612
+ company: resolved.company
2613
+ };
2614
+ const nodesHtml = await renderNodesToHtml(doc.nodes, resolved, baseDir, mergedTokens);
2615
+ bodyHtml += ` <main class="markforge-content-body">
2616
+ ${nodesHtml} </main>
2617
+ `;
2618
+ let footnotesHtml = "";
2619
+ if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
2620
+ let fnListHtml = "";
2621
+ for (const def of doc.footnoteDefs) {
2622
+ const defInner = await renderInlinesToHtml(def.inlines, baseDir, mergedTokens);
2623
+ fnListHtml += ` <li id="fn-${escapeHtml(def.id)}">${defInner} <a href="#fnref-${escapeHtml(def.id)}" class="markforge-fn-return">&#8617;</a></li>
2624
+ `;
2625
+ }
2626
+ footnotesHtml = `
2627
+ <footer class="markforge-footnotes">
2628
+ <hr />
2629
+ <ol>
2630
+ ${fnListHtml} </ol>
2631
+ </footer>
2632
+ `;
2633
+ }
2634
+ let watermarkCss = "";
2635
+ let watermarkHtml = "";
2636
+ if (resolved.watermark) {
2637
+ const wm = resolved.watermark;
2638
+ watermarkCss = `
2639
+ .document-watermark {
2640
+ position: fixed;
2641
+ top: 0;
2642
+ left: 0;
2643
+ right: 0;
2644
+ bottom: 0;
2645
+ width: 100%;
2646
+ height: 100%;
2647
+ pointer-events: none;
2648
+ z-index: 0;
2649
+ user-select: none;
2650
+ -webkit-user-select: none;
2651
+ -webkit-print-color-adjust: exact;
2652
+ print-color-adjust: exact;
2653
+ }
2654
+ .document-container {
2655
+ position: relative;
2656
+ z-index: 1;
2657
+ }
2658
+ @media print {
2659
+ .document-watermark {
2660
+ display: none !important;
2661
+ }
2662
+ }
2663
+ `;
2664
+ watermarkHtml = ` <div id="markforge-watermark" class="document-watermark" aria-hidden="true"></div>
2665
+ <script>
2666
+ (function() {
2667
+ try {
2668
+ var canvas = document.createElement('canvas');
2669
+ var dpr = 2;
2670
+ var width = 1200;
2671
+ var height = 1600;
2672
+ canvas.width = width * dpr;
2673
+ canvas.height = height * dpr;
2674
+ var ctx = canvas.getContext('2d');
2675
+ if (ctx) {
2676
+ ctx.scale(dpr, dpr);
2677
+ ctx.translate(width / 2, height / 2);
2678
+ ctx.rotate((-Math.abs(${wm.rotate || 45}) * Math.PI) / 180);
2679
+ ctx.textAlign = 'center';
2680
+ ctx.textBaseline = 'middle';
2681
+ ctx.font = '900 ${wm.fontSize * 1.5}px system-ui, -apple-system, sans-serif';
2682
+ ctx.fillStyle = '${wm.color}';
2683
+ ctx.globalAlpha = ${wm.opacity};
2684
+ try { ctx.letterSpacing = '0.15em'; } catch(e) {}
2685
+ ctx.fillText(${JSON.stringify(wm.text.toUpperCase())}, 0, 0);
2686
+ var dataUrl = canvas.toDataURL('image/png');
2687
+ var wmEl = document.getElementById('markforge-watermark');
2688
+ if (wmEl) {
2689
+ wmEl.style.backgroundImage = 'url("' + dataUrl + '")';
2690
+ wmEl.style.backgroundRepeat = 'no-repeat';
2691
+ wmEl.style.backgroundPosition = 'center center';
2692
+ wmEl.style.backgroundSize = 'contain';
2693
+ }
2694
+ }
2695
+ } catch(err) {}
1809
2696
  })();
1810
2697
  </script>
1811
2698
  `;
@@ -1815,10 +2702,6 @@ ${escapeHtml(node.text || "")}
1815
2702
  if (resolved.signatures && resolved.signatures.items.length > 0) {
1816
2703
  const sig = resolved.signatures;
1817
2704
  const numItems = sig.items.length;
1818
- let justifyCss = "flex-end";
1819
- if (sig.align === "left") justifyCss = "flex-start";
1820
- else if (sig.align === "center") justifyCss = "center";
1821
- else if (sig.align === "space-between") justifyCss = "space-between";
1822
2705
  signaturesCss = `
1823
2706
  .markforge-signatures {
1824
2707
  margin-top: ${sig.spacingBefore};
@@ -1930,6 +2813,10 @@ ${itemCards}
1930
2813
  <style>
1931
2814
  ${THEME_COMPONENTS}
1932
2815
  ${baseThemeCss}
2816
+ ${KATEX_INLINE_CSS}
2817
+ ${extraCss}
2818
+ ${coverCss}
2819
+ ${backCss}
1933
2820
  ${customCss}
1934
2821
  ${inlinedCss}
1935
2822
  ${watermarkCss}
@@ -1937,14 +2824,95 @@ ${signaturesCss}
1937
2824
  </style>
1938
2825
  </head>
1939
2826
  <body>
1940
- ${watermarkHtml} <div class="document-container">
1941
- ${bodyHtml}${signaturesHtml} </div>
2827
+ ${watermarkHtml}${coverHtml} <div class="document-container">
2828
+ ${bodyHtml}${footnotesHtml}${signaturesHtml} </div>
1942
2829
  ${mermaidScript}
1943
- </body>
2830
+ ${backHtml}</body>
1944
2831
  </html>`;
1945
2832
  }
1946
2833
 
1947
2834
  // src/core/pdf/pdfBuilder.ts
2835
+ function escapeXml(str) {
2836
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
2837
+ }
2838
+ function generateWatermarkPngBuffer(chromePath, wm) {
2839
+ const tmpHtml = path4.join(os.tmpdir(), `markforge-wm-${Date.now()}-${Math.random().toString(36).slice(2)}.html`);
2840
+ const tmpPng = path4.join(os.tmpdir(), `markforge-wm-${Date.now()}-${Math.random().toString(36).slice(2)}.png`);
2841
+ try {
2842
+ const text = escapeXml(wm.text.toUpperCase());
2843
+ const fontSize = (wm.fontSize || 52) * 1.5;
2844
+ const color = wm.color || "#E11D48";
2845
+ const opacity = wm.opacity !== void 0 ? wm.opacity : 0.12;
2846
+ const rotate = wm.rotate !== void 0 ? wm.rotate : -45;
2847
+ const html = `<!DOCTYPE html>
2848
+ <html>
2849
+ <head>
2850
+ <meta charset="utf-8">
2851
+ <style>
2852
+ html, body {
2853
+ margin: 0;
2854
+ padding: 0;
2855
+ width: 1200px;
2856
+ height: 1600px;
2857
+ background: transparent;
2858
+ overflow: hidden;
2859
+ }
2860
+ .wm-box {
2861
+ width: 1200px;
2862
+ height: 1600px;
2863
+ display: flex;
2864
+ align-items: center;
2865
+ justify-content: center;
2866
+ transform: rotate(${rotate}deg);
2867
+ }
2868
+ .wm-text {
2869
+ font-family: system-ui, -apple-system, sans-serif;
2870
+ font-weight: 900;
2871
+ font-size: ${fontSize}px;
2872
+ color: ${color};
2873
+ opacity: ${opacity};
2874
+ letter-spacing: 0.15em;
2875
+ text-transform: uppercase;
2876
+ white-space: nowrap;
2877
+ }
2878
+ </style>
2879
+ </head>
2880
+ <body>
2881
+ <div class="wm-box"><span class="wm-text">${text}</span></div>
2882
+ </body>
2883
+ </html>`;
2884
+ fs4.writeFileSync(tmpHtml, html, "utf8");
2885
+ const fileUrl = (0, import_node_url2.pathToFileURL)(tmpHtml).href;
2886
+ const isWin = process.platform === "win32";
2887
+ (0, import_node_child_process.spawnSync)(
2888
+ chromePath,
2889
+ [
2890
+ "--headless=new",
2891
+ "--disable-gpu",
2892
+ "--disable-sync",
2893
+ "--disable-extensions",
2894
+ ...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
2895
+ `--screenshot=${tmpPng}`,
2896
+ "--window-size=1200,1600",
2897
+ "--default-background-color=00000000",
2898
+ fileUrl
2899
+ ],
2900
+ { timeout: 15e3, windowsHide: true }
2901
+ );
2902
+ if (fs4.existsSync(tmpPng) && fs4.statSync(tmpPng).size > 0) {
2903
+ return fs4.readFileSync(tmpPng);
2904
+ }
2905
+ return null;
2906
+ } catch {
2907
+ return null;
2908
+ } finally {
2909
+ try {
2910
+ if (fs4.existsSync(tmpHtml)) fs4.unlinkSync(tmpHtml);
2911
+ if (fs4.existsSync(tmpPng)) fs4.unlinkSync(tmpPng);
2912
+ } catch {
2913
+ }
2914
+ }
2915
+ }
1948
2916
  function findChromeExecutable() {
1949
2917
  if (process.env.CHROME_PATH && fs4.existsSync(process.env.CHROME_PATH)) {
1950
2918
  return process.env.CHROME_PATH;
@@ -2009,7 +2977,7 @@ function findChromeExecutable() {
2009
2977
  return null;
2010
2978
  }
2011
2979
  function injectPagedMediaStyles(html, config, metadata) {
2012
- var _a, _b, _c, _d, _e, _f;
2980
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
2013
2981
  const resolved = resolveDocumentConfig(metadata || {}, config);
2014
2982
  const size = resolved.paperSize;
2015
2983
  const orientation = resolved.orientation;
@@ -2053,6 +3021,66 @@ function injectPagedMediaStyles(html, config, metadata) {
2053
3021
  ${fontStyle}
2054
3022
  }`;
2055
3023
  };
3024
+ const coverPageCss = ((_a = resolved.coverPage) == null ? void 0 : _a.enabled) ? `
3025
+ @page :first {
3026
+ margin-top: 0;
3027
+ margin-bottom: 0;
3028
+ margin-left: 0;
3029
+ margin-right: 0;
3030
+ background-image: none !important;
3031
+ @top-left { content: none; }
3032
+ @top-center { content: none; }
3033
+ @top-right { content: none; }
3034
+ @bottom-left { content: none; }
3035
+ @bottom-center { content: none; }
3036
+ @bottom-right { content: none; }
3037
+ }` : "";
3038
+ const backCoverCss = ((_b = resolved.backCover) == null ? void 0 : _b.enabled) ? `
3039
+ @page back-cover-page {
3040
+ size: ${size} ${orientation};
3041
+ margin: 0;
3042
+ background-image: none !important;
3043
+ @top-left { content: none; }
3044
+ @top-center { content: none; }
3045
+ @top-right { content: none; }
3046
+ @bottom-left { content: none; }
3047
+ @bottom-center { content: none; }
3048
+ @bottom-right { content: none; }
3049
+ }
3050
+ .markforge-back-cover {
3051
+ page: back-cover-page;
3052
+ min-height: 100vh;
3053
+ height: 100vh;
3054
+ box-sizing: border-box;
3055
+ break-before: page;
3056
+ break-after: avoid;
3057
+ }` : "";
3058
+ const tocPageCss = resolved.toc ? `
3059
+ @page toc-page {
3060
+ size: ${size} ${orientation};
3061
+ margin-top: ${top};
3062
+ margin-bottom: ${bottom};
3063
+ margin-left: ${left};
3064
+ margin-right: ${right};
3065
+ ${buildZoneCss("top-left", (_c = resolved.header) == null ? void 0 : _c.left)}
3066
+ ${buildZoneCss("top-center", (_d = resolved.header) == null ? void 0 : _d.center)}
3067
+ ${buildZoneCss("top-right", (_e = resolved.header) == null ? void 0 : _e.right)}
3068
+ ${buildZoneCss("bottom-left", (_f = resolved.footer) == null ? void 0 : _f.left)}
3069
+ ${buildZoneCss("bottom-center", (_g = resolved.footer) == null ? void 0 : _g.center)}
3070
+ @bottom-right {
3071
+ content: counter(page, lower-roman);
3072
+ font-size: 9pt;
3073
+ color: #94a3b8;
3074
+ }
3075
+ }
3076
+ .table-of-contents {
3077
+ page: toc-page;
3078
+ page-break-after: always;
3079
+ break-after: page;
3080
+ }
3081
+ .markforge-content-body {
3082
+ counter-reset: page 1;
3083
+ }` : "";
2056
3084
  const pagedCss = `
2057
3085
  @page {
2058
3086
  size: ${size} ${orientation};
@@ -2060,15 +3088,19 @@ function injectPagedMediaStyles(html, config, metadata) {
2060
3088
  margin-bottom: ${bottom};
2061
3089
  margin-left: ${left};
2062
3090
  margin-right: ${right};
2063
- ${buildZoneCss("top-left", (_a = resolved.header) == null ? void 0 : _a.left)}
2064
- ${buildZoneCss("top-center", (_b = resolved.header) == null ? void 0 : _b.center)}
2065
- ${buildZoneCss("top-right", (_c = resolved.header) == null ? void 0 : _c.right)}
2066
- ${buildZoneCss("bottom-left", (_d = resolved.footer) == null ? void 0 : _d.left)}
2067
- ${buildZoneCss("bottom-center", (_e = resolved.footer) == null ? void 0 : _e.center)}
2068
- ${buildZoneCss("bottom-right", (_f = resolved.footer) == null ? void 0 : _f.right, true)}
3091
+ ${buildZoneCss("top-left", (_h = resolved.header) == null ? void 0 : _h.left)}
3092
+ ${buildZoneCss("top-center", (_i = resolved.header) == null ? void 0 : _i.center)}
3093
+ ${buildZoneCss("top-right", (_j = resolved.header) == null ? void 0 : _j.right)}
3094
+ ${buildZoneCss("bottom-left", (_k = resolved.footer) == null ? void 0 : _k.left)}
3095
+ ${buildZoneCss("bottom-center", (_l = resolved.footer) == null ? void 0 : _l.center)}
3096
+ ${buildZoneCss("bottom-right", (_m = resolved.footer) == null ? void 0 : _m.right, true)}
2069
3097
  }
3098
+ ${coverPageCss}
3099
+ ${tocPageCss}
3100
+ ${backCoverCss}
2070
3101
  @media print {
2071
3102
  body { padding: 0; }
3103
+ .document-watermark { display: none !important; }
2072
3104
  h1, h2, h3, pre, table, blockquote, .callout {
2073
3105
  break-inside: avoid;
2074
3106
  }
@@ -2116,6 +3148,7 @@ startxref
2116
3148
  return Buffer.from(pdfBody, "utf-8");
2117
3149
  }
2118
3150
  async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
3151
+ var _a, _b, _c;
2119
3152
  const baseHtml = await buildHtmlDocument(doc, config, baseDir);
2120
3153
  const pagedHtml = injectPagedMediaStyles(baseHtml, config, doc.metadata);
2121
3154
  const chromePath = findChromeExecutable();
@@ -2125,6 +3158,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2125
3158
  const tmpHtml = path4.join(tmpDir, `markforge_${tmpId}.html`);
2126
3159
  const tmpPdf = path4.join(tmpDir, `markforge_${tmpId}.pdf`);
2127
3160
  const tmpProfile = path4.join(tmpDir, `markforge_prof_${tmpId}`);
3161
+ const isWin = process.platform === "win32";
2128
3162
  const isolatedFlags = [
2129
3163
  `--user-data-dir=${tmpProfile}`,
2130
3164
  "--no-first-run",
@@ -2135,7 +3169,6 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2135
3169
  "--disable-default-apps",
2136
3170
  "--disable-extensions",
2137
3171
  "--disable-domain-reliability",
2138
- "--disable-client-side-phishing-detection",
2139
3172
  "--disable-breakpad",
2140
3173
  "--disable-component-extensions-with-background-pages",
2141
3174
  "--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculatedNewTabPage,ChromeWhatsNewUI,PrivacySandboxSettings4",
@@ -2144,12 +3177,10 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2144
3177
  "--mute-audio",
2145
3178
  "--no-service-autorun",
2146
3179
  "--disable-gpu",
2147
- "--no-sandbox",
2148
- "--disable-setuid-sandbox",
2149
- "--allow-file-access-from-files",
2150
- "--disable-web-security",
3180
+ ...isWin ? [] : ["--no-sandbox", "--disable-setuid-sandbox"],
2151
3181
  "--force-color-profile=srgb",
2152
- "--no-pdf-header-footer"
3182
+ "--no-pdf-header-footer",
3183
+ "--window-size=1200,1600"
2153
3184
  ];
2154
3185
  try {
2155
3186
  fs4.writeFileSync(tmpHtml, pagedHtml, "utf-8");
@@ -2164,7 +3195,7 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2164
3195
  `--print-to-pdf=${tmpPdf}`,
2165
3196
  fileUrl
2166
3197
  ],
2167
- { timeout: 3e4 }
3198
+ { timeout: 3e4, windowsHide: true }
2168
3199
  );
2169
3200
  if ((res.status !== 0 || !fs4.existsSync(tmpPdf)) && chromePath) {
2170
3201
  res = (0, import_node_child_process.spawnSync)(
@@ -2175,12 +3206,79 @@ async function buildPdfDocument(doc, config, baseDir = process.cwd()) {
2175
3206
  `--print-to-pdf=${tmpPdf}`,
2176
3207
  fileUrl
2177
3208
  ],
2178
- { timeout: 3e4 }
3209
+ { timeout: 3e4, windowsHide: true }
2179
3210
  );
2180
3211
  }
2181
3212
  if (fs4.existsSync(tmpPdf) && fs4.statSync(tmpPdf).size > 0) {
2182
3213
  const pdfBuffer = fs4.readFileSync(tmpPdf);
2183
- return pdfBuffer;
3214
+ try {
3215
+ const pdfDoc = await import_pdf_lib.PDFDocument.load(pdfBuffer);
3216
+ const resolved = resolveDocumentConfig(doc.metadata, config);
3217
+ if (resolved.title) pdfDoc.setTitle(resolved.title);
3218
+ if (resolved.author) pdfDoc.setAuthor(resolved.author);
3219
+ if (resolved.subtitle) pdfDoc.setSubject(resolved.subtitle);
3220
+ pdfDoc.setCreator("MarkForge Enterprise Document Generator");
3221
+ pdfDoc.setProducer("MarkForge (by Ma'sum)");
3222
+ pdfDoc.setModificationDate(/* @__PURE__ */ new Date());
3223
+ if (((_a = resolved.backCover) == null ? void 0 : _a.enabled) && pdfDoc.getPageCount() > 2) {
3224
+ pdfDoc.removePage(pdfDoc.getPageCount() - 1);
3225
+ }
3226
+ if (resolved.watermark) {
3227
+ const wmPng = generateWatermarkPngBuffer(chromePath, resolved.watermark);
3228
+ if (wmPng) {
3229
+ const embeddedPng = await pdfDoc.embedPng(wmPng);
3230
+ const totalPages = pdfDoc.getPageCount();
3231
+ const pages = pdfDoc.getPages();
3232
+ const startPageIndex = ((_b = resolved.coverPage) == null ? void 0 : _b.enabled) ? 1 : 0;
3233
+ const endPageIndex = ((_c = resolved.backCover) == null ? void 0 : _c.enabled) ? totalPages - 1 : totalPages;
3234
+ for (let i = startPageIndex; i < endPageIndex; i++) {
3235
+ const page = pages[i];
3236
+ const { width, height } = page.getSize();
3237
+ page.drawImage(embeddedPng, {
3238
+ x: 0,
3239
+ y: 0,
3240
+ width,
3241
+ height
3242
+ });
3243
+ }
3244
+ }
3245
+ }
3246
+ const savedBytes = await pdfDoc.save();
3247
+ let finalBuffer = Buffer.from(savedBytes);
3248
+ if (resolved.security) {
3249
+ const sec = resolved.security;
3250
+ const hasUserPassword = typeof sec.userPassword === "string" && sec.userPassword.length > 0;
3251
+ const hasOwnerPassword = typeof sec.ownerPassword === "string" && sec.ownerPassword.length > 0;
3252
+ if (hasUserPassword || hasOwnerPassword) {
3253
+ try {
3254
+ const userPass = sec.userPassword ?? "";
3255
+ const ownerPass = sec.ownerPassword ?? userPass;
3256
+ const perms = sec.permissions;
3257
+ const encryptedBytes = await (0, import_pdf_encrypt.encryptPDF)(
3258
+ new Uint8Array(finalBuffer),
3259
+ userPass,
3260
+ {
3261
+ ownerPassword: ownerPass,
3262
+ algorithm: "AES-256",
3263
+ allowPrinting: (perms == null ? void 0 : perms.printing) !== "none",
3264
+ allowHighQualityPrint: (perms == null ? void 0 : perms.printing) === "highResolution",
3265
+ allowModifying: (perms == null ? void 0 : perms.modifying) ?? true,
3266
+ allowCopying: (perms == null ? void 0 : perms.copying) ?? true,
3267
+ allowAnnotating: (perms == null ? void 0 : perms.annotating) ?? true,
3268
+ allowFillingForms: (perms == null ? void 0 : perms.fillingForms) ?? true,
3269
+ allowExtraction: (perms == null ? void 0 : perms.contentAccessibility) ?? true,
3270
+ allowAssembly: (perms == null ? void 0 : perms.documentAssembly) ?? true
3271
+ }
3272
+ );
3273
+ finalBuffer = Buffer.from(encryptedBytes);
3274
+ } catch {
3275
+ }
3276
+ }
3277
+ }
3278
+ return finalBuffer;
3279
+ } catch {
3280
+ return pdfBuffer;
3281
+ }
2184
3282
  }
2185
3283
  } catch {
2186
3284
  } finally {
@@ -2435,6 +3533,29 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
2435
3533
  );
2436
3534
  continue;
2437
3535
  }
3536
+ if (span.type === "mathInline") {
3537
+ runs.push(
3538
+ new import_docx.TextRun({
3539
+ text: span.content,
3540
+ font: "Cambria Math",
3541
+ italics: true,
3542
+ size: options.size,
3543
+ color: options.color || "0F172A"
3544
+ })
3545
+ );
3546
+ continue;
3547
+ }
3548
+ if (span.type === "footnoteRef") {
3549
+ runs.push(
3550
+ new import_docx.TextRun({
3551
+ text: `[${span.content}]`,
3552
+ superScript: true,
3553
+ color: "009DA0",
3554
+ bold: true
3555
+ })
3556
+ );
3557
+ continue;
3558
+ }
2438
3559
  runs.push(
2439
3560
  new import_docx.TextRun({
2440
3561
  text: span.content,
@@ -2449,7 +3570,7 @@ async function convertInlinesToTextRuns(spans = [], baseDir = process.cwd(), opt
2449
3570
  return runs;
2450
3571
  }
2451
3572
  async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2452
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
3573
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2453
3574
  const resolved = resolveDocumentConfig(doc.metadata, config);
2454
3575
  const docElements = [];
2455
3576
  const themeProps = typeof resolved.theme === "object" ? resolved.theme : {};
@@ -2460,7 +3581,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2460
3581
  const borderHex = (themeProps.borderColor || "#E2E8F0").replace("#", "");
2461
3582
  const cardBgHex = (themeProps.cardBackground || "#F8FAFC").replace("#", "");
2462
3583
  const defaultFont = themeProps.fontFamily ? themeProps.fontFamily.split(",")[0].replace(/['"]/g, "").trim() : "Segoe UI";
2463
- if (resolved.title) {
3584
+ if (resolved.title && !((_a = resolved.coverPage) == null ? void 0 : _a.enabled)) {
2464
3585
  docElements.push(
2465
3586
  new import_docx.Paragraph({
2466
3587
  children: [
@@ -2521,6 +3642,9 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2521
3642
  );
2522
3643
  }
2523
3644
  }
3645
+ if (((_b = resolved.numberHeadings) == null ? void 0 : _b.enabled) !== false && resolved.numberHeadings) {
3646
+ applyHeadingNumbering(doc.nodes, doc.tocEntries, resolved.numberHeadings);
3647
+ }
2524
3648
  if (resolved.toc) {
2525
3649
  const headingNodes = doc.nodes.filter(
2526
3650
  (n) => n.type === "heading" && typeof n.level === "number" && n.level >= 1 && n.level <= 3
@@ -2590,7 +3714,11 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2590
3714
  ]
2591
3715
  });
2592
3716
  docElements.push(tocCard);
2593
- docElements.push(new import_docx.Paragraph({ spacing: { after: 200 } }));
3717
+ docElements.push(
3718
+ new import_docx.Paragraph({
3719
+ children: [new import_docx.PageBreak()]
3720
+ })
3721
+ );
2594
3722
  }
2595
3723
  }
2596
3724
  for (const node of doc.nodes) {
@@ -2600,7 +3728,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2600
3728
  font: defaultFont,
2601
3729
  size: 34,
2602
3730
  // 17pt
2603
- color: textHex,
3731
+ color: primaryDarkHex,
2604
3732
  bold: true
2605
3733
  });
2606
3734
  docElements.push(
@@ -2646,7 +3774,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2646
3774
  font: defaultFont,
2647
3775
  size: 22,
2648
3776
  // 11pt
2649
- color: textHex,
3777
+ color: primaryDarkHex,
2650
3778
  bold: true
2651
3779
  });
2652
3780
  docElements.push(
@@ -2876,7 +4004,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2876
4004
  }
2877
4005
  if (node.type === "table" && node.children) {
2878
4006
  const tableRows = [];
2879
- const numCols = ((_b = (_a = node.children[0]) == null ? void 0 : _a.children) == null ? void 0 : _b.length) || 1;
4007
+ const numCols = ((_d = (_c = node.children[0]) == null ? void 0 : _c.children) == null ? void 0 : _d.length) || 1;
2880
4008
  const colWidth = Math.floor(9e3 / numCols);
2881
4009
  for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {
2882
4010
  const rowNode = node.children[rowIdx];
@@ -2886,7 +4014,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
2886
4014
  if (rowNode.children) {
2887
4015
  for (let colIdx = 0; colIdx < rowNode.children.length; colIdx++) {
2888
4016
  const cellNode = rowNode.children[colIdx];
2889
- const align = (_c = node.align) == null ? void 0 : _c[colIdx];
4017
+ const align = (_e = node.align) == null ? void 0 : _e[colIdx];
2890
4018
  let alignment = import_docx.AlignmentType.LEFT;
2891
4019
  if (align === "center") alignment = import_docx.AlignmentType.CENTER;
2892
4020
  if (align === "right") alignment = import_docx.AlignmentType.RIGHT;
@@ -3025,16 +4153,123 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3025
4153
  }
3026
4154
  continue;
3027
4155
  }
3028
- }
3029
- if (resolved.signatures && resolved.signatures.items.length > 0) {
3030
- const sig = resolved.signatures;
3031
- const numItems = sig.items.length;
3032
- const contentWidth = Math.max(
3033
- 1e3,
3034
- resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
3035
- );
3036
- docElements.push(new import_docx.Paragraph({ spacing: { before: sig.spacingBeforeTwip } }));
3037
- const sigCells = [];
4156
+ if (node.type === "mathBlock") {
4157
+ docElements.push(
4158
+ new import_docx.Paragraph({
4159
+ alignment: import_docx.AlignmentType.CENTER,
4160
+ children: [
4161
+ new import_docx.TextRun({
4162
+ text: node.text || "",
4163
+ font: "Cambria Math",
4164
+ italics: true,
4165
+ size: 24,
4166
+ // 12pt
4167
+ color: textHex
4168
+ })
4169
+ ],
4170
+ spacing: { before: 180, after: 180 },
4171
+ shading: { fill: cardBgHex, type: import_docx.ShadingType.CLEAR },
4172
+ border: {
4173
+ top: { style: import_docx.BorderStyle.SINGLE, size: 4, color: borderHex },
4174
+ bottom: { style: import_docx.BorderStyle.SINGLE, size: 4, color: borderHex },
4175
+ left: { style: import_docx.BorderStyle.SINGLE, size: 4, color: borderHex },
4176
+ right: { style: import_docx.BorderStyle.SINGLE, size: 4, color: borderHex }
4177
+ }
4178
+ })
4179
+ );
4180
+ continue;
4181
+ }
4182
+ if (node.type === "columns") {
4183
+ const cols = node.columnsCount || 2;
4184
+ const contentWidth = Math.max(
4185
+ 1e3,
4186
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
4187
+ );
4188
+ const cellWidthDxa = Math.floor(contentWidth / cols);
4189
+ const cells = [];
4190
+ for (const col of node.children || []) {
4191
+ const colParagraphs = [];
4192
+ for (const childNode of col.children || []) {
4193
+ if (childNode.type === "heading") {
4194
+ const runs = await convertInlinesToTextRuns(childNode.inlines, baseDir, { font: defaultFont, bold: true, size: 24, color: primaryDarkHex });
4195
+ colParagraphs.push(new import_docx.Paragraph({ children: runs, spacing: { before: 120, after: 60 } }));
4196
+ } else if (childNode.type === "paragraph") {
4197
+ const runs = await convertInlinesToTextRuns(childNode.inlines, baseDir, { font: defaultFont, size: 21, color: textHex });
4198
+ colParagraphs.push(new import_docx.Paragraph({ children: runs, spacing: { after: 100 } }));
4199
+ } else if (childNode.type === "list" && childNode.children) {
4200
+ for (const item of childNode.children) {
4201
+ const runs = await convertInlinesToTextRuns(item.inlines, baseDir, { font: defaultFont, size: 21, color: textHex });
4202
+ colParagraphs.push(new import_docx.Paragraph({ children: [new import_docx.TextRun({ text: "\u2022 ", font: defaultFont, color: primaryHex }), ...runs], spacing: { after: 40 } }));
4203
+ }
4204
+ }
4205
+ }
4206
+ if (colParagraphs.length === 0) colParagraphs.push(new import_docx.Paragraph({}));
4207
+ cells.push(
4208
+ new import_docx.TableCell({
4209
+ width: { size: cellWidthDxa, type: import_docx.WidthType.DXA },
4210
+ borders: {
4211
+ top: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
4212
+ bottom: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
4213
+ left: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" },
4214
+ right: { style: import_docx.BorderStyle.NONE, size: 0, color: "auto" }
4215
+ },
4216
+ margins: { top: 60, bottom: 60, left: 100, right: 100 },
4217
+ children: colParagraphs
4218
+ })
4219
+ );
4220
+ }
4221
+ docElements.push(
4222
+ new import_docx.Table({
4223
+ width: { size: 100, type: import_docx.WidthType.PERCENTAGE },
4224
+ rows: [new import_docx.TableRow({ children: cells })]
4225
+ })
4226
+ );
4227
+ docElements.push(new import_docx.Paragraph({ spacing: { after: 120 } }));
4228
+ continue;
4229
+ }
4230
+ }
4231
+ if (doc.footnoteDefs && doc.footnoteDefs.length > 0) {
4232
+ docElements.push(
4233
+ new import_docx.Paragraph({
4234
+ border: {
4235
+ top: { style: import_docx.BorderStyle.SINGLE, size: 4, color: borderHex, space: 8 }
4236
+ },
4237
+ spacing: { before: 360, after: 120 }
4238
+ })
4239
+ );
4240
+ for (const def of doc.footnoteDefs) {
4241
+ const defRuns = await convertInlinesToTextRuns(def.inlines, baseDir, {
4242
+ font: defaultFont,
4243
+ size: 18,
4244
+ // 9pt
4245
+ color: textMutedHex
4246
+ });
4247
+ docElements.push(
4248
+ new import_docx.Paragraph({
4249
+ children: [
4250
+ new import_docx.TextRun({
4251
+ text: `[${def.id}] `,
4252
+ bold: true,
4253
+ color: primaryDarkHex,
4254
+ font: defaultFont,
4255
+ size: 18
4256
+ }),
4257
+ ...defRuns
4258
+ ],
4259
+ spacing: { after: 60 }
4260
+ })
4261
+ );
4262
+ }
4263
+ }
4264
+ if (resolved.signatures && resolved.signatures.items.length > 0) {
4265
+ const sig = resolved.signatures;
4266
+ const numItems = sig.items.length;
4267
+ const contentWidth = Math.max(
4268
+ 1e3,
4269
+ resolved.paperDimensions.widthTwip - resolved.margins.leftTwip - resolved.margins.rightTwip
4270
+ );
4271
+ docElements.push(new import_docx.Paragraph({ spacing: { before: sig.spacingBeforeTwip } }));
4272
+ const sigCells = [];
3038
4273
  const colWidths = [];
3039
4274
  if (numItems === 1) {
3040
4275
  const cardWidth = Math.min(3400, Math.floor(contentWidth * 0.42));
@@ -3086,7 +4321,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3086
4321
  const centerPos = Math.round(contentWidthTwip / 2);
3087
4322
  const rightPos = contentWidthTwip;
3088
4323
  const headerRuns = [];
3089
- if ((_d = resolved.header) == null ? void 0 : _d.left) {
4324
+ if ((_f = resolved.header) == null ? void 0 : _f.left) {
3090
4325
  headerRuns.push(
3091
4326
  new import_docx.TextRun({
3092
4327
  text: resolved.header.left.text,
@@ -3099,7 +4334,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3099
4334
  );
3100
4335
  }
3101
4336
  headerRuns.push(new import_docx.TextRun({ text: " " }));
3102
- if ((_e = resolved.header) == null ? void 0 : _e.center) {
4337
+ if ((_g = resolved.header) == null ? void 0 : _g.center) {
3103
4338
  headerRuns.push(
3104
4339
  new import_docx.TextRun({
3105
4340
  text: resolved.header.center.text,
@@ -3112,7 +4347,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3112
4347
  );
3113
4348
  }
3114
4349
  headerRuns.push(new import_docx.TextRun({ text: " " }));
3115
- if ((_f = resolved.header) == null ? void 0 : _f.right) {
4350
+ if ((_h = resolved.header) == null ? void 0 : _h.right) {
3116
4351
  headerRuns.push(
3117
4352
  new import_docx.TextRun({
3118
4353
  text: resolved.header.right.text,
@@ -3151,7 +4386,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3151
4386
  ]
3152
4387
  }) : void 0;
3153
4388
  const footerRuns = [];
3154
- if ((_g = resolved.footer) == null ? void 0 : _g.left) {
4389
+ if ((_i = resolved.footer) == null ? void 0 : _i.left) {
3155
4390
  footerRuns.push(
3156
4391
  new import_docx.TextRun({
3157
4392
  text: resolved.footer.left.text,
@@ -3164,7 +4399,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3164
4399
  );
3165
4400
  }
3166
4401
  footerRuns.push(new import_docx.TextRun({ text: " " }));
3167
- if ((_h = resolved.footer) == null ? void 0 : _h.center) {
4402
+ if ((_j = resolved.footer) == null ? void 0 : _j.center) {
3168
4403
  footerRuns.push(
3169
4404
  new import_docx.TextRun({
3170
4405
  text: resolved.footer.center.text,
@@ -3177,7 +4412,7 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3177
4412
  );
3178
4413
  }
3179
4414
  footerRuns.push(new import_docx.TextRun({ text: " " }));
3180
- if ((_i = resolved.footer) == null ? void 0 : _i.right) {
4415
+ if ((_k = resolved.footer) == null ? void 0 : _k.right) {
3181
4416
  const rZone = resolved.footer.right;
3182
4417
  const rColor = rZone.color.replace("#", "");
3183
4418
  const rSize = (rZone.fontSize || 9) * 2;
@@ -3262,6 +4497,95 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3262
4497
  ]
3263
4498
  }) : void 0;
3264
4499
  const isLandscape = resolved.orientation === "landscape";
4500
+ const docSections = [];
4501
+ if (resolved.coverPage && resolved.coverPage.enabled) {
4502
+ const coverElements = await buildDocxCoverPageElements(
4503
+ resolved.coverPage,
4504
+ defaultFont,
4505
+ textHex,
4506
+ primaryHex,
4507
+ primaryDarkHex,
4508
+ textMutedHex,
4509
+ baseDir
4510
+ );
4511
+ docSections.push({
4512
+ properties: {
4513
+ page: {
4514
+ size: {
4515
+ width: resolved.paperDimensions.widthTwip,
4516
+ height: resolved.paperDimensions.heightTwip,
4517
+ orientation: isLandscape ? import_docx.PageOrientation.LANDSCAPE : import_docx.PageOrientation.PORTRAIT
4518
+ },
4519
+ margin: {
4520
+ top: resolved.margins.topTwip,
4521
+ bottom: resolved.margins.bottomTwip,
4522
+ left: resolved.margins.leftTwip,
4523
+ right: resolved.margins.rightTwip
4524
+ }
4525
+ }
4526
+ },
4527
+ headers: void 0,
4528
+ footers: void 0,
4529
+ children: coverElements
4530
+ });
4531
+ }
4532
+ docSections.push({
4533
+ properties: {
4534
+ page: {
4535
+ pageNumbers: {
4536
+ start: 1,
4537
+ formatType: import_docx.NumberFormat.DECIMAL
4538
+ },
4539
+ size: {
4540
+ width: resolved.paperDimensions.widthTwip,
4541
+ height: resolved.paperDimensions.heightTwip,
4542
+ orientation: isLandscape ? import_docx.PageOrientation.LANDSCAPE : import_docx.PageOrientation.PORTRAIT
4543
+ },
4544
+ margin: {
4545
+ top: resolved.margins.topTwip,
4546
+ bottom: resolved.margins.bottomTwip,
4547
+ left: resolved.margins.leftTwip,
4548
+ right: resolved.margins.rightTwip,
4549
+ header: 720,
4550
+ footer: 720
4551
+ }
4552
+ }
4553
+ },
4554
+ headers: docHeader ? { default: docHeader } : void 0,
4555
+ footers: docFooter ? { default: docFooter } : void 0,
4556
+ children: docElements
4557
+ });
4558
+ if (resolved.backCover && resolved.backCover.enabled) {
4559
+ const backElements = await buildDocxBackCoverElements(
4560
+ resolved.backCover,
4561
+ defaultFont,
4562
+ textHex,
4563
+ primaryHex,
4564
+ primaryDarkHex,
4565
+ textMutedHex,
4566
+ baseDir
4567
+ );
4568
+ docSections.push({
4569
+ properties: {
4570
+ page: {
4571
+ size: {
4572
+ width: resolved.paperDimensions.widthTwip,
4573
+ height: resolved.paperDimensions.heightTwip,
4574
+ orientation: isLandscape ? import_docx.PageOrientation.LANDSCAPE : import_docx.PageOrientation.PORTRAIT
4575
+ },
4576
+ margin: {
4577
+ top: resolved.margins.topTwip,
4578
+ bottom: resolved.margins.bottomTwip,
4579
+ left: resolved.margins.leftTwip,
4580
+ right: resolved.margins.rightTwip
4581
+ }
4582
+ }
4583
+ },
4584
+ headers: void 0,
4585
+ footers: void 0,
4586
+ children: backElements
4587
+ });
4588
+ }
3265
4589
  const document = new import_docx.Document({
3266
4590
  styles: {
3267
4591
  default: {
@@ -3282,33 +4606,250 @@ async function buildDocxDocument(doc, config, baseDir = process.cwd()) {
3282
4606
  }
3283
4607
  }
3284
4608
  },
3285
- sections: [
3286
- {
3287
- properties: {
3288
- page: {
3289
- size: {
3290
- width: resolved.paperDimensions.widthTwip,
3291
- height: resolved.paperDimensions.heightTwip,
3292
- orientation: isLandscape ? import_docx.PageOrientation.LANDSCAPE : import_docx.PageOrientation.PORTRAIT
3293
- },
3294
- margin: {
3295
- top: resolved.margins.topTwip,
3296
- bottom: resolved.margins.bottomTwip,
3297
- left: resolved.margins.leftTwip,
3298
- right: resolved.margins.rightTwip,
3299
- header: 720,
3300
- footer: 720
3301
- }
3302
- }
3303
- },
3304
- headers: docHeader ? { default: docHeader } : void 0,
3305
- footers: docFooter ? { default: docFooter } : void 0,
3306
- children: docElements
3307
- }
3308
- ]
4609
+ sections: docSections
3309
4610
  });
3310
4611
  return await import_docx.Packer.toBuffer(document);
3311
4612
  }
4613
+ async function buildDocxBackCoverElements(backCover, defaultFont, textHex, primaryHex, primaryDarkHex, textMutedHex, baseDir) {
4614
+ var _a;
4615
+ const elements = [];
4616
+ elements.push(new import_docx.Paragraph({ spacing: { before: 1800 } }));
4617
+ if (backCover.logo) {
4618
+ const resolvedLogo = await resolveImage(backCover.logo, baseDir);
4619
+ if (resolvedLogo) {
4620
+ const logoW = typeof backCover.logoWidth === "number" ? backCover.logoWidth : 140;
4621
+ const logoType = ((_a = resolvedLogo.mimeType) == null ? void 0 : _a.includes("png")) ? "png" : "jpg";
4622
+ elements.push(
4623
+ new import_docx.Paragraph({
4624
+ children: [
4625
+ new import_docx.ImageRun({
4626
+ data: resolvedLogo.buffer,
4627
+ transformation: {
4628
+ width: logoW,
4629
+ height: Math.round(logoW * 0.75)
4630
+ },
4631
+ type: logoType
4632
+ })
4633
+ ],
4634
+ spacing: { after: 240 }
4635
+ })
4636
+ );
4637
+ }
4638
+ }
4639
+ if (backCover.badge) {
4640
+ elements.push(
4641
+ new import_docx.Paragraph({
4642
+ children: [
4643
+ new import_docx.TextRun({
4644
+ text: `[ ${backCover.badge.toUpperCase()} ]`,
4645
+ font: defaultFont,
4646
+ size: 20,
4647
+ bold: true,
4648
+ color: primaryDarkHex
4649
+ })
4650
+ ],
4651
+ spacing: { after: 240 }
4652
+ })
4653
+ );
4654
+ }
4655
+ elements.push(
4656
+ new import_docx.Paragraph({
4657
+ children: [
4658
+ new import_docx.TextRun({
4659
+ text: backCover.title,
4660
+ font: defaultFont,
4661
+ size: 52,
4662
+ // 26pt
4663
+ bold: true,
4664
+ color: textHex
4665
+ })
4666
+ ],
4667
+ spacing: { after: 140 }
4668
+ })
4669
+ );
4670
+ if (backCover.subtitle) {
4671
+ elements.push(
4672
+ new import_docx.Paragraph({
4673
+ children: [
4674
+ new import_docx.TextRun({
4675
+ text: backCover.subtitle,
4676
+ font: defaultFont,
4677
+ size: 24,
4678
+ // 12pt
4679
+ color: textMutedHex
4680
+ })
4681
+ ],
4682
+ spacing: { after: 480 }
4683
+ })
4684
+ );
4685
+ }
4686
+ elements.push(
4687
+ new import_docx.Paragraph({
4688
+ border: {
4689
+ bottom: { style: import_docx.BorderStyle.SINGLE, size: 16, color: primaryHex, space: 8 }
4690
+ },
4691
+ spacing: { after: 480 }
4692
+ })
4693
+ );
4694
+ const contactRuns = [];
4695
+ if (backCover.company) contactRuns.push(new import_docx.TextRun({ text: `Organization: ${backCover.company}
4696
+ `, font: defaultFont, size: 21, color: textHex }));
4697
+ if (backCover.address) contactRuns.push(new import_docx.TextRun({ text: `Address: ${backCover.address}
4698
+ `, font: defaultFont, size: 21, color: textHex }));
4699
+ if (backCover.email) contactRuns.push(new import_docx.TextRun({ text: `Email: ${backCover.email}
4700
+ `, font: defaultFont, size: 21, color: textHex }));
4701
+ if (backCover.phone) contactRuns.push(new import_docx.TextRun({ text: `Phone: ${backCover.phone}
4702
+ `, font: defaultFont, size: 21, color: textHex }));
4703
+ if (backCover.website) contactRuns.push(new import_docx.TextRun({ text: `Website: ${backCover.website}
4704
+ `, font: defaultFont, size: 21, color: textHex }));
4705
+ if (backCover.social) {
4706
+ for (const [net, url] of Object.entries(backCover.social)) {
4707
+ if (url) {
4708
+ contactRuns.push(new import_docx.TextRun({ text: `${net.toUpperCase()}: ${url}
4709
+ `, font: defaultFont, size: 21, color: textHex }));
4710
+ }
4711
+ }
4712
+ }
4713
+ if (contactRuns.length > 0) {
4714
+ elements.push(
4715
+ new import_docx.Paragraph({
4716
+ children: contactRuns,
4717
+ spacing: { before: 360, after: 360 }
4718
+ })
4719
+ );
4720
+ }
4721
+ if (backCover.copyright) {
4722
+ elements.push(
4723
+ new import_docx.Paragraph({
4724
+ children: [
4725
+ new import_docx.TextRun({
4726
+ text: backCover.copyright,
4727
+ font: defaultFont,
4728
+ size: 18,
4729
+ color: "94A3B8"
4730
+ })
4731
+ ],
4732
+ spacing: { before: 720 }
4733
+ })
4734
+ );
4735
+ }
4736
+ return elements;
4737
+ }
4738
+ async function buildDocxCoverPageElements(cover, defaultFont, textHex, primaryHex, primaryDarkHex, textMutedHex, baseDir) {
4739
+ var _a;
4740
+ const elements = [];
4741
+ elements.push(new import_docx.Paragraph({ spacing: { before: 1800 } }));
4742
+ if (cover.logo) {
4743
+ const resolvedLogo = await resolveImage(cover.logo, baseDir);
4744
+ if (resolvedLogo) {
4745
+ const logoW = typeof cover.logoWidth === "number" ? cover.logoWidth : 140;
4746
+ const logoType = ((_a = resolvedLogo.mimeType) == null ? void 0 : _a.includes("png")) ? "png" : "jpg";
4747
+ elements.push(
4748
+ new import_docx.Paragraph({
4749
+ children: [
4750
+ new import_docx.ImageRun({
4751
+ data: resolvedLogo.buffer,
4752
+ transformation: {
4753
+ width: logoW,
4754
+ height: Math.round(logoW * 0.75)
4755
+ },
4756
+ type: logoType
4757
+ })
4758
+ ],
4759
+ spacing: { after: 240 }
4760
+ })
4761
+ );
4762
+ }
4763
+ }
4764
+ if (cover.badge) {
4765
+ elements.push(
4766
+ new import_docx.Paragraph({
4767
+ children: [
4768
+ new import_docx.TextRun({
4769
+ text: `[ ${cover.badge.toUpperCase()} ]`,
4770
+ font: defaultFont,
4771
+ size: 20,
4772
+ bold: true,
4773
+ color: primaryDarkHex
4774
+ })
4775
+ ],
4776
+ spacing: { after: 240 }
4777
+ })
4778
+ );
4779
+ }
4780
+ elements.push(
4781
+ new import_docx.Paragraph({
4782
+ children: [
4783
+ new import_docx.TextRun({
4784
+ text: cover.title,
4785
+ font: defaultFont,
4786
+ size: 56,
4787
+ // 28pt
4788
+ bold: true,
4789
+ color: textHex
4790
+ })
4791
+ ],
4792
+ spacing: { after: 140 }
4793
+ })
4794
+ );
4795
+ if (cover.subtitle) {
4796
+ elements.push(
4797
+ new import_docx.Paragraph({
4798
+ children: [
4799
+ new import_docx.TextRun({
4800
+ text: cover.subtitle,
4801
+ font: defaultFont,
4802
+ size: 26,
4803
+ // 13pt
4804
+ color: textMutedHex
4805
+ })
4806
+ ],
4807
+ spacing: { after: 480 }
4808
+ })
4809
+ );
4810
+ }
4811
+ elements.push(
4812
+ new import_docx.Paragraph({
4813
+ border: {
4814
+ bottom: { style: import_docx.BorderStyle.SINGLE, size: 16, color: primaryHex, space: 8 }
4815
+ },
4816
+ spacing: { after: 480 }
4817
+ })
4818
+ );
4819
+ if (cover.company || cover.author || cover.version || cover.date) {
4820
+ const metaRuns = [];
4821
+ if (cover.company) metaRuns.push(new import_docx.TextRun({ text: `Organization: ${cover.company}
4822
+ `, font: defaultFont, size: 21, color: textHex }));
4823
+ if (cover.author) metaRuns.push(new import_docx.TextRun({ text: `Author: ${cover.author}
4824
+ `, font: defaultFont, size: 21, color: textHex }));
4825
+ if (cover.version) metaRuns.push(new import_docx.TextRun({ text: `Version: ${cover.version}
4826
+ `, font: defaultFont, size: 21, color: textHex }));
4827
+ if (cover.date) metaRuns.push(new import_docx.TextRun({ text: `Date: ${cover.date}
4828
+ `, font: defaultFont, size: 21, color: textHex }));
4829
+ elements.push(
4830
+ new import_docx.Paragraph({
4831
+ children: metaRuns,
4832
+ spacing: { before: 360, after: 360 }
4833
+ })
4834
+ );
4835
+ }
4836
+ if (cover.footerText) {
4837
+ elements.push(
4838
+ new import_docx.Paragraph({
4839
+ children: [
4840
+ new import_docx.TextRun({
4841
+ text: cover.footerText,
4842
+ font: defaultFont,
4843
+ size: 18,
4844
+ color: "94A3B8"
4845
+ })
4846
+ ],
4847
+ spacing: { before: 720 }
4848
+ })
4849
+ );
4850
+ }
4851
+ return elements;
4852
+ }
3312
4853
  async function buildDocxSignatureCell(item, sig, widthDxa, defaultFont, baseDir) {
3313
4854
  const cellParagraphs = [];
3314
4855
  if (item.title) {
@@ -3536,14 +5077,885 @@ async function compileMarkdown(inputFilePathOrContent, userConfig = {}, onProgre
3536
5077
  };
3537
5078
  }
3538
5079
 
5080
+ // src/server/previewServer.ts
5081
+ var http = __toESM(require("http"));
5082
+ var fs7 = __toESM(require("fs"));
5083
+ var path7 = __toESM(require("path"));
5084
+ async function startPreviewServer(options) {
5085
+ const absoluteFilePath = path7.resolve(process.cwd(), options.filePath);
5086
+ if (!fs7.existsSync(absoluteFilePath)) {
5087
+ throw new Error(`MarkForge preview error: File not found at "${absoluteFilePath}"`);
5088
+ }
5089
+ const baseDir = path7.dirname(absoluteFilePath);
5090
+ const { config: fileConfig } = await loadConfig(void 0, baseDir);
5091
+ const baseConfig = options.config || fileConfig;
5092
+ const port = options.port || 3e3;
5093
+ const sseClients = /* @__PURE__ */ new Set();
5094
+ const broadcastReload = () => {
5095
+ sseClients.forEach((client) => {
5096
+ try {
5097
+ client.write(`event: reload
5098
+ data: ${Date.now()}
5099
+
5100
+ `);
5101
+ } catch {
5102
+ sseClients.delete(client);
5103
+ }
5104
+ });
5105
+ };
5106
+ let debounceTimer = null;
5107
+ const watcher = fs7.watch(baseDir, { recursive: false }, (_event, filename) => {
5108
+ if (!filename) return;
5109
+ const changedPath = path7.resolve(baseDir, filename);
5110
+ if (changedPath === absoluteFilePath || filename.includes("markforge") || filename.endsWith(".css")) {
5111
+ if (debounceTimer) clearTimeout(debounceTimer);
5112
+ debounceTimer = setTimeout(() => {
5113
+ broadcastReload();
5114
+ }, 150);
5115
+ }
5116
+ });
5117
+ const server = http.createServer(async (req, res) => {
5118
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
5119
+ if (url.pathname === "/events") {
5120
+ res.writeHead(200, {
5121
+ "Content-Type": "text/event-stream",
5122
+ "Cache-Control": "no-cache, no-transform",
5123
+ Connection: "keep-alive"
5124
+ });
5125
+ res.write(`data: connected
5126
+
5127
+ `);
5128
+ sseClients.add(res);
5129
+ req.on("close", () => {
5130
+ sseClients.delete(res);
5131
+ });
5132
+ return;
5133
+ }
5134
+ if (url.pathname === "/api/file-content" && req.method === "GET") {
5135
+ try {
5136
+ const content = fs7.readFileSync(absoluteFilePath, "utf-8");
5137
+ res.writeHead(200, { "Content-Type": "application/json" });
5138
+ res.end(
5139
+ JSON.stringify({
5140
+ content,
5141
+ fileName: path7.basename(absoluteFilePath),
5142
+ filePath: absoluteFilePath
5143
+ })
5144
+ );
5145
+ } catch (err) {
5146
+ const msg = err instanceof Error ? err.message : String(err);
5147
+ res.writeHead(500, { "Content-Type": "application/json" });
5148
+ res.end(JSON.stringify({ error: msg }));
5149
+ }
5150
+ return;
5151
+ }
5152
+ if (url.pathname === "/api/save-content" && req.method === "POST") {
5153
+ let body = "";
5154
+ req.on("data", (chunk) => {
5155
+ body += chunk;
5156
+ });
5157
+ req.on("end", () => {
5158
+ try {
5159
+ const parsed = JSON.parse(body);
5160
+ if (typeof parsed.content === "string") {
5161
+ fs7.writeFileSync(absoluteFilePath, parsed.content, "utf-8");
5162
+ broadcastReload();
5163
+ res.writeHead(200, { "Content-Type": "application/json" });
5164
+ res.end(JSON.stringify({ success: true, savedAt: Date.now() }));
5165
+ } else {
5166
+ res.writeHead(400, { "Content-Type": "application/json" });
5167
+ res.end(JSON.stringify({ error: "Missing content field in request body" }));
5168
+ }
5169
+ } catch (err) {
5170
+ const msg = err instanceof Error ? err.message : String(err);
5171
+ res.writeHead(500, { "Content-Type": "application/json" });
5172
+ res.end(JSON.stringify({ error: msg }));
5173
+ }
5174
+ });
5175
+ return;
5176
+ }
5177
+ if (url.pathname === "/api/export" && (req.method === "GET" || req.method === "POST")) {
5178
+ const format = url.searchParams.get("format") || "docx";
5179
+ try {
5180
+ const mdContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5181
+ const doc = parseMarkdownDocument(mdContent);
5182
+ const { config: resolvedConfig } = await loadConfig(void 0, baseDir);
5183
+ const mergedConfig = { ...baseConfig, ...resolvedConfig };
5184
+ const fileBase = path7.basename(absoluteFilePath, path7.extname(absoluteFilePath));
5185
+ if (format === "docx") {
5186
+ const buffer = await buildDocxDocument(doc, mergedConfig, baseDir);
5187
+ res.writeHead(200, {
5188
+ "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
5189
+ "Content-Disposition": `attachment; filename="${fileBase}.docx"`
5190
+ });
5191
+ res.end(buffer);
5192
+ return;
5193
+ } else if (format === "pdf") {
5194
+ const buffer = await buildPdfDocument(doc, mergedConfig, baseDir);
5195
+ res.writeHead(200, {
5196
+ "Content-Type": "application/pdf",
5197
+ "Content-Disposition": `attachment; filename="${fileBase}.pdf"`
5198
+ });
5199
+ res.end(buffer);
5200
+ return;
5201
+ } else {
5202
+ const html = await buildHtmlDocument(doc, mergedConfig, baseDir);
5203
+ res.writeHead(200, {
5204
+ "Content-Type": "text/html; charset=utf-8",
5205
+ "Content-Disposition": `attachment; filename="${fileBase}.html"`
5206
+ });
5207
+ res.end(html);
5208
+ return;
5209
+ }
5210
+ } catch (err) {
5211
+ const msg = err instanceof Error ? err.message : String(err);
5212
+ res.writeHead(500, { "Content-Type": "text/plain" });
5213
+ res.end(`Export failed: ${msg}`);
5214
+ return;
5215
+ }
5216
+ }
5217
+ if (url.pathname === "/document-content") {
5218
+ try {
5219
+ const mdContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5220
+ const doc = parseMarkdownDocument(mdContent);
5221
+ const { config: resolvedConfig } = await loadConfig(void 0, baseDir);
5222
+ const html = await buildHtmlDocument(doc, { ...baseConfig, ...resolvedConfig }, baseDir);
5223
+ const injectedScript = `
5224
+ <script>
5225
+ (function() {
5226
+ var evtSource = new EventSource('/events');
5227
+ evtSource.addEventListener('reload', function() {
5228
+ var scrollPos = window.scrollY;
5229
+ sessionStorage.setItem('markforge_scroll', scrollPos);
5230
+ window.location.reload();
5231
+ });
5232
+ window.addEventListener('load', function() {
5233
+ var saved = sessionStorage.getItem('markforge_scroll');
5234
+ if (saved) {
5235
+ window.scrollTo(0, parseInt(saved, 10));
5236
+ }
5237
+ });
5238
+ })();
5239
+ </script>
5240
+ `;
5241
+ const finalHtml = html.replace("</body>", `${injectedScript}</body>`);
5242
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5243
+ res.end(finalHtml);
5244
+ } catch (err) {
5245
+ const msg = err instanceof Error ? err.message : String(err);
5246
+ res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
5247
+ res.end(`<div style="padding:2rem;font-family:sans-serif;color:#ef4444;background:#fef2f2;border:1px solid #f87171;border-radius:8px;"><h3>MarkForge Compilation Error</h3><pre>${escapeHtml2(msg)}</pre></div>`);
5248
+ }
5249
+ return;
5250
+ }
5251
+ if (url.pathname === "/" || url.pathname === "/index.html") {
5252
+ const fileName = path7.basename(absoluteFilePath);
5253
+ const initialContent = fs7.readFileSync(absoluteFilePath, "utf-8");
5254
+ const appHtml = `<!DOCTYPE html>
5255
+ <html lang="en">
5256
+ <head>
5257
+ <meta charset="UTF-8">
5258
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5259
+ <title>MarkForge Live Studio - ${escapeHtml2(fileName)}</title>
5260
+ <link rel="preconnect" href="https://fonts.googleapis.com">
5261
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
5262
+ <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
5263
+ <style>
5264
+ :root {
5265
+ --mf-primary: #0D998D;
5266
+ --mf-primary-dark: #008277;
5267
+ --mf-primary-light: #ECFDFD;
5268
+ --mf-primary-border: #33CDCF;
5269
+ --mf-dark: #0F172A;
5270
+ --mf-slate: #1E293B;
5271
+ --mf-editor-bg: #0F172A;
5272
+ --mf-editor-gutter: #1E293B;
5273
+ --mf-editor-text: #F8FAFC;
5274
+ --mf-muted: #64748B;
5275
+ --mf-light-border: #E2E8F0;
5276
+ --mf-bg: #F1F5F9;
5277
+ }
5278
+ * { box-sizing: border-box; margin: 0; padding: 0; }
5279
+ body {
5280
+ font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
5281
+ background: var(--mf-bg);
5282
+ color: var(--mf-dark);
5283
+ display: flex;
5284
+ flex-direction: column;
5285
+ height: 100vh;
5286
+ overflow: hidden;
5287
+ }
5288
+ header {
5289
+ background: #FFFFFF;
5290
+ border-bottom: 1px solid var(--mf-light-border);
5291
+ min-height: 56px;
5292
+ display: flex;
5293
+ flex-wrap: wrap;
5294
+ align-items: center;
5295
+ justify-content: space-between;
5296
+ padding: 0.4rem 1.2rem;
5297
+ z-index: 10;
5298
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
5299
+ gap: 0.75rem;
5300
+ }
5301
+ .brand-section {
5302
+ display: flex;
5303
+ align-items: center;
5304
+ gap: 0.75rem;
5305
+ }
5306
+ .brand-badge {
5307
+ font-size: 0.72rem;
5308
+ font-weight: 800;
5309
+ letter-spacing: 0.08em;
5310
+ background: var(--mf-dark);
5311
+ color: #FFFFFF;
5312
+ padding: 0.25rem 0.55rem;
5313
+ border-radius: 4px;
5314
+ text-transform: uppercase;
5315
+ }
5316
+ .file-name {
5317
+ font-size: 0.9rem;
5318
+ font-weight: 700;
5319
+ color: var(--mf-dark);
5320
+ }
5321
+ .sync-status {
5322
+ display: flex;
5323
+ align-items: center;
5324
+ gap: 0.35rem;
5325
+ font-size: 0.75rem;
5326
+ font-weight: 600;
5327
+ color: var(--mf-primary-dark);
5328
+ background: var(--mf-primary-light);
5329
+ padding: 0.2rem 0.55rem;
5330
+ border-radius: 9999px;
5331
+ border: 1px solid var(--mf-primary-border);
5332
+ }
5333
+ .sync-dot {
5334
+ width: 7px;
5335
+ height: 7px;
5336
+ background-color: var(--mf-primary);
5337
+ border-radius: 50%;
5338
+ box-shadow: 0 0 0 2px rgba(13, 153, 141, 0.2);
5339
+ }
5340
+ .toolbar-section {
5341
+ display: flex;
5342
+ align-items: center;
5343
+ gap: 0.3rem;
5344
+ background: #F8FAFC;
5345
+ padding: 0.25rem 0.4rem;
5346
+ border-radius: 6px;
5347
+ border: 1px solid var(--mf-light-border);
5348
+ }
5349
+ .tool-btn {
5350
+ font-family: 'JetBrains Mono', monospace;
5351
+ font-size: 0.75rem;
5352
+ font-weight: 600;
5353
+ padding: 0.25rem 0.45rem;
5354
+ background: transparent;
5355
+ border: 1px solid transparent;
5356
+ border-radius: 4px;
5357
+ cursor: pointer;
5358
+ color: var(--mf-slate);
5359
+ transition: all 0.1s ease;
5360
+ }
5361
+ .tool-btn:hover {
5362
+ background: #FFFFFF;
5363
+ border-color: var(--mf-light-border);
5364
+ color: var(--mf-primary-dark);
5365
+ }
5366
+ .tool-divider {
5367
+ width: 1px;
5368
+ height: 16px;
5369
+ background: var(--mf-light-border);
5370
+ margin: 0 0.15rem;
5371
+ }
5372
+ .controls {
5373
+ display: flex;
5374
+ align-items: center;
5375
+ gap: 0.5rem;
5376
+ }
5377
+ .view-toggles {
5378
+ display: flex;
5379
+ background: #F1F5F9;
5380
+ padding: 2px;
5381
+ border-radius: 6px;
5382
+ border: 1px solid var(--mf-light-border);
5383
+ }
5384
+ .toggle-btn {
5385
+ font-family: inherit;
5386
+ font-size: 0.74rem;
5387
+ font-weight: 600;
5388
+ padding: 0.25rem 0.55rem;
5389
+ border: none;
5390
+ background: transparent;
5391
+ border-radius: 4px;
5392
+ cursor: pointer;
5393
+ color: var(--mf-muted);
5394
+ transition: all 0.15s ease;
5395
+ }
5396
+ .toggle-btn.active {
5397
+ background: #FFFFFF;
5398
+ color: var(--mf-dark);
5399
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
5400
+ }
5401
+ .btn {
5402
+ font-family: inherit;
5403
+ font-size: 0.78rem;
5404
+ font-weight: 600;
5405
+ padding: 0.35rem 0.75rem;
5406
+ border-radius: 6px;
5407
+ cursor: pointer;
5408
+ text-decoration: none;
5409
+ transition: all 0.15s ease;
5410
+ display: inline-flex;
5411
+ align-items: center;
5412
+ gap: 0.3rem;
5413
+ border: 1px solid var(--mf-light-border);
5414
+ background: #FFFFFF;
5415
+ color: var(--mf-dark);
5416
+ }
5417
+ .btn:hover {
5418
+ background: #F8FAFC;
5419
+ border-color: #CBD5E1;
5420
+ }
5421
+ .btn-primary {
5422
+ background: var(--mf-primary);
5423
+ color: #FFFFFF;
5424
+ border-color: var(--mf-primary);
5425
+ }
5426
+ .btn-primary:hover {
5427
+ background: var(--mf-primary-dark);
5428
+ border-color: var(--mf-primary-dark);
5429
+ }
5430
+ .save-indicator {
5431
+ font-size: 0.75rem;
5432
+ font-weight: 600;
5433
+ color: var(--mf-muted);
5434
+ min-width: 65px;
5435
+ text-align: right;
5436
+ }
5437
+ .save-indicator.saved {
5438
+ color: var(--mf-primary-dark);
5439
+ }
5440
+ .save-indicator.saving {
5441
+ color: #D97706;
5442
+ }
5443
+ .save-indicator.unsaved {
5444
+ color: #E11D48;
5445
+ }
5446
+
5447
+ /* Main Workspace Splitter Layout */
5448
+ main.workspace {
5449
+ flex: 1;
5450
+ display: flex;
5451
+ height: calc(100vh - 56px);
5452
+ overflow: hidden;
5453
+ background: var(--mf-bg);
5454
+ position: relative;
5455
+ }
5456
+ .editor-pane {
5457
+ width: 50%;
5458
+ height: 100%;
5459
+ display: flex;
5460
+ flex-direction: column;
5461
+ background: var(--mf-editor-bg);
5462
+ border-right: 1px solid #334155;
5463
+ overflow: hidden;
5464
+ }
5465
+ .editor-header {
5466
+ background: #090D16;
5467
+ border-bottom: 1px solid #1E293B;
5468
+ padding: 0.4rem 0.8rem;
5469
+ display: flex;
5470
+ align-items: center;
5471
+ justify-content: space-between;
5472
+ color: #94A3B8;
5473
+ font-size: 0.74rem;
5474
+ font-weight: 500;
5475
+ }
5476
+ .editor-container {
5477
+ flex: 1;
5478
+ display: flex;
5479
+ position: relative;
5480
+ overflow: hidden;
5481
+ background: var(--mf-editor-bg);
5482
+ }
5483
+ .line-numbers {
5484
+ width: 44px;
5485
+ padding: 0.8rem 0.4rem;
5486
+ font-family: 'JetBrains Mono', monospace;
5487
+ font-size: 13px;
5488
+ line-height: 1.55;
5489
+ color: #475569;
5490
+ text-align: right;
5491
+ user-select: none;
5492
+ background: var(--mf-editor-gutter);
5493
+ overflow: hidden;
5494
+ border-right: 1px solid #1E293B;
5495
+ }
5496
+ .code-editor {
5497
+ flex: 1;
5498
+ padding: 0.8rem 1rem;
5499
+ font-family: 'JetBrains Mono', monospace;
5500
+ font-size: 13px;
5501
+ line-height: 1.55;
5502
+ color: var(--mf-editor-text);
5503
+ background: transparent;
5504
+ border: none;
5505
+ outline: none;
5506
+ resize: none;
5507
+ white-space: pre;
5508
+ overflow-wrap: normal;
5509
+ overflow: auto;
5510
+ tab-size: 2;
5511
+ }
5512
+
5513
+ /* Draggable Splitter Handle */
5514
+ .splitter {
5515
+ width: 8px;
5516
+ cursor: col-resize;
5517
+ background: #E2E8F0;
5518
+ transition: background 0.15s ease;
5519
+ position: relative;
5520
+ z-index: 5;
5521
+ }
5522
+ .splitter:hover, .splitter.active {
5523
+ background: var(--mf-primary);
5524
+ }
5525
+
5526
+ /* Right Preview Pane */
5527
+ .preview-pane {
5528
+ width: 50%;
5529
+ height: 100%;
5530
+ display: flex;
5531
+ flex-direction: column;
5532
+ background: #FFFFFF;
5533
+ overflow: hidden;
5534
+ }
5535
+ .preview-header {
5536
+ background: #FFFFFF;
5537
+ border-bottom: 1px solid var(--mf-light-border);
5538
+ padding: 0.35rem 0.8rem;
5539
+ display: flex;
5540
+ align-items: center;
5541
+ justify-content: space-between;
5542
+ color: var(--mf-muted);
5543
+ font-size: 0.74rem;
5544
+ font-weight: 600;
5545
+ }
5546
+ .viewport-selector {
5547
+ display: flex;
5548
+ gap: 0.25rem;
5549
+ }
5550
+ .vp-btn {
5551
+ font-size: 0.72rem;
5552
+ padding: 0.15rem 0.4rem;
5553
+ border: 1px solid var(--mf-light-border);
5554
+ background: #F8FAFC;
5555
+ border-radius: 4px;
5556
+ cursor: pointer;
5557
+ color: var(--mf-muted);
5558
+ }
5559
+ .vp-btn.active {
5560
+ background: var(--mf-primary-light);
5561
+ color: var(--mf-primary-dark);
5562
+ border-color: var(--mf-primary-border);
5563
+ }
5564
+ .preview-wrapper {
5565
+ flex: 1;
5566
+ display: flex;
5567
+ justify-content: center;
5568
+ align-items: stretch;
5569
+ background: #F1F5F9;
5570
+ overflow: hidden;
5571
+ }
5572
+ iframe {
5573
+ width: 100%;
5574
+ height: 100%;
5575
+ border: none;
5576
+ background: #FFFFFF;
5577
+ transition: max-width 0.2s ease;
5578
+ }
5579
+ .author-footer {
5580
+ font-size: 0.72rem;
5581
+ color: var(--mf-muted);
5582
+ padding-right: 0.5rem;
5583
+ }
5584
+ .author-footer a {
5585
+ color: var(--mf-primary-dark);
5586
+ text-decoration: none;
5587
+ font-weight: 600;
5588
+ }
5589
+ </style>
5590
+ </head>
5591
+ <body>
5592
+ <header>
5593
+ <div class="brand-section">
5594
+ <span class="brand-badge">MARKFORGE STUDIO</span>
5595
+ <span class="file-name" title="${escapeHtml2(absoluteFilePath)}">${escapeHtml2(fileName)}</span>
5596
+ <div class="sync-status">
5597
+ <div class="sync-dot"></div>
5598
+ <span>Live Sync Active</span>
5599
+ </div>
5600
+ </div>
5601
+
5602
+ <!-- Quick Formatting Toolbar -->
5603
+ <div class="toolbar-section">
5604
+ <button class="tool-btn" onclick="insertFormat('h1')" title="Heading 1">H1</button>
5605
+ <button class="tool-btn" onclick="insertFormat('h2')" title="Heading 2">H2</button>
5606
+ <button class="tool-btn" onclick="insertFormat('h3')" title="Heading 3">H3</button>
5607
+ <div class="tool-divider"></div>
5608
+ <button class="tool-btn" onclick="insertFormat('bold')" title="Bold">B</button>
5609
+ <button class="tool-btn" onclick="insertFormat('italic')" title="Italic">I</button>
5610
+ <button class="tool-btn" onclick="insertFormat('code')" title="Inline Code">&lt;&gt;</button>
5611
+ <button class="tool-btn" onclick="insertFormat('quote')" title="Blockquote">&gt;</button>
5612
+ <div class="tool-divider"></div>
5613
+ <button class="tool-btn" onclick="insertFormat('table')" title="GFM Table">Table</button>
5614
+ <button class="tool-btn" onclick="insertFormat('list')" title="List">List</button>
5615
+ <button class="tool-btn" onclick="insertFormat('task')" title="Task Checklist">Task</button>
5616
+ <div class="tool-divider"></div>
5617
+ <button class="tool-btn" onclick="insertFormat('callout')" title="Callout Box">Callout</button>
5618
+ <button class="tool-btn" onclick="insertFormat('math')" title="LaTeX Math">Math</button>
5619
+ <button class="tool-btn" onclick="insertFormat('columns')" title="Multi-Columns">Columns</button>
5620
+ <button class="tool-btn" onclick="insertFormat('footnote')" title="Footnote">Footnote</button>
5621
+ <button class="tool-btn" onclick="insertFormat('mermaid')" title="Mermaid Diagram">Mermaid</button>
5622
+ </div>
5623
+
5624
+ <!-- Controls & View Mode -->
5625
+ <div class="controls">
5626
+ <div class="view-toggles">
5627
+ <button class="toggle-btn active" id="btn-split" onclick="setViewMode('split')">Split</button>
5628
+ <button class="toggle-btn" id="btn-edit" onclick="setViewMode('edit')">Editor</button>
5629
+ <button class="toggle-btn" id="btn-prev" onclick="setViewMode('prev')">Preview</button>
5630
+ </div>
5631
+ <span class="save-indicator saved" id="save-status">Saved</span>
5632
+ <button class="btn btn-primary" onclick="saveContentManual()" title="Save (Ctrl+S)">Save</button>
5633
+ <button class="btn" onclick="exportDoc('docx')" title="Download Word Document">DOCX</button>
5634
+ <button class="btn" onclick="exportDoc('pdf')" title="Download PDF Document">PDF</button>
5635
+ <button class="btn" onclick="printDoc()" title="Print / PDF dialog">Print</button>
5636
+ </div>
5637
+ </header>
5638
+
5639
+ <main class="workspace" id="workspace">
5640
+ <!-- Left: Code Editor Pane -->
5641
+ <div class="editor-pane" id="editor-pane">
5642
+ <div class="editor-header">
5643
+ <span>MARKDOWN SOURCE</span>
5644
+ <span id="editor-stats">Lines: 1 | Words: 0 | UTF-8</span>
5645
+ </div>
5646
+ <div class="editor-container">
5647
+ <div class="line-numbers" id="line-numbers">1</div>
5648
+ <textarea class="code-editor" id="code-editor" spellcheck="false" placeholder="Write markdown here...">${escapeHtml2(initialContent)}</textarea>
5649
+ </div>
5650
+ </div>
5651
+
5652
+ <!-- Middle: Draggable Splitter Handle -->
5653
+ <div class="splitter" id="splitter"></div>
5654
+
5655
+ <!-- Right: Rendered Preview Pane -->
5656
+ <div class="preview-pane" id="preview-pane">
5657
+ <div class="preview-header">
5658
+ <span>RENDERED PREVIEW</span>
5659
+ <div class="viewport-selector">
5660
+ <button class="vp-btn active" onclick="setViewport('100%')" id="vp-full">100% Full</button>
5661
+ <button class="vp-btn" onclick="setViewport('820px')" id="vp-a4">A4 (820px)</button>
5662
+ <button class="vp-btn" onclick="setViewport('440px')" id="vp-mob">Mobile</button>
5663
+ </div>
5664
+ <span class="author-footer">Created by <a href="https://github.com/masumrpg" target="_blank">Ma'sum (@masumrpg)</a></span>
5665
+ </div>
5666
+ <div class="preview-wrapper">
5667
+ <iframe id="preview-frame" src="/document-content"></iframe>
5668
+ </div>
5669
+ </div>
5670
+ </main>
5671
+
5672
+ <script>
5673
+ var editor = document.getElementById('code-editor');
5674
+ var lineNumbers = document.getElementById('line-numbers');
5675
+ var stats = document.getElementById('editor-stats');
5676
+ var saveStatus = document.getElementById('save-status');
5677
+ var previewFrame = document.getElementById('preview-frame');
5678
+ var editorPane = document.getElementById('editor-pane');
5679
+ var previewPane = document.getElementById('preview-pane');
5680
+ var splitter = document.getElementById('splitter');
5681
+ var isDirty = false;
5682
+ var autoSaveTimeout = null;
5683
+
5684
+ // Update Line Numbers & Stats
5685
+ function updateStatsAndLines() {
5686
+ var lines = editor.value.split('\\n');
5687
+ var lineCount = lines.length;
5688
+ var numHtml = '';
5689
+ for (var i = 1; i <= lineCount; i++) {
5690
+ numHtml += i + '<br>';
5691
+ }
5692
+ lineNumbers.innerHTML = numHtml;
5693
+
5694
+ var words = editor.value.trim().length > 0 ? editor.value.trim().split(/\\s+/).length : 0;
5695
+ var chars = editor.value.length;
5696
+ stats.textContent = 'Lines: ' + lineCount + ' | Words: ' + words + ' | Chars: ' + chars + ' | UTF-8';
5697
+ }
5698
+
5699
+ // Synchronize vertical scroll between Line Numbers and Textarea
5700
+ editor.addEventListener('scroll', function() {
5701
+ lineNumbers.scrollTop = editor.scrollTop;
5702
+ });
5703
+
5704
+ // Handle Input & Debounced Auto-Save
5705
+ editor.addEventListener('input', function() {
5706
+ updateStatsAndLines();
5707
+ setSaveState('unsaved');
5708
+ if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
5709
+ autoSaveTimeout = setTimeout(function() {
5710
+ saveContent();
5711
+ }, 600);
5712
+ });
5713
+
5714
+ function setSaveState(state) {
5715
+ if (state === 'saved') {
5716
+ saveStatus.textContent = 'Saved';
5717
+ saveStatus.className = 'save-indicator saved';
5718
+ isDirty = false;
5719
+ } else if (state === 'saving') {
5720
+ saveStatus.textContent = 'Saving...';
5721
+ saveStatus.className = 'save-indicator saving';
5722
+ } else {
5723
+ saveStatus.textContent = 'Changes...';
5724
+ saveStatus.className = 'save-indicator unsaved';
5725
+ isDirty = true;
5726
+ }
5727
+ }
5728
+
5729
+ // Save Content via API
5730
+ function saveContent(callback) {
5731
+ setSaveState('saving');
5732
+ fetch('/api/save-content', {
5733
+ method: 'POST',
5734
+ headers: { 'Content-Type': 'application/json' },
5735
+ body: JSON.stringify({ content: editor.value }),
5736
+ })
5737
+ .then(function(res) { return res.json(); })
5738
+ .then(function(data) {
5739
+ if (data.success) {
5740
+ setSaveState('saved');
5741
+ if (callback) callback();
5742
+ } else {
5743
+ saveStatus.textContent = 'Save Error';
5744
+ }
5745
+ })
5746
+ .catch(function() {
5747
+ saveStatus.textContent = 'Save Error';
5748
+ });
5749
+ }
5750
+
5751
+ function saveContentManual() {
5752
+ saveContent();
5753
+ }
5754
+
5755
+ // Keyboard Shortcuts: Tab (2 spaces), Shift+Tab, Ctrl+S
5756
+ editor.addEventListener('keydown', function(e) {
5757
+ if ((e.ctrlKey || e.metaKey) && e.key === 's') {
5758
+ e.preventDefault();
5759
+ saveContent();
5760
+ return;
5761
+ }
5762
+
5763
+ if (e.key === 'Tab') {
5764
+ e.preventDefault();
5765
+ var start = this.selectionStart;
5766
+ var end = this.selectionEnd;
5767
+ this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
5768
+ this.selectionStart = this.selectionEnd = start + 2;
5769
+ updateStatsAndLines();
5770
+ setSaveState('unsaved');
5771
+ if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
5772
+ autoSaveTimeout = setTimeout(saveContent, 600);
5773
+ }
5774
+ });
5775
+
5776
+ // Formatting Snippet Injector
5777
+ function insertFormat(type) {
5778
+ var start = editor.selectionStart;
5779
+ var end = editor.selectionEnd;
5780
+ var selected = editor.value.substring(start, end);
5781
+ var replacement = '';
5782
+
5783
+ switch (type) {
5784
+ case 'h1': replacement = '# ' + (selected || 'Heading 1'); break;
5785
+ case 'h2': replacement = '## ' + (selected || 'Heading 2'); break;
5786
+ case 'h3': replacement = '### ' + (selected || 'Heading 3'); break;
5787
+ case 'bold': replacement = '**' + (selected || 'bold text') + '**'; break;
5788
+ case 'italic': replacement = '*' + (selected || 'italic text') + '*'; break;
5789
+ case 'code': replacement = '\`' + (selected || 'inline code') + '\`'; break;
5790
+ case 'quote': replacement = '> ' + (selected || 'Quote text'); break;
5791
+ case 'table':
5792
+ replacement = '\\n| Column 1 | Column 2 | Column 3 |\\n| :--- | :---: | ---: |\\n| Data A | Data B | Data C |\\n| Data D | Data E | Data F |\\n';
5793
+ break;
5794
+ case 'list': replacement = '- ' + (selected || 'List item'); break;
5795
+ case 'task': replacement = '- [ ] ' + (selected || 'Task item'); break;
5796
+ case 'callout':
5797
+ replacement = '> [!NOTE]\\n> ' + (selected || 'This is an important callout note.');
5798
+ break;
5799
+ case 'math':
5800
+ replacement = '$$\\n' + (selected || '\\\\int_{-\\\\infty}^{\\\\infty} e^{-x^2} dx = \\\\sqrt{\\\\pi}') + '\\n$$';
5801
+ break;
5802
+ case 'columns':
5803
+ replacement = ':::columns 2\\n:::col\\n### Left Column\\n' + (selected || 'Content on the left.') + '\\n:::\\n:::col\\n### Right Column\\nContent on the right.\\n:::\\n:::';
5804
+ break;
5805
+ case 'footnote':
5806
+ replacement = (selected || 'Statement with footnote') + '[^1]\\n\\n[^1]: Note description text.';
5807
+ break;
5808
+ case 'mermaid':
5809
+ replacement = '\\n\`\`\`mermaid\\ngraph TD\\n A[Start] --> B(Process)\\n B --> C{Decision}\\n C -->|Yes| D[Done]\\n C -->|No| B\\n\`\`\`\\n';
5810
+ break;
5811
+ }
5812
+
5813
+ editor.value = editor.value.substring(0, start) + replacement + editor.value.substring(end);
5814
+ editor.selectionStart = editor.selectionEnd = start + replacement.length;
5815
+ editor.focus();
5816
+ updateStatsAndLines();
5817
+ setSaveState('unsaved');
5818
+ if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
5819
+ autoSaveTimeout = setTimeout(saveContent, 600);
5820
+ }
5821
+
5822
+ // View Mode Toggle (Split / Editor Only / Preview Only)
5823
+ function setViewMode(mode) {
5824
+ document.getElementById('btn-split').classList.remove('active');
5825
+ document.getElementById('btn-edit').classList.remove('active');
5826
+ document.getElementById('btn-prev').classList.remove('active');
5827
+
5828
+ if (mode === 'split') {
5829
+ document.getElementById('btn-split').classList.add('active');
5830
+ editorPane.style.display = 'flex';
5831
+ editorPane.style.width = '50%';
5832
+ previewPane.style.display = 'flex';
5833
+ previewPane.style.width = '50%';
5834
+ splitter.style.display = 'block';
5835
+ } else if (mode === 'edit') {
5836
+ document.getElementById('btn-edit').classList.add('active');
5837
+ editorPane.style.display = 'flex';
5838
+ editorPane.style.width = '100%';
5839
+ previewPane.style.display = 'none';
5840
+ splitter.style.display = 'none';
5841
+ } else if (mode === 'prev') {
5842
+ document.getElementById('btn-prev').classList.add('active');
5843
+ editorPane.style.display = 'none';
5844
+ previewPane.style.display = 'flex';
5845
+ previewPane.style.width = '100%';
5846
+ splitter.style.display = 'none';
5847
+ }
5848
+ }
5849
+
5850
+ // Viewport Width Resizer
5851
+ function setViewport(width) {
5852
+ document.getElementById('vp-full').classList.remove('active');
5853
+ document.getElementById('vp-a4').classList.remove('active');
5854
+ document.getElementById('vp-mob').classList.remove('active');
5855
+
5856
+ if (width === '100%') {
5857
+ document.getElementById('vp-full').classList.add('active');
5858
+ previewFrame.style.maxWidth = '100%';
5859
+ } else if (width === '820px') {
5860
+ document.getElementById('vp-a4').classList.add('active');
5861
+ previewFrame.style.maxWidth = '820px';
5862
+ } else if (width === '440px') {
5863
+ document.getElementById('vp-mob').classList.add('active');
5864
+ previewFrame.style.maxWidth = '440px';
5865
+ }
5866
+ }
5867
+
5868
+ // Draggable Splitter Handle Logic
5869
+ var isDragging = false;
5870
+ splitter.addEventListener('mousedown', function(e) {
5871
+ isDragging = true;
5872
+ splitter.classList.add('active');
5873
+ document.body.style.cursor = 'col-resize';
5874
+ document.body.style.userSelect = 'none';
5875
+ });
5876
+
5877
+ window.addEventListener('mousemove', function(e) {
5878
+ if (!isDragging) return;
5879
+ var totalWidth = document.getElementById('workspace').clientWidth;
5880
+ var newEditorWidth = (e.clientX / totalWidth) * 100;
5881
+ if (newEditorWidth > 15 && newEditorWidth < 85) {
5882
+ editorPane.style.width = newEditorWidth + '%';
5883
+ previewPane.style.width = (100 - newEditorWidth) + '%';
5884
+ }
5885
+ });
5886
+
5887
+ window.addEventListener('mouseup', function() {
5888
+ if (isDragging) {
5889
+ isDragging = false;
5890
+ splitter.classList.remove('active');
5891
+ document.body.style.cursor = '';
5892
+ document.body.style.userSelect = '';
5893
+ }
5894
+ });
5895
+
5896
+ // Document Print Action
5897
+ function printDoc() {
5898
+ previewFrame.contentWindow.print();
5899
+ }
5900
+
5901
+ // Document Export Action
5902
+ function exportDoc(format) {
5903
+ saveContent(function() {
5904
+ window.location.href = '/api/export?format=' + format;
5905
+ });
5906
+ }
5907
+
5908
+ // Initialize line stats
5909
+ updateStatsAndLines();
5910
+ </script>
5911
+ </body>
5912
+ </html>`;
5913
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
5914
+ res.end(appHtml);
5915
+ return;
5916
+ }
5917
+ res.writeHead(404, { "Content-Type": "text/plain" });
5918
+ res.end("Not Found");
5919
+ });
5920
+ return new Promise((resolve6, reject) => {
5921
+ server.listen(port, () => {
5922
+ const url = `http://localhost:${port}`;
5923
+ resolve6({
5924
+ server,
5925
+ port,
5926
+ url,
5927
+ close: async () => {
5928
+ watcher.close();
5929
+ sseClients.forEach((client) => {
5930
+ try {
5931
+ client.end();
5932
+ } catch {
5933
+ }
5934
+ });
5935
+ sseClients.clear();
5936
+ return new Promise((res) => {
5937
+ server.close(() => res());
5938
+ });
5939
+ }
5940
+ });
5941
+ });
5942
+ server.on("error", (err) => {
5943
+ reject(err);
5944
+ });
5945
+ });
5946
+ }
5947
+ function escapeHtml2(str) {
5948
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
5949
+ }
5950
+
3539
5951
  // src/config/defineConfig.ts
3540
5952
  function defineConfig(config) {
3541
5953
  return config;
3542
5954
  }
3543
5955
 
3544
5956
  // src/version.ts
3545
- var fs7 = __toESM(require("fs"));
3546
- var path7 = __toESM(require("path"));
5957
+ var fs8 = __toESM(require("fs"));
5958
+ var path8 = __toESM(require("path"));
3547
5959
  var import_node_url4 = require("url");
3548
5960
  var import_meta = {};
3549
5961
  try {
@@ -3566,32 +5978,33 @@ try {
3566
5978
  }
3567
5979
  } catch {
3568
5980
  }
3569
- var FALLBACK_VERSION = "0.3.0";
3570
5981
  function readVersionFromPackageJson(fromDir) {
3571
5982
  let currentDir = fromDir;
3572
- for (let i = 0; i < 6; i++) {
5983
+ for (let i = 0; i < 10; i++) {
3573
5984
  try {
3574
- const pkgJsonPath = path7.join(currentDir, "package.json");
3575
- if (fs7.existsSync(pkgJsonPath)) {
3576
- const pkg = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
5985
+ const pkgJsonPath = path8.join(currentDir, "package.json");
5986
+ if (fs8.existsSync(pkgJsonPath)) {
5987
+ const pkg = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
3577
5988
  if (pkg.name === "@masumdev/markforge" && pkg.version) {
3578
5989
  return pkg.version;
3579
5990
  }
3580
5991
  }
3581
5992
  } catch {
3582
5993
  }
3583
- const parentDir = path7.dirname(currentDir);
5994
+ const parentDir = path8.dirname(currentDir);
3584
5995
  if (parentDir === currentDir) break;
3585
5996
  currentDir = parentDir;
3586
5997
  }
3587
- return FALLBACK_VERSION;
5998
+ throw new Error(
5999
+ "Failed to resolve '@masumdev/markforge' package version: package.json was not found or is missing a valid 'version' field."
6000
+ );
3588
6001
  }
3589
6002
  function getPackageDir() {
3590
6003
  if (typeof __dirname !== "undefined") {
3591
6004
  return __dirname;
3592
6005
  }
3593
6006
  try {
3594
- return path7.dirname((0, import_node_url4.fileURLToPath)(import_meta.url));
6007
+ return path8.dirname((0, import_node_url4.fileURLToPath)(import_meta.url));
3595
6008
  } catch {
3596
6009
  return process.cwd();
3597
6010
  }
@@ -3603,6 +6016,7 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
3603
6016
  // Annotate the CommonJS export names for ESM import in node:
3604
6017
  0 && (module.exports = {
3605
6018
  DEFAULT_CONFIG,
6019
+ KATEX_INLINE_CSS,
3606
6020
  MARKFORGE_VERSION,
3607
6021
  Orientation,
3608
6022
  OutputFormat,
@@ -3615,6 +6029,7 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
3615
6029
  THEME_DEFAULT,
3616
6030
  Theme,
3617
6031
  WatermarkPosition,
6032
+ applyHeadingNumbering,
3618
6033
  buildDocxDocument,
3619
6034
  buildHtmlDocument,
3620
6035
  buildPdfDocument,
@@ -3631,18 +6046,28 @@ function getMarkforgeVersion(fromDir = getPackageDir()) {
3631
6046
  inlineHtmlImages,
3632
6047
  loadConfig,
3633
6048
  markforge,
6049
+ normalizeBackCover,
6050
+ normalizeCoverPage,
3634
6051
  normalizeHeaderFooter,
3635
6052
  normalizeHeaderFooterSlot,
6053
+ normalizeNumberHeadings,
6054
+ normalizeSecurity,
3636
6055
  normalizeSignatures,
3637
6056
  normalizeWatermark,
3638
6057
  parseInlineSpans,
3639
6058
  parseMarginToTwip,
6059
+ parseMarkdown,
3640
6060
  parseMarkdownDocument,
6061
+ renderBackCoverHtml,
6062
+ renderCoverPageHtml,
3641
6063
  renderInlinesToHtml,
6064
+ renderMathToHtml,
3642
6065
  renderMermaidToPng,
6066
+ renderNodesToHtml,
3643
6067
  replaceDocumentTokens,
3644
6068
  resolveDocumentConfig,
3645
6069
  resolveImage,
3646
6070
  slugify,
6071
+ startPreviewServer,
3647
6072
  tokenizeCodeLine
3648
6073
  });